File Coverage

lib/Finance/Robinhood/Equity/Instrument.pm
Criterion Covered Total %
statement 95 140 67.8
branch 2 20 10.0
condition 8 19 42.1
subroutine 30 37 81.0
pod 11 11 100.0
total 146 227 64.3


line stmt bran cond sub pod time code
1             package Finance::Robinhood::Equity::Instrument;
2              
3             =encoding utf-8
4              
5             =for stopwords watchlist watchlists untradable urls
6              
7             =head1 NAME
8              
9             Finance::Robinhood::Equity::Instrument - Represents a Single Equity Instrument
10              
11             =head1 SYNOPSIS
12              
13             use Finance::Robinhood;
14             my $rh = Finance::Robinhood->new;
15             my $instruments = $rh->instruments();
16              
17             for my $instrument ($instruments->all) {
18             CORE::say $instrument->symbol;
19             }
20              
21             =cut
22              
23             our $VERSION = '0.92_002';
24 1     1   7 use Mojo::Base-base, -signatures;
  1         2  
  1         9  
25 1     1   278 use Mojo::URL;
  1         2  
  1         7  
26 1     1   471 use Finance::Robinhood::Equity::Fundamentals;
  1         3  
  1         11  
27 1     1   496 use Finance::Robinhood::Equity::Quote;
  1         2  
  1         8  
28 1     1   577 use Finance::Robinhood::Equity::OrderBuilder;
  1         3  
  1         9  
29 1     1   565 use Finance::Robinhood::Equity::Market;
  1         3  
  1         6  
30 1     1   475 use Finance::Robinhood::Equity::Ratings;
  1         3  
  1         7  
31 1     1   450 use Finance::Robinhood::Equity::Tag;
  1         3  
  1         8  
32 1     1   36 use Time::Moment;
  1         2  
  1         171  
33              
34             sub _test__init {
35 1     1   12067 my $rh = t::Utility::rh_instance(0);
36 1         17 my $msft = $rh->equity_instrument_by_symbol('MSFT');
37 1         28 isa_ok( $msft, __PACKAGE__ );
38 1         474 t::Utility::stash( 'MSFT', $msft ); # Store it for later
39              
40 1   0     19 t::Utility::rh_instance(1) // skip_all();
41 0         0 $rh = t::Utility::rh_instance(1);
42 0         0 $msft = $rh->equity_instrument_by_symbol('MSFT');
43 0         0 isa_ok( $msft, __PACKAGE__ );
44              
45 0         0 t::Utility::stash( 'MSFT_AUTH', $msft );
46             }
47 1     1   8 use overload '""' => sub ( $s, @ ) { $s->{url} }, fallback => 1;
  1     6   2  
  1         7  
  6         12  
  6         37  
  6         413  
  6         16  
48              
49             sub _test_stringify {
50 1   50 1   2848 t::Utility::stash('MSFT') // skip_all();
51 1         6 is(
52             +t::Utility::stash('MSFT'),
53             'https://api.robinhood.com/instruments/50810c35-d215-4866-9758-0ada4ac79ffa/',
54             );
55             }
56             #
57             has _rh => undef => weak => 1;
58              
59             =head1 METHODS
60              
61             =head2 C
62              
63             https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier
64              
65             =head2 C
66              
67             Country code of location of headquarters.
68              
69             =head2 C
70              
71              
72              
73             =head2 C
74              
75             Instrument id used by RH to refer to this particular instrument.
76              
77             =head2 C
78              
79             Returns a Time::Moment object containing the date the instrument began trading
80             publically.
81              
82             =cut
83              
84 1     1 1 12 sub list_date ($s) {
  1         3  
  1         3  
85 1         49 Time::Moment->from_string( $s->{list_date} . 'T00:00:00.000Z' );
86             }
87              
88             sub _test_list_date {
89 1   50 1   1996 t::Utility::stash('MSFT') // skip_all();
90 1         5 isa_ok( t::Utility::stash('MSFT')->list_date(), 'Time::Moment' );
91             }
92              
93             =head2 C
94              
95             =head2 C
96              
97             =head2 C
98              
99             If applicable, this returns the regulatory defined tick size. See
100             http://www.finra.org/industry/tick-size-pilot-program
101              
102             =head2 C
103              
104             Full name of the instrument.
105              
106             =head2 C
107              
108             Indicates whether the instrument can be traded specifically on Robinhood.
109             Returns C or C.
110              
111             =head2 C
112              
113             Shorter name for the instrument. Best suited for display.
114              
115             =head2 C
116              
117             Indicates whether this instrument is C or C.
118              
119             =head2 C
120              
121             Ticker symbol.
122              
123             =head2 C
124              
125             Indicates whether or not this instrument can be traded in general. Returns
126             C or C.
127              
128             =head2 C
129              
130             Id for the related options chain as a UUID.
131              
132             =head2 C
133              
134             Returns a boolean value.
135              
136             =head2 C
137              
138             Indicates what sort of instrument this is. May one one of these: C,
139             C, C, C, or C.
140              
141             =cut
142              
143             has [
144             'bloomberg_unique', 'country', 'day_trade_ratio', 'id',
145             'maintenance_ratio', 'margin_initial_ratio', 'min_tick_size', 'name',
146             'rhs_tradability', 'simple_name', 'state', 'symbol',
147             'tradability', 'tradable_chain_id', 'tradeable', 'type',
148             'url'
149             ];
150              
151             =head2 C
152              
153             my $quote = $instrument->quote();
154              
155             Builds a Finance::Robinhood::Equity::Quote object with this instrument's quote
156             data.
157              
158             You do not need to be logged in for this to work.
159              
160             =cut
161              
162 0     0 1 0 sub quote ($s) {
  0         0  
  0         0  
163 0         0 my $res = $s->_rh->_get( $s->{quote} );
164             $res->is_success
165 0 0       0 ? Finance::Robinhood::Equity::Quote->new( _rh => $s->_rh, %{ $res->json } )
  0 0       0  
166             : Finance::Robinhood::Error->new(
167             $res->is_server_error ? ( details => $res->message ) : $res->json );
168             }
169              
170             sub _test_quote {
171 1   50 1   2779 t::Utility::stash('MSFT_AUTH') // skip_all();
172 0         0 isa_ok( t::Utility::stash('MSFT_AUTH')->quote(), 'Finance::Robinhood::Equity::Quote' );
173             }
174              
175             =head2 C
176              
177             my @splits = $instrument->splits->all;
178              
179             Returns an iterator with Finance::Robinhood::Equity::Split objects.
180              
181             =cut
182              
183 2     2 1 24 sub splits ( $s ) {
  2         9  
  2         5  
184             Finance::Robinhood::Utility::Iterator->new(
185             _rh => $s->_rh,
186 2         12 _next_page => Mojo::URL->new( $s->{splits} ),
187             _class => 'Finance::Robinhood::Equity::Split'
188             );
189             }
190              
191             sub _test_splits {
192 1     1   1828 my $rh = t::Utility::rh_instance(0);
193 1         16 my $splits = $rh->equity_instrument_by_symbol('JNUG')->splits;
194 1         189 isa_ok( $splits, 'Finance::Robinhood::Utility::Iterator' );
195 1         479 isa_ok( $splits->next, 'Finance::Robinhood::Equity::Split' );
196             }
197              
198             =head2 C
199              
200             my $market = $instrument->market();
201              
202             Builds a Finance::Robinhood::Equity::Market object with this instrument's quote
203             data.
204              
205             You do not need to be logged in for this to work.
206              
207             =cut
208              
209 1     1 1 2 sub market ($s) {
  1         3  
  1         2  
210 1         8 my $res = $s->_rh->_get( $s->{market} );
211             $res->is_success
212 1 0       34 ? Finance::Robinhood::Equity::Market->new( _rh => $s->_rh, %{ $res->json } )
  1 50       34  
213             : Finance::Robinhood::Error->new(
214             $res->is_server_error ? ( details => $res->message ) : $res->json );
215             }
216              
217             sub _test_market {
218 1     1   2108 my $rh = t::Utility::rh_instance(0);
219 1         15 my $msft = $rh->equity_instrument_by_symbol('MSFT');
220 1   50     26 $msft // skip_all();
221 1         9 isa_ok( $msft->market(), 'Finance::Robinhood::Equity::Market' );
222             }
223              
224             =head2 C
225              
226             my $fundamentals = $instrument->fundamentals();
227              
228             Builds a Finance::Robinhood::Equity::Fundamentals object with this instrument's
229             data.
230              
231             You do not need to be logged in for this to work.
232              
233             =cut
234              
235 0     0 1 0 sub fundamentals ($s) {
  0         0  
  0         0  
236 0         0 my $res = $s->_rh->_get( $s->{fundamentals} );
237             $res->is_success
238 0 0       0 ? Finance::Robinhood::Equity::Fundamentals->new( _rh => $s->_rh, %{ $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_fundamentals {
244 1   50 1   1859 t::Utility::stash('MSFT_AUTH') // skip_all();
245 0         0 isa_ok(
246             t::Utility::stash('MSFT_AUTH')->fundamentals(),
247             'Finance::Robinhood::Equity::Fundamentals'
248             );
249             }
250              
251             =head2 C
252              
253             my $fundamentals = $instrument->ratings();
254              
255             Builds a Finance::Robinhood::Equity::Ratings object with this instrument's
256             data.
257              
258             =cut
259              
260 0     0 1 0 sub ratings ($s) {
  0         0  
  0         0  
261 0         0 my $res = $s->_rh->_get( 'https://midlands.robinhood.com/ratings/' . $s->id . '/' );
262             $res->is_success
263 0 0       0 ? Finance::Robinhood::Equity::Ratings->new( _rh => $s->_rh, %{ $res->json } )
  0 0       0  
264             : Finance::Robinhood::Error->new(
265             $res->is_server_error ? ( details => $res->message ) : $res->json );
266             }
267              
268             sub _test_ratings {
269 1   50 1   1885 t::Utility::stash('MSFT_AUTH') // skip_all();
270 0         0 isa_ok( t::Utility::stash('MSFT_AUTH')->ratings(), 'Finance::Robinhood::Equity::Ratings' );
271             }
272              
273             =head2 C
274            
275             $instrument = $rh->search('MSFT')->equity_instruments->[0];
276             my $chains = $instrument->options_chains;
277              
278             Returns an iterator containing chain elements.
279              
280             =cut
281              
282 1     1 1 19 sub options_chains ($s) {
  1         3  
  1         2  
283 1         6 $s->_rh->options_chains($s);
284             }
285              
286             sub _test_options_chains {
287 1     1   2783 my $chains = t::Utility::stash('MSFT')->options_chains;
288 1         154 isa_ok( $chains, 'Finance::Robinhood::Utility::Iterator' );
289 1         274 isa_ok( $chains->current, 'Finance::Robinhood::Options::Chain' );
290             }
291              
292             =head2 C
293              
294             my $news = $instrument->news;
295              
296             Returns an iterator containing Finance::Robinhood::News elements.
297              
298             =cut
299              
300 1     1 1 18 sub news ($s) { $s->_rh->news( $s->symbol ) }
  1         4  
  1         1  
  1         5  
301              
302             sub _test_news {
303 1     1   3684 my $news = t::Utility::stash('MSFT')->news;
304 1         289 isa_ok( $news, 'Finance::Robinhood::Utility::Iterator' );
305 1         249 isa_ok( $news->current, 'Finance::Robinhood::News' );
306             }
307              
308             =head2 C
309              
310             my $tags = $instrument->tags( );
311              
312             Locates an instrument's tags and returns a list of
313             Finance::Robinhood::Equity::Tag objects.
314              
315             =cut
316              
317 1     1 1 19 sub tags ( $s ) {
  1         4  
  1         2  
318 1         9 my $res = $s->_rh->_get( 'https://midlands.robinhood.com/tags/instrument/' . $s->id . '/' );
319             return $res->is_success
320 15         132100 ? map { Finance::Robinhood::Equity::Tag->new( _rh => $s, %{$_} ) }
  15         103  
321 1 0       372 @{ $res->json->{tags} }
  1 50       25  
322             : Finance::Robinhood::Error->new(
323             $res->is_server_error ? ( details => $res->message ) : $res->json );
324             }
325              
326             sub _test_tags {
327 1     1   10273 my @tags = t::Utility::stash('MSFT')->tags;
328 1         36 ok(@tags);
329 1         19 isa_ok(
330             $tags[0],
331             'Finance::Robinhood::Equity::Tag'
332             );
333             }
334              
335             =head2 C
336            
337             my $order = $instrument->buy(34);
338              
339             Returns a Finance::Robinhood::Equity::OrderBuilder object.
340              
341             Without any additional method calls, this will create an order that looks like
342             this:
343              
344             {
345             account => "https://api.robinhood.com/accounts/XXXXXXXXXX/",
346             instrument => "https://api.robinhood.com/instruments/50810c35-d215-4866-9758-0ada4ac79ffa/",
347             price => "111.700000", # Automatically grabs last trade price quote on submission
348             quantity => 4, # Actually the number of shares you requested
349             side => "buy",
350             symbol => "MSFT",
351             time_in_force => "gfd",
352             trigger => "immediate",
353             type => "market"
354             }
355              
356             =cut
357              
358 0     0 1 0 sub buy ( $s, $quantity, $account = $s->_rh->equity_accounts->next ) {
  0         0  
  0         0  
  0         0  
  0         0  
359 0         0 Finance::Robinhood::Equity::OrderBuilder->new(
360             _rh => $s->_rh,
361             _instrument => $s,
362             _account => $account,
363             quantity => $quantity
364             )->buy;
365             }
366              
367             sub _test_buy {
368 1   50 1   2534 t::Utility::stash('MSFT_AUTH') // skip_all();
369             #
370 0         0 my $market = t::Utility::stash('MSFT_AUTH')->buy(4);
371 0         0 is(
372             { $market->_dump(1) },
373             {
374             account => '--private--',
375             instrument =>
376             'https://api.robinhood.com/instruments/50810c35-d215-4866-9758-0ada4ac79ffa/',
377             quantity => 4,
378             side => 'buy',
379             trigger => 'immediate',
380             type => 'market',
381             time_in_force => 'gfd',
382             ref_id => '00000000-0000-0000-0000-000000000000',
383             symbol => 'MSFT',
384             price => '5.00'
385             }
386             );
387              
388             #->stop(43)->limit(55);#->submit;
389             #ddx \{$order->_dump};
390 0     0   0 todo( "Write actual tests!" => sub { pass('ugh') } );
  0         0  
391              
392             #my $news = t::Utility::stash('MSFT')->news;
393             #isa_ok( $news, 'Finance::Robinhood::Utility::Iterator' );
394             #isa_ok( $news->current, 'Finance::Robinhood::News' );
395             }
396              
397             =head2 C
398            
399             my $order = $instrument->sell(34);
400              
401             Returns a Finance::Robinhood::Equity::OrderBuilder object.
402              
403             Without any additional method calls, this will create an order that looks like
404             this:
405              
406             {
407             account => "https://api.robinhood.com/accounts/XXXXXXXXXX/",
408             instrument => "https://api.robinhood.com/instruments/50810c35-d215-4866-9758-0ada4ac79ffa/",
409             price => "111.700000", # Automatically grabs last trade price quote on submission
410             quantity => 4, # Actually the number of shares you requested
411             side => "sell",
412             symbol => "MSFT",
413             time_in_force => "gfd",
414             trigger => "immediate",
415             type => "market"
416             }
417              
418             =cut
419              
420 0     0 1 0 sub sell ( $s, $quantity, $account = $s->_rh->equity_accounts->next ) {
  0         0  
  0         0  
  0         0  
  0         0  
421 0         0 Finance::Robinhood::Equity::OrderBuilder->new(
422             _rh => $s->_rh,
423             _instrument => $s,
424             _account => $account,
425             quantity => $quantity
426             )->sell;
427             }
428              
429             sub _test_sell {
430 1   50 1   1901 t::Utility::stash('MSFT_AUTH') // skip_all();
431             #
432 0           my $market = t::Utility::stash('MSFT_AUTH')->sell(4);
433 0           is(
434             { $market->_dump(1) },
435             {
436             account => '--private--',
437             instrument =>
438             'https://api.robinhood.com/instruments/50810c35-d215-4866-9758-0ada4ac79ffa/',
439             quantity => 4,
440             side => 'sell',
441             trigger => 'immediate',
442             type => 'market',
443             time_in_force => 'gfd',
444             ref_id => '00000000-0000-0000-0000-000000000000',
445             symbol => 'MSFT',
446             price => '5.00'
447             }
448             );
449              
450             #->stop(43)->limit(55);#->submit;
451             #ddx \{$order->_dump};
452 0     0     todo( "Write actual tests!" => sub { pass('ugh') } );
  0            
453              
454             #my $news = t::Utility::stash('MSFT')->news;
455             #isa_ok( $news, 'Finance::Robinhood::Utility::Iterator' );
456             #isa_ok( $news->current, 'Finance::Robinhood::News' );
457             }
458              
459             =head1 LEGAL
460              
461             This is a simple wrapper around the API used in the official apps. The author
462             provides no investment, legal, or tax advice and is not responsible for any
463             damages incurred while using this software. This software is not affiliated
464             with Robinhood Financial LLC in any way.
465              
466             For Robinhood's terms and disclosures, please see their website at
467             https://robinhood.com/legal/
468              
469             =head1 LICENSE
470              
471             Copyright (C) Sanko Robinson.
472              
473             This library is free software; you can redistribute it and/or modify it under
474             the terms found in the Artistic License 2. Other copyrights, terms, and
475             conditions may apply to data transmitted through this module. Please refer to
476             the L section.
477              
478             =head1 AUTHOR
479              
480             Sanko Robinson Esanko@cpan.orgE
481              
482             =cut
483              
484             1;