File Coverage

lib/Finance/Robinhood/Forex/Order.pm
Criterion Covered Total %
statement 25 122 20.4
branch 0 26 0.0
condition 7 34 20.5
subroutine 15 26 57.6
pod 9 9 100.0
total 56 217 25.8


line stmt bran cond sub pod time code
1             package Finance::Robinhood::Forex::Order;
2              
3             =encoding utf-8
4              
5             =for stopwords watchlist watchlists untradable urls
6              
7             =head1 NAME
8              
9             Finance::Robinhood::Forex::Order - Represents a Single Forex Order
10              
11             =head1 SYNOPSIS
12              
13             use Finance::Robinhood;
14             my $rh = Finance::Robinhood->new;
15              
16             # TODO
17              
18             =cut
19              
20             our $VERSION = '0.92_003';
21 1     1   8 use Mojo::Base-base, -signatures;
  1         4  
  1         7  
22 1     1   220 use Mojo::URL;
  1         2  
  1         8  
23 1     1   483 use Finance::Robinhood::Forex::Account;
  1         4  
  1         8  
24 1     1   517 use Finance::Robinhood::Forex::Order::Execution;
  1         3  
  1         6  
25              
26             sub _test__init {
27 1     1   12702 my $rh = t::Utility::rh_instance(1);
28 0         0 my $order = $rh->forex_orders->current;
29 0         0 isa_ok($order, __PACKAGE__);
30 0         0 t::Utility::stash('ORDER', $order); # Store it for later
31             }
32 1     1   138 use overload '""' => sub ($s, @) { $s->{id} }, fallback => 1;
  1     0   2  
  1         8  
  0         0  
  0         0  
  0         0  
  0         0  
33              
34             sub _test_stringify {
35 1   50 1   1894 t::Utility::stash('ORDER') // skip_all();
36 0         0 like(+t::Utility::stash('ORDER'),
37             qr'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'i
38             );
39             }
40             #
41             has _rh => undef => weak => 1;
42              
43             =head1 METHODS
44              
45             =head2 C
46              
47             Returns the amount of currency bought or sold.
48              
49             =head2 C
50              
51             Returns a UUIS.
52              
53             =head2 C
54              
55             Returns a UUID.
56              
57             =head2 C
58              
59             Returns a dollar amount.
60              
61             =head2 C
62              
63             Returns the amount of currency in the order.
64              
65             =head2 C
66              
67             Returns a UUID.
68              
69             =head2 C
70              
71             Returns C or C.
72              
73             =head2 C
74              
75             Returns C, C, C, C, or C.
76              
77             =head2 C
78              
79             =cut
80              
81             has ['cumulative_quantity', 'pair_id', 'id', 'price',
82             'quantity', 'ref_id', 'side', 'state',
83             'time_in_force', 'type'
84             ];
85              
86             =head2 C
87              
88             Returns true if the order can be cancelled.
89              
90             =cut
91              
92 0 0   0 1 0 sub can_cancel ($s) { defined $s->{cancel_url} ? !0 : !1 }
  0         0  
  0         0  
  0         0  
93              
94             sub _test_can_cancel {
95 1     1   1858 my $rh = t::Utility::rh_instance(1);
96 0         0 my $orders = $rh->forex_orders;
97 0         0 my ($filled, $cancelled);
98 0         0 while ($orders->has_next) {
99 0         0 my $order = $orders->next;
100              
101             #$filled = $order if $order->state eq 'queued';
102 0 0       0 $cancelled = $order if $order->state eq 'canceled';
103 0 0 0     0 last if $filled && $cancelled;
104             }
105             SKIP: {
106 0         0 skip('I need to create a new forex order here', 1);
  0         0  
107 0   0     0 $filled // skip('Cannot find a filled forex order', 1);
108 0         0 my $execution = $filled->executions->[0];
109 0         0 isa_ok($execution, 'Finance::Robinhood::Forex::Order::Execution');
110             }
111             SKIP: {
112 0   0     0 $cancelled // skip('Cannot find a cancelled forex order', 1);
  0         0  
113 0         0 is($cancelled->can_cancel, !1, 'cancelled order cannot be cancelled');
114             }
115             todo(
116             "Place an order and test if if can be cancelled then cancel it, reload, and retest it"
117 0     0   0 => sub { pass('ugh') });
  0         0  
118             }
119              
120             =head2 C
121              
122             Returns a Time::Moment object.
123              
124             =cut
125              
126 0     0 1 0 sub created_at ($s) {
  0         0  
  0         0  
127 0         0 Time::Moment->from_string($s->{created_at});
128             }
129              
130             sub _test_created_at {
131 1   50 1   1967 t::Utility::stash('ORDER') // skip_all();
132 0         0 isa_ok(t::Utility::stash('ORDER')->created_at, 'Time::Moment');
133             }
134              
135             =head2 C
136              
137             Returns a Time::Moment object.
138              
139             =cut
140              
141 0     0 1 0 sub last_transaction_at ($s) {
  0         0  
  0         0  
142 0         0 Time::Moment->from_string($s->{last_transaction_at});
143             }
144              
145             sub _test_last_transaction_at {
146 1   50 1   1992 t::Utility::stash('ORDER') // skip_all();
147 0         0 isa_ok(t::Utility::stash('ORDER')->last_transaction_at, 'Time::Moment');
148             }
149              
150             =head2 C
151              
152             Returns a Time::Moment object.
153              
154             =cut
155              
156 0     0 1 0 sub updated_at ($s) {
  0         0  
  0         0  
157 0         0 Time::Moment->from_string($s->{updated_at});
158             }
159              
160             sub _test_updated_at {
161 1   50 1   1856 t::Utility::stash('ORDER') // skip_all();
162 0         0 isa_ok(t::Utility::stash('ORDER')->updated_at, 'Time::Moment');
163             }
164              
165             =head2 C
166              
167             Returns the related Finance::Robinhood::Forex::Account object.
168              
169             =cut
170              
171 0     0 1 0 sub account ($s) {
  0         0  
  0         0  
172             my $res = $s->_rh->_get(
173 0         0 'https://nummus.robinhood.com/accounts/' . $s->{account_id} . '/');
174             $res->is_success
175             ? Finance::Robinhood::Forex::Account->new(_rh => $s->_rh,
176 0 0       0 %{$res->json})
  0 0       0  
177             : Finance::Robinhood::Error->new(
178             $res->is_server_error ? (details => $res->message) : $res->json);
179             }
180              
181             sub _test_account {
182 1   50 1   2111 t::Utility::stash('ORDER') // skip_all('No order object in stash');
183 0         0 isa_ok(t::Utility::stash('ORDER')->account,
184             'Finance::Robinhood::Forex::Account');
185             }
186              
187             =head2 C
188              
189             Returns the related Finance::Robinhood::Forex::Pair object.
190              
191             =cut
192              
193 0     0 1 0 sub pair ($s) {
  0         0  
  0         0  
194 0         0 $s->_rh->forex_pair_by_id($s->{currency_pair_id});
195             }
196              
197             sub _test_pair {
198 1   50 1   1849 t::Utility::stash('ORDER') // skip_all('No order object in stash');
199 0         0 isa_ok(t::Utility::stash('ORDER')->pair,
200             'Finance::Robinhood::Forex::Pair');
201             }
202              
203             =head2 C
204              
205             Returns a list of related Finance::Robinhood::Forex::Order::Execution objects
206             if applicable.
207              
208             =cut
209              
210 0     0 1 0 sub executions ($s) {
  0         0  
  0         0  
211             map {
212             Finance::Robinhood::Forex::Order::Execution->new(_rh => $s->_rh,
213 0         0 %{$_})
  0         0  
214 0         0 } @{$s->{executions}};
  0         0  
215             }
216              
217             sub _test_executions {
218 1     1   1845 my $rh = t::Utility::rh_instance(1);
219 0         0 my $orders = $rh->forex_orders;
220 0         0 my ($filled, $rejected);
221 0         0 while ($orders->has_next) {
222 0         0 my $order = $orders->next;
223 0 0       0 $filled = $order if $order->state eq 'filled';
224 0 0       0 $rejected = $order if $order->state eq 'rejected';
225 0 0 0     0 last if $filled && $rejected;
226             }
227             SKIP: {
228 0   0     0 $filled // skip('Cannot find a filled forex order', 1);
  0         0  
229 0         0 my ($execution) = $filled->executions;
230 0         0 isa_ok($execution, 'Finance::Robinhood::Forex::Order::Execution');
231             }
232             SKIP: {
233 0   0     0 $rejected // skip('Cannot find a rejected forex order', 1);
  0         0  
234 0         0 is($rejected->executions, [], 'rejected order has no executions');
235             }
236             }
237              
238             =head2 C
239              
240             $order->cancel();
241              
242             If the order can be cancelled, this method will do it.
243              
244             Be aware that the order is still active for about a second after this is called
245             so I'm adding a 'smart' delay here.
246              
247             =cut
248              
249 0     0 1 0 sub cancel ($s) {
  0         0  
  0         0  
250 0         0 CORE::state $delay = .15;
251 0   0     0 $s->can_cancel // return !1;
252 0         0 my $res;
253 0         0 for my $tries (1 .. 10) {
254 0         0 $res = $s->_rh->_post($s->{cancel_url});
255 0 0       0 $s->reload if $res->is_success;
256 0 0       0 return $s if !$s->can_cancel;
257 0         0 require Time::HiRes;
258 0         0 Time::HiRes::sleep($delay + ($tries * .15));
259             }
260             Finance::Robinhood::Error->new(
261 0 0       0 $res->is_server_error ? (details => $res->message) : $res->json);
262             }
263              
264             =head2 C
265              
266             $order->reload();
267              
268             Reloads the data for this order from the API server.
269              
270             Use this if you think the status or some other info might have changed.
271              
272             =cut
273              
274 0     0 1 0 sub reload($s) {
  0         0  
  0         0  
275             my $res = $s->_rh->_get(
276 0         0 'https://nummus.robinhood.com/orders/' . $s->{id} . '/');
277             $_[0]
278             = $res->is_success
279 0 0       0 ? Finance::Robinhood::Forex::Order->new(_rh => $s->_rh, %{$res->json})
  0 0       0  
280             : Finance::Robinhood::Error->new(
281             $res->is_server_error ? (details => $res->message) : $res->json);
282             }
283              
284             sub _test_reload {
285 1   50 1   1865 t::Utility::stash('ORDER') // skip_all('No order object in stash');
286 0           t::Utility::stash('ORDER')->reload;
287 0           isa_ok(t::Utility::stash('ORDER'), 'Finance::Robinhood::Forex::Order');
288             }
289              
290             =head1 LEGAL
291              
292             This is a simple wrapper around the API used in the official apps. The author
293             provides no investment, legal, or tax advice and is not responsible for any
294             damages incurred while using this software. This software is not affiliated
295             with Robinhood Financial LLC in any way.
296              
297             For Robinhood's terms and disclosures, please see their website at
298             https://robinhood.com/legal/
299              
300             =head1 LICENSE
301              
302             Copyright (C) Sanko Robinson.
303              
304             This library is free software; you can redistribute it and/or modify it under
305             the terms found in the Artistic License 2. Other copyrights, terms, and
306             conditions may apply to data transmitted through this module. Please refer to
307             the L section.
308              
309             =head1 AUTHOR
310              
311             Sanko Robinson Esanko@cpan.orgE
312              
313             =cut
314              
315             1;