File Coverage

lib/Finance/Robinhood/Equity/Instrument.pm
Criterion Covered Total %
statement 96 152 63.1
branch 2 28 7.1
condition 9 23 39.1
subroutine 31 39 79.4
pod 12 12 100.0
total 150 254 59.0


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