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