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