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