def run(self, alert_manager=None, publish=False, show_progress=False): if self.mode == MODE.LIVE: self.live_run(alert_manager, publish) # no progress bar for live elif self.mode == MODE.BACKTEST: self.back_test_run(alert_manager, publish, show_progress) else: raise StateMachineError(msg="undefined mode")
def live_run(self, alert_manager=None, publish_packets=False): ''' The entry point for a live run. ''' if self.mode != MODE.LIVE: raise StateMachineError(msg="mode must be live") if not self.context.is_initialized(): raise InitializationError(msg="context is not " "properly initialized") if not isinstance(self.context.clock, RealtimeClock): raise ValidationError(msg="clock must be real-time clock") # reset the clock at the start of the run, this also aligns # ticks to the nearest rounded bar depending on the frequency self._reset_clock(self.context.clock.emit_frequency) # initialize the coroutines clock_coro = self.context.clock.tick() algo_coro = self._run_live(alert_manager, publish_packets) try: tasks = asyncio.gather(clock_coro, algo_coro, return_exceptions=False) self._loop.run_until_complete(tasks) except BaseException as e: # TODO: do a proper exception handling here print("exception {}".format(e)) tasks.cancel() raise e finally: self._loop.close()
def shutdown(self, *args, **kwargs): try: self.fsm_stop() self.log_warning("algorithm stopped.") except MachineError: # this should never fail. msg = f"State Machine Error ({self.state}): attempt to stop." raise StateMachineError(msg=msg)
def pause(self, *args, **kwargs): try: self.fsm_pause() msg = "algorithm paused. No further processing till resumed." self.log_warning(msg) except MachineError: msg = f"State Machine Error ({self.state}): attempt to pause." raise StateMachineError(msg=msg)
def state(self, to_state): if to_state not in self._transition_dict[self._current_state]: strmsg = f"Illegal state change from {self._current_state}\ to {to_state}" raise StateMachineError(msg=strmsg) self._current_state = to_state
def before_trading_start(self, timestamp): ''' Called at the start of the session. ''' try: self.fsm_before_trading_start() except MachineError: msg = f"State Machine Error ({self.state}): in before_trading_start" raise StateMachineError(msg=msg) self._before_trading_start(self.context, self.context.data_portal) # call the handle data state update here. if self.mode != MODE.LIVE: try: self.fsm_handle_data() except MachineError: msg = f"State Machine Error ({self.state}): in handle_data" raise StateMachineError(msg=msg)
def heartbeat(self, timestamp): ''' Called when we are not in a session. ''' try: self.fsm_heartbeat() except MachineError: msg = f"State Machine Error ({self.state}): in heartbeat" raise StateMachineError(msg=msg) self._heartbeat(self.context)
def analyze(self, timestamp): ''' Called at the end of the algo run. ''' try: self.fsm_analyze() except MachineError: msg = f"State Machine Error ({self.state}): in analyze" raise StateMachineError(msg=msg) self._analyze(self.context)
def after_trading_hours(self, timestamp): ''' Called at the end of the session. ''' try: self.fsm_after_trading_hours() except MachineError: msg = f"State Machine Error ({self.state}): in after_trading_hours" raise StateMachineError(msg=msg) self._after_trading_hours(self.context, self.context.data_portal)
def initialize(self, timestamp): ''' Called at the start of the algo. Or at every resume command. ''' try: self.fsm_initialize() except MachineError: msg = f"State Machine Error ({self.state}): in initialize" raise StateMachineError(msg=msg) self._initialize(self.context)
def _back_test_generator(self, alert_manager=None): ''' The entry point for backtest run. This generator yields the current day performance. ''' if self.mode != MODE.BACKTEST: raise StateMachineError(msg="mode must be back-test") if not self.context.is_initialized(): raise InitializationError(msg="context is not " "properly initialized") if not isinstance(self.context.clock, SimulationClock): raise ValidationError(msg="clock must be simulation clock") self._make_broker_dispatch() # only useful for backtest for t, bar in self.context.clock: try: ts = pd.Timestamp(t, unit='ns', tz=self.context.trading_calendar.tz) if bar == BARS.ALGO_START: self.context.set_up(timestamp=ts) self.context.set_timestamp(ts) self._BROKER_FUNC_DISPATCH.get(bar, self._bar_noop)(ts) if bar == BARS.TRADING_BAR: #self.context.BAR_update(ts) pass if bar == BARS.AFTER_TRADING_HOURS: #self.context.BAR_update(ts) self.context.EOD_update(ts) perf = self.context.performance perf['timestamp'] = str(self.context.timestamp) yield perf self._USER_FUNC_DISPATCH.get(bar, self._bar_noop)(ts) except BlueShiftException as e: if not alert_manager: raise e else: timestamp = self.context.timestamp alert_manager.handle_error(e, 'algorithm', mode=self.mode, timestamp=timestamp) continue
def set_broker(self, name, *args, **kwargs): ''' Change the broker in run time. This is useful to modify the broker api (including execution logic if any) or the capital. This will NOT change the clock, and we do not want that either. ''' if not self.is_INITIALIZED() or not self.is_HEARTBEAT(): msg = "cannot set broker in state ({self.state})" raise StateMachineError(msg=msg) broker = get_broker(name, *args, **kwargs) # we cannot switch from backtest to live or reverse if type(self.context.clock) != type(broker.clock): msg = "cannot switch from backtest to live or reverse" raise StateMachineError(msg=msg) # we should not attempt to change the clock! self.context.reset(api=broker.broker, auth=broker.auth, asset_finder=broker.asset_finder, data_portal=broker.data_portal)
def resume(self, *args, **kwargs): ''' Resuming an algo trigger state changes starting with a call to initialize. ''' try: self.fsm_resume() # PAUSED ==> STARTUP # TODO: this is risky, there is a change that the queue # may be None within the tick coroutine in clock possibly? self._reset_clock(self.context.clock.emit_frequency) self.log_warning("algorithm resumed.") except MachineError: msg = f"State Machine Error ({self.state}): attempt to resume." raise StateMachineError(msg=msg)
def handle_data(self, timestamp): ''' Called at the start of each trading bar. We call the state machine function only for live mode here, and club with before trading start for backtest mode. This a bit hacky but speeds things up. ''' if self.mode == MODE.LIVE: try: self.fsm_handle_data() except MachineError: msg = f"State Machine Error ({self.state}): in handle_data" raise StateMachineError(msg=msg) self._handle_data(self.context, self.context.data_portal)