File Coverage

lib/Finance/Alpaca.pm
Criterion Covered Total %
statement 72 330 21.8
branch 0 106 0.0
condition 0 2 0.0
subroutine 24 61 39.3
pod 32 33 96.9
total 128 532 24.0


line stmt bran cond sub pod time code
1             package Finance::Alpaca 0.9902 {
2 1     1   1304 use strictures 2;
  1         1668  
  1         39  
3 1     1   890 use Moo;
  1         11777  
  1         5  
4 1     1   1482 use feature 'signatures';
  1         3  
  1         94  
5 1     1   6 no warnings 'experimental::signatures';
  1         1  
  1         30  
6 1     1   658 use Mojo::UserAgent;
  1         453738  
  1         8  
7 1     1   699 use Types::Standard qw[ArrayRef Bool Dict Enum InstanceOf Maybe Num Str Int];
  1         102614  
  1         15  
8 1     1   2411 use Types::UUID;
  1         15299  
  1         11  
9             #
10 1     1   362 use lib '../../lib/';
  1         3  
  1         10  
11 1     1   585 use Finance::Alpaca::DataStream;
  1         3  
  1         41  
12 1     1   436 use Finance::Alpaca::Struct::Account qw[to_Account];
  1         3  
  1         6  
13 1     1   782 use Finance::Alpaca::Struct::Activity qw[to_Activity Activity];
  1         3  
  1         9  
14 1     1   1093 use Finance::Alpaca::Struct::Asset qw[to_Asset Asset];
  1         3  
  1         7  
15 1     1   520 use Finance::Alpaca::Struct::Bar qw[to_Bar Bar];
  1         1  
  1         7  
16 1     1   831 use Finance::Alpaca::Struct::Calendar qw[to_Calendar Calendar];
  1         3  
  1         8  
17 1     1   944 use Finance::Alpaca::Struct::Configuration qw[to_Configuration Configuration];
  1         3  
  1         7  
18 1     1   908 use Finance::Alpaca::Struct::Clock qw[to_Clock];
  1         3  
  1         6  
19 1     1   783 use Finance::Alpaca::Struct::Order qw[to_Order Order];
  1         3  
  1         11  
20 1     1   1044 use Finance::Alpaca::Struct::Position qw[to_Position Position];
  1         3  
  1         7  
21 1     1   574 use Finance::Alpaca::Struct::Quote qw[to_Quote Quote];
  1         2  
  1         8  
22 1     1   476 use Finance::Alpaca::Struct::Trade qw[to_Trade Trade];
  1         2  
  1         8  
23 1     1   855 use Finance::Alpaca::Struct::TradeActivity qw[to_TradeActivity TradeActivity];
  1         3  
  1         7  
24 1     1   910 use Finance::Alpaca::Struct::Watchlist qw[to_Watchlist Watchlist];
  1         3  
  1         9  
25 1     1   921 use Finance::Alpaca::TradeStream;
  1         3  
  1         32  
26 1     1   6 use Finance::Alpaca::Types;
  1         2  
  1         7  
27             #
28             has ua => ( is => 'lazy', isa => InstanceOf ['Mojo::UserAgent'] );
29              
30 0     0     sub _build_ua ($s) {
  0            
  0            
31 0           my $ua = Mojo::UserAgent->new;
32 0           $ua->transactor->name(
33             sprintf 'Finance::Alpaca %f (Perl %s)',
34             $Finance::Alpaca::VERSION, $^V
35             );
36 0           $ua->on(
37 0     0     start => sub ( $ua, $tx ) {
  0            
  0            
38 0 0         $tx->req->headers->header( 'APCA-API-KEY-ID' => $s->keys->[0] ) if $s->has_keys;
39 0 0         $tx->req->headers->header( 'APCA-API-SECRET-KEY' => $s->keys->[1] ) if $s->has_keys;
40             }
41 0           );
42 0           return $ua;
43             }
44             has api_version => ( is => 'ro', isa => Enum [ 1, 2 ], required => 1, default => 2 );
45             has paper => ( is => 'rw', isa => Bool, required => 1, default => 0, coerce => 1 );
46              
47 0     0 0   sub endpoint ($s) {
  0            
  0            
48 0 0         $s->paper ? 'https://paper-api.alpaca.markets' : '';
49             }
50             has keys => ( is => 'rwp', isa => ArrayRef [ Str, 2 ], predicate => 1 );
51             #
52 0     0 1   sub account ($s) {
  0            
  0            
53 0           my $tx = $s->ua->build_tx( GET => $s->endpoint . '/v2/account' );
54 0           $tx = $s->ua->start($tx);
55 0           return to_Account( $tx->result->json );
56             }
57              
58 0     0 1   sub clock ($s) {
  0            
  0            
59 0           my $tx = $s->ua->build_tx( GET => $s->endpoint . '/v2/clock' );
60 0           $tx = $s->ua->start($tx);
61 0           return to_Clock( $tx->result->json );
62             }
63              
64 0     0 1   sub calendar ( $s, %params ) {
  0            
  0            
  0            
65 0           my $params = '';
66             $params .= '?' . join '&', map {
67 0 0         $_ . '='
68 0 0         . ( ref $params{$_} eq 'Time::Moment' ? $params{$_}->to_string() : $params{$_} )
69             } keys %params if keys %params;
70 0           my $tx = $s->ua->build_tx( GET => $s->endpoint . '/v2/calendar' . $params );
71 0           $tx = $s->ua->start($tx);
72 0           return @{ ( ArrayRef [Calendar] )->assert_coerce( $tx->result->json ) };
  0            
73             }
74              
75 0     0 1   sub assets ( $s, %params ) {
  0            
  0            
  0            
76 0           my $params = '';
77             $params .= '?' . join '&', map {
78 0 0         $_ . '='
79 0 0         . ( ref $params{$_} eq 'Time::Moment' ? $params{$_}->to_string() : $params{$_} )
80             } keys %params if keys %params;
81             return @{
82 0           ( ArrayRef [Asset] )->assert_coerce(
  0            
83             $s->ua->get( $s->endpoint . '/v2/assets' . $params )->result->json
84             )
85             };
86              
87             }
88              
89 0     0 1   sub asset ( $s, $symbol_or_asset_id ) {
  0            
  0            
  0            
90 0           my $res = $s->ua->get( $s->endpoint . '/v2/assets/' . $symbol_or_asset_id )->result;
91 0 0         return $res->is_error ? () : to_Asset( $res->json );
92             }
93              
94 0     0 1   sub bars ( $s, %params ) {
  0            
  0            
  0            
95 0           my $symbol = delete $params{symbol};
96 0           my $params = '';
97             $params .= '?' . join '&', map {
98 0 0         $_ . '='
99             . (
100             ref $params{$_} eq 'Time::Moment'
101             ? $params{$_}->strftime('%Y-%m-%dT%H:%M:%S%Z')
102 0 0         : $params{$_}
103             )
104             } keys %params if keys %params;
105 0           my $res = $s->ua->get(
106             sprintf 'https://data.alpaca.markets/v%d/stocks/%s/bars%s',
107             $s->api_version, $symbol, $params
108             )->result;
109             return $res->is_error ? $res->json : (
110             ( next_page_token => $res->json->{next_page_token} ),
111 0 0         map { delete $_->{symbol} => delete $_->{bars} }
  0            
112             ( Dict [ bars => ArrayRef [Bar], symbol => Str, next_page_token => Maybe [Str] ] )
113             ->assert_coerce( $res->json )
114             );
115             }
116              
117 0     0 1   sub quotes ( $s, %params ) {
  0            
  0            
  0            
118 0           my $symbol = delete $params{symbol};
119 0           my $params = '';
120             $params .= '?' . join '&', map {
121 0 0         $_ . '='
122 0 0         . ( ref $params{$_} eq 'Time::Moment' ? $params{$_}->to_string() : $params{$_} )
123             } keys %params if keys %params;
124             return (
125 0           Dict [ quotes => ArrayRef [Quote], symbol => Str, next_page_token => Maybe [Str] ] )
126             ->assert_coerce(
127             $s->ua->get(
128             sprintf 'https://data.alpaca.markets/v%d/stocks/%s/quotes%s',
129             $s->api_version, $symbol, $params
130             )->result->json
131             );
132             }
133              
134 0     0 1   sub trades ( $s, %params ) {
  0            
  0            
  0            
135 0           my $symbol = delete $params{symbol};
136 0           my $params = '';
137             $params .= '?' . join '&', map {
138 0 0         $_ . '='
139 0 0         . ( ref $params{$_} eq 'Time::Moment' ? $params{$_}->to_string() : $params{$_} )
140             } keys %params if keys %params;
141             return (
142 0           Dict [ trades => ArrayRef [Trade], symbol => Str, next_page_token => Maybe [Str] ] )
143             ->assert_coerce(
144             $s->ua->get(
145             sprintf 'https://data.alpaca.markets/v%d/stocks/%s/trades%s',
146             $s->api_version, $symbol, $params
147             )->result->json
148             );
149             }
150              
151 0     0 1   sub trade_stream ( $s, $cb, %params ) {
  0            
  0            
  0            
  0            
152 0           my $stream = Finance::Alpaca::TradeStream->new( cb => $cb );
153 0           $stream->authorize( $s->ua, $s->keys, $s->paper )->catch(
154 0     0     sub ($err) {
  0            
155 0           $stream = ();
156 0           warn "WebSocket error: $err";
157             }
158 0           )->wait;
159 0           $stream;
160             }
161              
162 0     0 1   sub data_stream ( $s, $cb, %params ) {
  0            
  0            
  0            
  0            
163             my $stream = Finance::Alpaca::DataStream->new(
164             cb => $cb,
165 0   0       source => delete $params{source} // 'iex' # iex or sip
166             );
167 0           $stream->authorize( $s->ua, $s->keys )->catch(
168 0     0     sub ($err) {
  0            
169 0           $stream = ();
170 0           warn "WebSocket error: $err";
171             }
172 0           )->wait;
173 0           $stream;
174             }
175              
176 0     0 1   sub orders ( $s, %params ) {
  0            
  0            
  0            
177 0           my $params = '';
178             $params .= '?' . join '&', map {
179 0 0         $_ . '='
180 0 0         . ( ref $params{$_} eq 'Time::Moment' ? $params{$_}->to_string() : $params{$_} )
181             } keys %params if keys %params;
182 0           return ( ArrayRef [Order] )
183             ->assert_coerce( $s->ua->get( $s->endpoint . '/v2/orders' . $params )->result->json );
184             }
185              
186 0     0 1   sub order_by_id ( $s, $order_id, $nested = 0 ) {
  0            
  0            
  0            
  0            
187 0 0         my $res = $s->ua->get(
188             $s->endpoint . '/v2/orders/' . $order_id . ( $nested ? '?nested=1' : '' ) )->result;
189 0 0         return $res->is_error ? () : to_Order( $res->json );
190             }
191              
192 0     0 1   sub order_by_client_id ( $s, $order_id ) {
  0            
  0            
  0            
193 0           my $res = $s->ua->get(
194             $s->endpoint . '/v2/orders:by_client_order_id?client_order_id=' . $order_id )->result;
195 0 0         return $res->is_error ? () : to_Order( $res->json );
196             }
197              
198 0     0 1   sub create_order ( $s, %params ) {
  0            
  0            
  0            
199             $params{extended_hours} = ( !!$params{extended_hours} ) ? 'true' : 'false'
200 0 0         if defined $params{extended_hours};
    0          
201 0           my $res = $s->ua->post( $s->endpoint . '/v2/orders' => json => \%params )->result;
202 0 0         return $res->is_error ? $res->json : to_Order( $res->json );
203             }
204              
205 0     0 1   sub replace_order ( $s, $order_id, %params ) {
  0            
  0            
  0            
  0            
206             $params{extended_hours} = ( !!$params{extended_hours} ) ? 'true' : 'false'
207 0 0         if defined $params{extended_hours};
    0          
208 0           my $res
209             = $s->ua->patch( $s->endpoint . '/v2/orders/' . $order_id => json => \%params )->result;
210 0 0         return $res->is_error ? $res->json : to_Order( $res->json );
211             }
212              
213 0     0 1   sub cancel_orders ($s) {
  0            
  0            
214 0           my $res = $s->ua->delete( $s->endpoint . '/v2/orders' )->result;
215 0 0         return $res->is_error
216             ? $res->json
217             : ( ArrayRef [ Dict [ body => Order, id => Uuid, status => Int ] ] )
218             ->assert_coerce( $res->json );
219             }
220              
221 0     0 1   sub cancel_order ( $s, $order_id ) {
  0            
  0            
  0            
222 0           my $res = $s->ua->delete( $s->endpoint . '/v2/orders/' . $order_id )->result;
223 0           return !$res->is_error;
224             }
225              
226 0     0 1   sub positions ($s) {
  0            
  0            
227             return
228 0           @{ ( ArrayRef [Position] )
  0            
229             ->assert_coerce( $s->ua->get( $s->endpoint . '/v2/positions' )->result->json ) };
230             }
231              
232 0     0 1   sub position ( $s, $symbol_or_asset_id ) {
  0            
  0            
  0            
233 0           my $res = $s->ua->get( $s->endpoint . '/v2/positions/' . $symbol_or_asset_id )->result;
234 0 0         return $res->is_error ? () : to_Position( $res->json );
235             }
236              
237 0     0 1   sub close_all_positions ( $s, $cancel_orders = !1 ) {
  0            
  0            
  0            
238 0 0         my $res
239             = $s->ua->delete(
240             $s->endpoint . '/v2/positions' . ( $cancel_orders ? '?cancel_orders=true' : '' ) )
241             ->result;
242 0 0         return $res->is_error
243             ? $res->json
244             : ( ArrayRef [ Dict [ body => Order, id => Uuid, status => Int ] ] )
245             ->assert_coerce( $res->json );
246             }
247              
248 0     0 1   sub close_position ( $s, $symbol_or_asset_id, $qty = () ) {
  0            
  0            
  0            
  0            
249 0 0         my $res
250             = $s->ua->get(
251             $s->endpoint . '/v2/positions/' . $symbol_or_asset_id . ( $qty ? '?qty=' . $qty : '' ) )
252             ->result;
253 0 0         return $res->is_error ? () : to_Order( $res->json );
254             }
255              
256 0     0 1   sub portfolio_history ( $s, %params ) {
  0            
  0            
  0            
257             $params{extended_hours} = ( !!$params{extended_hours} ) ? 'true' : 'false'
258 0 0         if defined $params{extended_hours};
    0          
259             $params{date_end}
260             = ref $params{date_end} eq 'Time::Moment'
261             ? $params{date_end}->strftime('%F')
262             : $params{date_end}
263 0 0         if defined $params{date_end};
    0          
264 0           my $res = $s->ua->get( $s->endpoint . '/v2/account/portfolio/history' => json => \%params )
265             ->result;
266 0 0         return $res->is_error ? $res->json : (
267             Dict [
268             base_value => Num,
269             equity => ArrayRef [Num],
270             profit_loss => ArrayRef [Num],
271             profit_loss_pct => ArrayRef [Num],
272             timeframe => Str,
273             timestamp => ArrayRef [Timestamp]
274             ]
275             )->assert_coerce( $res->json );
276             }
277              
278 0     0 1   sub watchlists ($s) {
  0            
  0            
279 0           return ( ArrayRef [Watchlist] )
280             ->assert_coerce( $s->ua->get( $s->endpoint . '/v2/watchlists' )->result->json );
281             }
282              
283 0     0 1   sub create_watchlist ( $s, $name, @symbols ) {
  0            
  0            
  0            
  0            
284 0 0         my $res
285             = $s->ua->post( $s->endpoint
286             . '/v2/watchlists' => json =>
287             { name => $name, ( @symbols ? ( symbols => \@symbols ) : () ) } )->result;
288 0 0         return $res->is_error ? ( $res->json ) : to_Watchlist( $res->json );
289             }
290              
291 0     0 1   sub delete_watchlist ( $s, $watchlist_id ) {
  0            
  0            
  0            
292 0           my $res = $s->ua->delete( $s->endpoint . '/v2/watchlists/' . $watchlist_id )->result;
293 0 0         return $res->is_error ? $res->json : 1;
294             }
295              
296 0     0 1   sub watchlist ( $s, $watchlist_id ) {
  0            
  0            
  0            
297 0           my $res = $s->ua->get( $s->endpoint . '/v2/watchlists/' . $watchlist_id )->result;
298 0 0         return $res->is_error ? ( $res->json ) : to_Watchlist( $res->json );
299             }
300              
301 0     0 1   sub update_watchlist ( $s, $watchlist_id, %params ) {
  0            
  0            
  0            
  0            
302 0           my $res
303             = $s->ua->put( $s->endpoint . '/v2/watchlists/' . $watchlist_id => json => {%params} )
304             ->result;
305 0 0         return $res->is_error ? ( $res->json ) : to_Watchlist( $res->json );
306             }
307              
308 0     0 1   sub add_to_watchlist ( $s, $watchlist_id, $symbol ) {
  0            
  0            
  0            
  0            
309 0           my $res
310             = $s->ua->post(
311             $s->endpoint . '/v2/watchlists/' . $watchlist_id => json => { symbol => $symbol } )
312             ->result;
313 0 0         return $res->is_error ? ( $res->json ) : to_Watchlist( $res->json );
314             }
315              
316 0     0 1   sub remove_from_watchlist ( $s, $watchlist_id, $symbol ) {
  0            
  0            
  0            
  0            
317 0           my $res = $s->ua->delete( $s->endpoint . '/v2/watchlists/' . $watchlist_id . '/' . $symbol )
318             ->result;
319 0 0         return $res->is_error ? ( $res->json ) : to_Watchlist( $res->json );
320             }
321              
322 0     0 1   sub configuration ($s) {
  0            
  0            
323 0           my $res = $s->ua->get( $s->endpoint . '/v2/account/configurations' )->result;
324 0 0         return $res->is_error ? ( $res->json ) : to_Configuration( $res->json );
325             }
326              
327 0     0 1   sub modify_configuration ( $s, %params ) {
  0            
  0            
  0            
328 0           my $res = $s->ua->patch( $s->endpoint . '/v2/account/configurations' => json => {%params} )
329             ->result;
330 0 0         return $res->is_error ? ( $res->json ) : to_Configuration( $res->json );
331             }
332              
333 0     0 1   sub activities ( $s, %params ) {
  0            
  0            
  0            
334 0 0         $params{activity_types} = join ',', @{ $params{activity_types} } if $params{activity_types};
  0            
335 0           my $params = '';
336             $params .= '?' . join '&', map {
337 0 0         $_ . '='
338             . (
339             ref $params{$_} eq 'Time::Moment' ? $params{$_}->to_string()
340 0           : ref $params{$_} eq 'ARRAY' ? @{ $params{$_} }
341 0 0         : $params{$_}
    0          
342             )
343             } keys %params if keys %params;
344 0 0         my $res = $s->ua->get(
345             sprintf $s->endpoint . '/v2/account/activities%s',
346             $params ? $params : ''
347             )->result;
348             return $res->is_error
349             ? $res->json
350 0 0         : map { $_->{activity_type} eq 'FILL' ? to_TradeActivity($_) : to_Activity($_) }
351 0 0         @{ $res->json };
  0            
352             }
353             }
354             1;
355             __END__