File Coverage

blib/lib/Finance/Exchange.pm
Criterion Covered Total %
statement 55 56 98.2
branch 9 12 75.0
condition n/a
subroutine 11 11 100.0
pod 1 1 100.0
total 76 80 95.0


line stmt bran cond sub pod time code
1             package Finance::Exchange;
2              
3 3     3   538575 use Moose;
  3         1593213  
  3         25  
4              
5 3     3   33319 use Time::Duration::Concise::Localize;
  3         99126  
  3         172  
6 3     3   1871 use Clone qw(clone);
  3         2163  
  3         246  
7 3     3   1342 use File::ShareDir;
  3         63978  
  3         209  
8 3     3   1583 use YAML::XS qw(LoadFile);
  3         11173  
  3         431  
9              
10             our $VERSION = '0.05';
11              
12             =head1 NAME
13              
14             Finance::Exchange - represents a financial stock exchange object.
15              
16             =head1 VERSION
17              
18             version 0.01
19              
20             =head1 SYNOPSIS
21              
22             use Finance::Exchange;
23              
24             my $exchange_symbol = 'LSE'; # London Stocks Exchange
25             my $exchange = Finance::Exchange->create_exchange($exchange_symbol);
26              
27             =head1 DESCRIPTION
28              
29             This is a generic representation of a financial stock exchange.
30              
31             =head2 USAGE
32              
33             my $exchange = Finance::Exchange->create_exchange('LSE');
34             is $exchange->symbol, 'LSE';
35             is $exchange->display_name, 'London Stock Exchange';
36             is $exchange->trading_days, 'weekdays';
37             is $exchange->trading_timezone, 'Europe/London';
38             # The list of days starts on Sunday and is a set of flags indicating whether
39             # we trade on that day or not
40             is $exchange->trading_days_list, [ 0, 1, 1, 1, 1, 1, 0 ];
41             is $exchange->market_times, { ... };
42             is $exchange->delay_amount, 15, 'LSE minimum delay is 15 minutes';
43             is $exchange->currency, 'GBP', 'LSE is traded in pound sterling';
44             is $exchange->trading_date_can_differ, 0, 'only applies to AU/NZ';
45             ...
46              
47             =cut
48              
49             my ($cached_objects, $exchange_config, $trading_day_aliases);
50              
51             BEGIN {
52 3     3   25 $exchange_config = YAML::XS::LoadFile(File::ShareDir::dist_file('Finance-Exchange', 'exchange.yml'));
53 3         15753 $trading_day_aliases = YAML::XS::LoadFile(File::ShareDir::dist_file('Finance-Exchange', 'exchanges_trading_days_aliases.yml'));
54             }
55              
56             =head2 create_exchange
57              
58             Exchange object constructor.
59              
60             =cut
61              
62             sub create_exchange {
63 295     295 1 642415 my ($class, $symbol) = @_;
64              
65 295 50       556 die 'symbol is required' unless $symbol;
66              
67 295 100       631 if (my $cached = $cached_objects->{$symbol}) {
68 197         349 return $cached;
69             }
70              
71 98         4824 my $params_ref = clone($exchange_config->{$symbol});
72 98 100       203 die 'Config for exchange[' . $symbol . '] not specified in exchange.yml' unless $params_ref;
73              
74 97         181 $params_ref->{_market_times} = delete $params_ref->{market_times};
75 97         3609 my $new = $class->new($params_ref);
76 97         204 $cached_objects->{$symbol} = $new;
77              
78 97         498 return $new;
79             }
80              
81             =head1 ATTRIBUTES
82              
83             =head2 display_name
84              
85             Exchange display name, e.g. London Stock Exchange.
86              
87             =head2 symbol
88              
89             Exchange symbol, e.g. LSE to represent London Stocks Exchange.
90              
91             =head2 trading_days
92              
93             An exchange's trading day category.
94              
95             For example, an exchange that trades from Monday to Friday is given a trading days category of 'weekdays'.
96              
97             The list is enumerated in the exchanges_trading_days_aliases.yml file.
98              
99             =head2 trading_timezone
100              
101             The timezone in which the exchange conducts business.
102              
103             This should be a string which will allow the standard DateTime module to find the proper information.
104              
105             =cut
106              
107             has [qw(display_name symbol trading_days trading_timezone)] => (
108             is => 'ro',
109             required => 1,
110             );
111              
112             has _market_times => (
113             is => 'ro',
114             required => 1,
115             );
116              
117             =head2 trading_days_list
118              
119             List the trading day index which is defined in exchanges_trading_days_aliases.yml.
120              
121             An example of a 'weekdays' trading days list is as follow:
122             - 0 # Sun
123             - 1 # Mon
124             - 1 # Tues
125             - 1 # Wed
126             - 1 # Thurs
127             - 1 # Fri
128             - 0 # Sat
129              
130             =cut
131              
132             has trading_days_list => (
133             is => 'ro',
134             lazy_build => 1,
135             );
136              
137             sub _build_trading_days_list {
138 1     1   2 my $self = shift;
139              
140 1         24 return $trading_day_aliases->{$self->trading_days};
141             }
142              
143             =head2 market_times
144              
145             A hash reference of human-readable exchange trading times in Greenwich Mean Time (GMT).
146              
147             The trading times are broken into three categories:
148              
149             1. standard - which represents the trading times in non Day Light Saving (DST) period.
150             2. dst - which represents the trading time in DST period.
151             3. partial_trading - which represents the trading breaks (e.g. lunch break) in a trading day
152              
153             =cut
154              
155             has market_times => (
156             is => 'ro',
157             lazy_build => 1,
158             );
159              
160             sub _build_market_times {
161 3     3   6 my $self = shift;
162              
163 3         262 my $mt = $self->_market_times;
164 3         6 my $market_times;
165              
166 3         13 foreach my $key (keys %$mt) {
167 9         290 foreach my $trading_segment (keys %{$mt->{$key}}) {
  9         30  
168 28 50       899 if ($trading_segment eq 'day_of_week_extended_trading_breaks') { next; }
  0 100       0  
169             elsif ($trading_segment ne 'trading_breaks') {
170             $market_times->{$key}->{$trading_segment} = Time::Duration::Concise::Localize->new(
171 26         89 interval => $mt->{$key}->{$trading_segment},
172             );
173             } else {
174 2         5 my $break_intervals = $mt->{$key}->{$trading_segment};
175 2         4 my @converted;
176 2         5 foreach my $int (@$break_intervals) {
177 2         9 my $open_int = Time::Duration::Concise::Localize->new(
178             interval => $int->[0],
179             );
180 2         99 my $close_int = Time::Duration::Concise::Localize->new(
181             interval => $int->[1],
182             );
183 2         106 push @converted, [$open_int, $close_int];
184             }
185 2         9 $market_times->{$key}->{$trading_segment} = \@converted;
186             }
187             }
188             }
189              
190 3         190 return $market_times;
191             }
192              
193             =head2 delay_amount
194              
195             The acceptable delay amount of feed on this exchange, in minutes. Default is 60 minutes.
196              
197             =cut
198              
199             has delay_amount => (
200             is => 'ro',
201             isa => 'Num',
202             default => 60,
203             );
204              
205             =head2 currency
206              
207             The currency in which the exchange is traded in.
208              
209             =cut
210              
211             has currency => (
212             is => 'ro',
213             );
214              
215             =head2 trading_date_can_differ
216              
217             A boolean flag to indicate if an exchange would open on the previous GMT date due to DST.
218              
219             =cut
220              
221             has trading_date_can_differ => (
222             is => 'ro',
223             lazy_build => 1,
224             );
225              
226             sub _build_trading_date_can_differ {
227 1     1   2 my $self = shift;
228              
229             my @premidnight_opens =
230 2         120 grep { $_->seconds < 0 }
231 2         85 map { $self->market_times->{$_}->{daily_open} }
232 3         56 grep { exists $self->market_times->{$_}->{daily_open} }
233 1         2 keys %{$self->market_times};
  1         42  
234 1 50       43 return (scalar @premidnight_opens) ? 1 : 0;
235             }
236              
237 3     3   3143 no Moose;
  3         7  
  3         33  
238             __PACKAGE__->meta->make_immutable;
239              
240             1;