File Coverage

blib/lib/Finance/Instrument.pm
Criterion Covered Total %
statement 81 83 97.5
branch 12 16 75.0
condition 3 6 50.0
subroutine 18 18 100.0
pod 0 1 0.0
total 114 124 91.9


line stmt bran cond sub pod time code
1             package Finance::Instrument;
2              
3 6     6   40715 use strict;
  6         13  
  6         242  
4 6     6   160 use 5.008_001;
  6         18  
  6         319  
5             our $VERSION = '0.02';
6              
7 6     6   2129 use Moose;
  6         621082  
  6         44  
8 6     6   50518 use methods;
  6         46836  
  6         49  
9 6     6   22316 use Finance::Instrument::Domain;
  6         22  
  6         249  
10 6     6   52 use Finance::Instrument::Exchange;
  6         10  
  6         145  
11 6     6   6248 use MooseX::ClassAttribute;
  6         535111  
  6         38  
12 6     6   1136748 use DateTime;
  6         150361  
  6         1899  
13              
14             class_has global => (is => "rw", isa => "Financial::Instrument::Domain",
15             default => sub { Finance::Instrument::Domain->global },
16             handles => [qw(load_default_exchanges
17             load_instrument_from_yml
18             load_default_instrument
19             get_exchange
20             add_exchange
21             get
22             load_instrument
23             )]);
24              
25             has domain => (is => "ro", isa => "Finance::Instrument::Domain", weak_ref => 1,
26             default => sub { Finance::Instrument::Domain->global });
27              
28             has code => (is => "ro", isa => "Str");
29             has name => (is => "ro", isa => "Str");
30              
31             has attributes => (is => "rw", isa => "HashRef",
32             traits => ['Hash'],
33             default => sub { {} },
34             handles => {
35             attr => 'accessor'
36             });
37              
38             has time_zone => (is => "rw", isa => "Str");
39             has tick_size => (is => "rw", isa => "Num");
40             has exchange => (is => "rw", isa => "Finance::Instrument::Exchange");
41             has session => (is => "rw", isa => "ArrayRef[ArrayRef[Int]]");
42              
43             has override_since => (is => "rw", isa => "HashRef", default => sub { {} });
44              
45             has override_dow => (is => "rw", isa => "HashRef", default => sub { {} });
46              
47 6     6   2379 method BUILD {
  9     9   8372  
  9         18  
48 9         411 $self->domain->add($self);
49             }
50              
51             around attr => sub {
52             my ($next, $self, @args) = @_;
53             my ($key, $val) = @args;
54             my $ret = $self->$next(@args);
55             unless (defined $ret || defined $val) {
56             $ret = $self->exchange->attr($key)
57             }
58             return $ret;
59             };
60              
61             sub get_spec_for_date {
62 7     7 0 126 my ($self, $date) = @_;
63 7         21 my $spec = { map { $_ => $self->$_ } qw(time_zone session tick_size override_dow) };
  28         694  
64 7         20 for (sort keys %{$self->override_since}) {
  7         126  
65 14 100       46 last if $_ gt $date;
66 9         30 $spec = { %$spec, %{$self->override_since->{$_}} };
  9         34  
67             }
68 7 50       31 if ($self->{override_dow}) {
69 7         40 my ($y, $m, $d) = split('-', $date);
70 7   33     63 my $dt = DateTime->new( year => $y, month => $m, day => $d,
71             time_zone => $spec->{time_zone} || $self->time_zone );
72 7 50       3448 $spec = { %$spec, %{$spec->{override_dow}{$dt->day_of_week} || {}} };
  7         36  
73             }
74 7         165 return $spec;
75             }
76              
77 6     6   4705 method derive_session($_dt) {
  5     5   26546  
  5         13  
  5         10  
78 5         27 my $dt = $_dt->clone->truncate(to => 'day');
79 5         2648 my $session = $self->session_for_day($dt);
80              
81 5 100 66     59 if ($session->[-1][1] > 1440 && $_dt <= $dt->clone->add(minutes => $session->[-1][1] - 1440)) {
82 1         1079 my $prev_session = $self->session_for_day($dt->add(days => -1));
83 1 50       15 if ($_dt <= $dt->clone->add(minutes => $session->[-1][1] - 1440)) {
84 0         0 return ($prev_session, $dt, $#{$prev_session});
  0         0  
85             }
86             }
87              
88 5         797 my $idx = 0;
89 5         7 for (@{ $session }) {
  5         20  
90 8         24 my ($start, $end) = map { $dt->clone->add(minutes => $_) } @$_;
  16         6641  
91 8 100       6088 if ($_dt <= $end) {
92 4         321 last;
93             }
94 4         349 ++$idx;
95             }
96              
97 5 100       11 if ($idx >= @{ $session }) {
  5         20  
98 1         3 $idx = 0;
99 1         6 $dt->add(days => 1);
100 1         898 return $self->derive_session($dt);
101             }
102              
103 4         29 return ($session, $dt, $idx);
104             }
105              
106 6     6   3935 method session_for_day($dt) {
  7     7   911  
  7         12  
  7         15  
107 7 50       29 $self->get_spec_for_date($dt->ymd)->{session} || $self->session;
108             }
109              
110 6     6   2231 method trading_time_for_day($dt) {
  1     1   2616  
  1         3  
  1         3  
111 1         6 my ($session) = $self->session_for_day($dt);
112 1         7 my $base = $dt->clone->truncate(to => 'day')->epoch;
113 1         477 return [map { [ $base + $_->[0] * 60, $base + $_->[1] * 60 ] } @$session]
  2         13  
114             }
115              
116             __PACKAGE__->meta->make_immutable;
117 6     6   2519 no Moose;
  6         15  
  6         53  
118             1;
119              
120             __END__
121              
122             =encoding utf-8
123              
124             =for stopwords
125              
126             =head1 NAME
127              
128             Finance::Instrument - Object representation of financial instruments
129              
130             =head1 SYNOPSIS
131              
132             use Finance::Instrument;
133             # load all iso10383 market identification exchanges
134             Finance::Instrument->load_default_exchanges;
135             Finance::Instrument->get_exchange('XHKF');
136              
137             my $spec = { type => 'Futures',
138             exchange => 'XHKF',
139             code => 'HSI',
140             time_zone => 'Asia/Hong_Kong',
141             currency => 'HKD',
142             session => [[555, 720], [810, 975]] };
143              
144             my $hsi = Finance::Instrument->load_instrument($spec);
145             my $contract = $hsi->contract(2012, 2);
146             $contract->trading_time_for_day(DateTime->now);
147              
148             =head1 DESCRIPTION
149              
150             Finance::Instrument models financial instruments and provide utility
151             functions, such as calculating current or next trading hours for a
152             given L<DateTime>, or retrieve near-term futures contract from futures
153             contract series.
154              
155             =head1 AUTHOR
156              
157             Chia-liang Kao E<lt>clkao@clkao.orgE<gt>
158              
159             =head1 LICENSE
160              
161             This library is free software; you can redistribute it and/or modify
162             it under the same terms as Perl itself.
163              
164             =head1 SEE ALSO
165              
166             =cut