File Coverage

lib/Finance/Robinhood/Equity/Order.pm
Criterion Covered Total %
statement 32 134 23.8
branch 0 36 0.0
condition 8 38 21.0
subroutine 18 30 60.0
pod 10 10 100.0
total 68 248 27.4


line stmt bran cond sub pod time code
1             package Finance::Robinhood::Equity::Order;
2              
3             =encoding utf-8
4              
5             =for stopwords watchlist watchlists untradable urls
6              
7             =head1 NAME
8              
9             Finance::Robinhood::Equity::Order - Represents a Single Equity Order
10              
11             =head1 SYNOPSIS
12              
13             use Finance::Robinhood;
14             my $rh = Finance::Robinhood->new->login('user', 'pass');
15             my $orders = $rh->equity_orders();
16              
17             for my $order ($orders->all) {
18             $order->cancel if $order->can_cancel;
19             }
20              
21             =head1 METHOD
22              
23             =cut
24              
25             our $VERSION = '0.92_001';
26 1     1   11 use Mojo::Base-base, -signatures;
  1         1  
  1         7  
27 1     1   185 use Mojo::URL;
  1         2  
  1         5  
28 1     1   551 use Finance::Robinhood::Equity::Account;
  1         3  
  1         7  
29 1     1   36 use Finance::Robinhood::Error;
  1         2  
  1         7  
30 1     1   496 use Finance::Robinhood::Equity::Position;
  1         3  
  1         6  
31 1     1   590 use Finance::Robinhood::Equity::Order::Execution;
  1         4  
  1         5  
32              
33             sub _test__init {
34 1     1   12147 my $rh = t::Utility::rh_instance(1);
35 0         0 my $order = $rh->equity_orders->current;
36 0         0 isa_ok( $order, __PACKAGE__ );
37 0         0 t::Utility::stash( 'ORDER', $order ); # Store it for later
38             }
39 1     1   116 use overload '""' => sub ( $s, @ ) { $s->{url} }, fallback => 1;
  1     0   2  
  1         18  
  0         0  
  0         0  
  0         0  
  0         0  
40              
41             sub _test_stringify {
42 1   50 1   1824 t::Utility::stash('ORDER') // skip_all();
43 0         0 like( +t::Utility::stash('ORDER'), qr'https://api.robinhood.com/orders/.+/' );
44             }
45             #
46             has _rh => undef => weak => 1;
47             has [
48             'average_price', 'cumulative_quantity',
49             'extended_hours', 'fees',
50             'id', 'override_day_trade_checks',
51             'override_dtbp_checks', 'price',
52             'quantity', 'ref_id',
53             'reject_reason', 'response_category',
54             'side', 'state',
55             'stop_price', 'time_in_force',
56             'trigger', 'type',
57             'url'
58             ];
59              
60             =head2 C
61              
62             Returns true if the order can be cancelled.
63              
64             =cut
65              
66 0 0   0 1 0 sub can_cancel ($s) { defined $s->{cancel} ? !0 : !1 }
  0         0  
  0         0  
  0         0  
67              
68             sub _test_can_cancel {
69 1     1   1911 my $rh = t::Utility::rh_instance(1);
70 0         0 my $orders = $rh->equity_orders;
71 0         0 my ( $filled, $cancelled );
72 0         0 while ( $orders->has_next ) {
73 0         0 my $order = $orders->next;
74              
75             #$filled = $order if $order->state eq 'queued';
76 0 0       0 $cancelled = $order if $order->state eq 'cancelled';
77 0 0 0     0 last if $filled && $cancelled;
78             }
79             SKIP: {
80 0         0 skip( 'I need to create a new equity order here', 1 );
  0         0  
81 0   0     0 $filled // skip( 'Cannot find a filled equity order', 1 );
82 0         0 my $execution = $filled->executions->[0];
83 0         0 isa_ok( $execution, 'Finance::Robinhood::Equity::Order::Execution' );
84             }
85             SKIP: {
86 0   0     0 $cancelled // skip( 'Cannot find a cancelled equity order', 1 );
  0         0  
87 0         0 is( $cancelled->can_cancel, !1, 'cancelled order cannot be cancelled' );
88             }
89             todo( "Place an order and test if it can be cancelled then cancel it, reload, and retest it" =>
90 0     0   0 sub { pass('ugh') } );
  0         0  
91             }
92              
93             =head2 C
94              
95             Returns the related Finance::Robinhood::Equity::Account object.
96              
97             =cut
98              
99 0     0 1 0 sub account ($s) {
  0         0  
  0         0  
100 0         0 my $res = $s->_rh->_get( $s->{account} );
101             $res->is_success
102 0 0       0 ? Finance::Robinhood::Equity::Account->new( _rh => $s->_rh, %{ $res->json } )
  0 0       0  
103             : Finance::Robinhood::Error->new(
104             $res->is_server_error ? ( details => $res->message ) : $res->json );
105             }
106              
107             sub _test_account {
108 1   50 1   1986 t::Utility::stash('ORDER') // skip_all('No order object in stash');
109 0         0 isa_ok( t::Utility::stash('ORDER')->account, 'Finance::Robinhood::Equity::Account' );
110             }
111              
112             =head2 C
113              
114             Returns the related Finance::Robinhood::Equity::Position object.
115              
116             =cut
117              
118 0     0 1 0 sub position ($s) {
  0         0  
  0         0  
119 0         0 my $res = $s->_rh->_get( $s->{position} );
120             $res->is_success
121 0 0       0 ? Finance::Robinhood::Equity::Position->new( _rh => $s->_rh, %{ $res->json } )
  0 0       0  
122             : Finance::Robinhood::Error->new(
123             $res->is_server_error ? ( details => $res->message ) : $res->json );
124             }
125              
126             sub _test_position {
127 1   50 1   2171 t::Utility::stash('ORDER') // skip_all('No order object in stash');
128 0         0 isa_ok( t::Utility::stash('ORDER')->position, 'Finance::Robinhood::Equity::Position' );
129             }
130              
131             =head2 C
132              
133             Average price per share for all executions so far.
134              
135             =head2 C
136              
137             $order->cancel();
138              
139             If the order can be cancelled, this method will do it.
140              
141             Be aware that the order is still active for about a second after this is called
142             so add a 'smart' delay here and then call C to update the object
143             correctly.
144              
145             =cut
146              
147 0     0 1 0 sub cancel ($s) {
  0         0  
  0         0  
148 0   0     0 $s->can_cancel // return $s;
149 0         0 my $res = $s->_rh->_post( $s->{cancel} );
150 0 0 0     0 $s->reload && return $s if $res->is_success;
151 0 0       0 Finance::Robinhood::Error->new(
152             $res->is_server_error ? ( details => $res->message ) : $res->json );
153             }
154              
155             =head2 C
156              
157             Returns a Time::Moment object.
158              
159             =cut
160              
161 0     0 1 0 sub created_at ($s) {
  0         0  
  0         0  
162 0         0 Time::Moment->from_string( $s->{created_at} );
163             }
164              
165             sub _test_created_at {
166 1   50 1   1907 t::Utility::stash('ORDER') // skip_all('No order object in stash');
167 0         0 isa_ok( t::Utility::stash('ORDER')->created_at, 'Time::Moment' );
168             }
169              
170             =head2 C
171              
172             Number of shares that have been bought or sold with this order's execution(s)
173             so far.
174              
175             =head2 C
176              
177             Returns a list of related Finance::Robinhood::Equity::Order::Execution objects
178             if applicable.
179              
180             =cut
181              
182 0     0 1 0 sub executions ($s) {
  0         0  
  0         0  
183 0         0 map { Finance::Robinhood::Equity::Order::Execution->new( _rh => $s->_rh, %{$_} ) }
  0         0  
184 0         0 @{ $s->{executions} };
  0         0  
185             }
186              
187             sub _test_executions {
188 1     1   1927 my $rh = t::Utility::rh_instance(1);
189 0         0 my $orders = $rh->equity_orders;
190 0         0 my ( $filled, $rejected );
191 0         0 while ( $orders->has_next ) {
192 0         0 my $order = $orders->next;
193 0 0       0 $filled = $order if $order->state eq 'filled';
194 0 0       0 $rejected = $order if $order->state eq 'rejected';
195 0 0 0     0 last if $filled && $rejected;
196             }
197             SKIP: {
198 0   0     0 $filled // skip( 'Cannot find a filled equity order', 1 );
  0         0  
199 0         0 my ($execution) = $filled->executions;
200 0         0 isa_ok( $execution, 'Finance::Robinhood::Equity::Order::Execution' );
201             }
202             SKIP: {
203 0   0     0 $rejected // skip( 'Cannot find a rejected equity order', 1 );
  0         0  
204 0         0 is( [ $rejected->executions ], [], 'rejected order has no executions' );
205             }
206             }
207              
208             =head2 C
209              
210             Returns a true value if this order is set to execute during extended hours and
211             pre-market.
212              
213             =head2 C
214              
215             Fees charged by Robinhood. Always C<0.00>.
216              
217             =head2 C
218              
219             UUID used to identify this specific order.
220              
221             =head2 C
222              
223             Returns the related Finance::Robinhood::Equity::Instrument object.
224              
225             =cut
226              
227 0     0 1 0 sub instrument($s) {
  0         0  
  0         0  
228 0         0 my $res = $s->_rh->_get( $s->{instrument} );
229             $res->is_success
230 0 0       0 ? Finance::Robinhood::Equity::Instrument->new( _rh => $s->_rh, %{ $res->json } )
  0 0       0  
231             : Finance::Robinhood::Error->new(
232             $res->is_server_error ? ( details => $res->message ) : $res->json );
233             }
234              
235             sub _test_instrument {
236 1   50 1   1924 t::Utility::stash('ORDER') // skip_all('No order object in stash');
237 0         0 isa_ok( t::Utility::stash('ORDER')->instrument, 'Finance::Robinhood::Equity::Instrument' );
238             }
239              
240             =head2 C
241              
242             Returns a Time::Moment object if applicable.
243              
244             =cut
245              
246 0     0 1 0 sub last_transaction_at($s) {
  0         0  
  0         0  
247 0 0       0 defined $s->{last_transaction_at} ? Time::Moment->from_string( $s->{last_transaction_at} ) : ();
248             }
249              
250             sub _test_last_transaction_at {
251 1   50 1   1878 t::Utility::stash('ORDER') // skip_all('No order in stash');
252 0         0 my $last_transaction_at = t::Utility::stash('ORDER')->last_transaction_at;
253 0 0       0 skip_all('No transactions... goodbye') if !defined $last_transaction_at;
254 0         0 isa_ok( $last_transaction_at, 'Time::Moment' );
255             }
256              
257             =head2 C
258              
259             A boolean value indicating whether this order bypassed PDT checks.
260              
261             =head2 C
262              
263             A boolean value indicating whether this order bypassed day trade buying power
264             checks.
265              
266             Risky Regulation T violation warning!
267              
268             =head2 C
269              
270             The price used as an exact limit (for limit and stop-limit orders) or as the
271             price to collar from (for market and stop-loss orders).
272              
273             =head2 C
274              
275             Returns the total number of shares in this order.
276              
277             =head2 C
278              
279             Client generated UUID is returned.
280              
281             =head2 C
282              
283             If the order was rejected, this will be filled with the reason.
284              
285             =head2 C
286              
287             Returns the response category if applicable.
288              
289             =head2 C
290              
291             Indicates if this is an order to C or C.
292              
293             =head2 C
294              
295             One of the following:
296              
297             =over
298              
299             =item C
300              
301             =item C
302              
303             =item C
304              
305             =item C
306              
307             =item C
308              
309             =item C
310              
311             =item C
312              
313             =item C
314              
315             =item C
316              
317             =item C
318              
319             =back
320              
321             =head2 C
322              
323             Returns a float if the trigger type is C, otherwise, C.
324              
325             =head2 C
326              
327             Returns the Time-in-Force value. C (good for day) or C (good 'til
328             cancelled).
329              
330             =head2 C
331              
332             Returns the trigger. C or C.
333              
334             =head2 C
335              
336             Returns the order type. C or C.
337              
338             =head2 C
339              
340             Returns a Time::Moment object.
341              
342             =cut
343              
344 0     0 1 0 sub updated_at($s) {
  0         0  
  0         0  
345 0         0 Time::Moment->from_string( $s->{updated_at} );
346             }
347              
348             sub _test_updated_at {
349 1   50 1   1874 t::Utility::stash('ORDER') // skip_all('No order object in stash');
350 0         0 isa_ok( t::Utility::stash('ORDER')->updated_at, 'Time::Moment' );
351             }
352              
353             =head2 C
354              
355             $order->reload();
356              
357             Reloads the data for this order from the API server.
358              
359             Use this if you think the status or some other info might have changed.
360              
361             =cut
362              
363 0     0 1 0 sub reload($s) {
  0         0  
  0         0  
364 0         0 my $res = $s->_rh->_get( $s->{url} );
365             $_[0]
366             = $res->is_success
367 0 0       0 ? Finance::Robinhood::Equity::Order->new( _rh => $s->_rh, %{ $res->json } )
  0 0       0  
368             : Finance::Robinhood::Error->new(
369             $res->is_server_error ? ( details => $res->message ) : $res->json );
370             }
371              
372             sub _test_reload {
373 1   50 1   1886 t::Utility::stash('ORDER') // skip_all('No order object in stash');
374 0           t::Utility::stash('ORDER')->reload;
375 0           isa_ok( t::Utility::stash('ORDER'), 'Finance::Robinhood::Equity::Order' );
376             }
377              
378             =head1 LEGAL
379              
380             This is a simple wrapper around the API used in the official apps. The author
381             provides no investment, legal, or tax advice and is not responsible for any
382             damages incurred while using this software. This software is not affiliated
383             with Robinhood Financial LLC in any way.
384              
385             For Robinhood's terms and disclosures, please see their website at
386             https://robinhood.com/legal/
387              
388             =head1 LICENSE
389              
390             Copyright (C) Sanko Robinson.
391              
392             This library is free software; you can redistribute it and/or modify it under
393             the terms found in the Artistic License 2. Other copyrights, terms, and
394             conditions may apply to data transmitted through this module. Please refer to
395             the L section.
396              
397             =head1 AUTHOR
398              
399             Sanko Robinson Esanko@cpan.orgE
400              
401             =cut
402              
403             1;