22 unicode_literals )
33import os
44import collections
5+ import time
56from enum import Enum
67import traceback
78
@@ -91,10 +92,17 @@ def _request(self,
9192
9293
9394class Granularity (Enum ):
95+ Ticks = "ticks"
9496 Daily = "day"
9597 Minute = "minute"
9698
9799
100+ class StreamingMethod (Enum ):
101+ AccountUpdate = 'account_update'
102+ Quote = "quote"
103+ MinuteAgg = "minute_agg"
104+
105+
98106class Streamer :
99107 conn = None
100108
@@ -104,7 +112,7 @@ def __init__(
104112 api_key = '' ,
105113 api_secret = '' ,
106114 instrument = '' ,
107- method = '' ,
115+ method : StreamingMethod = StreamingMethod . AccountUpdate ,
108116 base_url = '' ,
109117 data_url = '' ,
110118 data_stream = '' ,
@@ -126,19 +134,23 @@ def __init__(
126134 self .q = q
127135 self .conn .on ('authenticated' )(self .on_auth )
128136 self .conn .on (r'Q.*' )(self .on_quotes )
137+ self .conn .on (r'AM.*' )(self .on_agg_min )
138+ self .conn .on (r'A.*' )(self .on_agg_min )
129139 self .conn .on (r'account_updates' )(self .on_account )
130140 self .conn .on (r'trade_updates' )(self .on_trade )
131141
132142 def run (self ):
133143 channels = []
134- if not self .method :
144+ if self .method == StreamingMethod . AccountUpdate :
135145 channels = ['trade_updates' ] # 'account_updates'
136146 else :
137147 if self .data_stream == 'polygon' :
138- maps = {"quote" : "Q." }
148+ maps = {"quote" : "Q." ,
149+ "minute_agg" : "AM." }
139150 elif self .data_stream == 'alpacadatav1' :
140- maps = {"quote" : "alpacadatav1/Q." }
141- channels = [maps [self .method ] + self .instrument ]
151+ maps = {"quote" : "alpacadatav1/Q." ,
152+ "minute_agg" : "alpacadatav1/AM." }
153+ channels = [maps [self .method .value ] + self .instrument ]
142154
143155 loop = asyncio .new_event_loop ()
144156 asyncio .set_event_loop (loop )
@@ -159,7 +171,8 @@ async def on_agg_sec(self, conn, subject, msg):
159171 self .q .put (msg )
160172
161173 async def on_agg_min (self , conn , subject , msg ):
162- self .q .put (msg )
174+ msg ._raw ['time' ] = msg .end .to_pydatetime ().timestamp ()
175+ self .q .put (msg ._raw )
163176
164177 async def on_account (self , conn , stream , msg ):
165178 self .q .put (msg )
@@ -308,6 +321,8 @@ def get_positions(self):
308321 return positions
309322
310323 def get_granularity (self , timeframe , compression ) -> Granularity :
324+ if timeframe == bt .TimeFrame .Ticks :
325+ return Granularity .Ticks
311326 if timeframe == bt .TimeFrame .Minutes :
312327 return Granularity .Minute
313328 elif timeframe == bt .TimeFrame .Days :
@@ -440,7 +455,7 @@ def _make_sure_dates_are_initialized_properly(self, dtbegin, dtend,
440455 dates may or may not be specified by the user.
441456 when they do, they are probably don't include NY timezome data
442457 also, when granularity is minute, we want to make sure we get data when
443- market is opened. so if it doesn't - let's get set end date to be last
458+ market is opened. so if it doesn't - let's set end date to be last
444459 known minute with opened market.
445460 this nethod takes care of all these issues.
446461 :param dtbegin:
@@ -585,9 +600,12 @@ def _iterate_api_calls():
585600 timeframe = "5Min"
586601 elif granularity == 'minute' and compression == 15 :
587602 timeframe = "15Min"
603+ elif granularity == 'ticks' :
604+ timeframe = "minute"
588605 else :
589606 timeframe = granularity
590607 r = self .oapi .get_barset (dataname ,
608+ 'minute' if timeframe == 'ticks' else
591609 timeframe ,
592610 limit = 1000 ,
593611 end = curr .isoformat ()
@@ -687,22 +705,31 @@ def _resample(df):
687705 response = response [~ response .index .duplicated ()]
688706 return response
689707
690- def streaming_prices (self , dataname , tmout = None ):
708+ def streaming_prices (self , dataname , timeframe , tmout = None ):
691709 q = queue .Queue ()
692- kwargs = {'q' : q , 'dataname' : dataname , 'tmout' : tmout }
710+ kwargs = {'q' : q ,
711+ 'dataname' : dataname ,
712+ 'timeframe' : timeframe ,
713+ 'tmout' : tmout }
693714 t = threading .Thread (target = self ._t_streaming_prices , kwargs = kwargs )
694715 t .daemon = True
695716 t .start ()
696717 return q
697718
698- def _t_streaming_prices (self , dataname , q , tmout ):
719+ def _t_streaming_prices (self , dataname , timeframe , q , tmout ):
699720 if tmout is not None :
700721 _time .sleep (tmout )
722+
723+ if timeframe == bt .TimeFrame .Ticks :
724+ method = StreamingMethod .Quote
725+ elif timeframe == bt .TimeFrame .Minutes :
726+ method = StreamingMethod .MinuteAgg
727+
701728 streamer = Streamer (q ,
702729 api_key = self .p .key_id ,
703730 api_secret = self .p .secret_key ,
704731 instrument = dataname ,
705- method = 'quote' ,
732+ method = method ,
706733 base_url = self .p .base_url ,
707734 data_url = os .environ .get ("DATA_PROXY_WS" , '' ),
708735 data_stream = 'polygon' if self .p .usePolygon else
0 commit comments