def __init__( self, symbol: Symbol, bar_spec: BarSpecification, fast_ema: int = 10, slow_ema: int = 20, extra_id_tag: str = '', ): """ Initialize a new instance of the EMACross class. Parameters ---------- symbol : Symbol The symbol for the strategy. bar_spec : BarSpecification The bar specification for the strategy. fast_ema : int The fast EMA period. slow_ema : int The slow EMA period. extra_id_tag : str, optional An additional order identifier tag. """ super().__init__(order_id_tag=symbol.code.replace('/', '') + extra_id_tag) # Custom strategy variables self.symbol = symbol self.bar_type = BarType(symbol, bar_spec) # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema) self.slow_ema = ExponentialMovingAverage(slow_ema)
def __init__( self, symbol: Symbol, bar_spec: BarSpecification, trade_size: Decimal, fast_ema_period: int, slow_ema_period: int, atr_period: int, trail_atr_multiple: float, order_id_tag: str, # Must be unique at 'trader level' ): """ Initialize a new instance of the `EMACrossStopEntryWithTrailingStop` class. Parameters ---------- symbol : Symbol The symbol for the strategy. bar_spec : BarSpecification The bar specification for the strategy. trade_size : Decimal The position size per trade. fast_ema_period : int The period for the fast EMA indicator. slow_ema_period : int The period for the slow EMA indicator. atr_period : int The period for the ATR indicator. trail_atr_multiple : float The ATR multiple for the trailing stop. order_id_tag : str The unique order identifier tag for the strategy. Must be unique amongst all running strategies for a particular trader identifier. """ super().__init__(order_id_tag=order_id_tag) # Custom strategy variables self.symbol = symbol self.bar_type = BarType(symbol, bar_spec) self.trade_size = trade_size self.trail_atr_multiple = trail_atr_multiple self.instrument = None # Initialize in on_start self.tick_size = None # Initialize in on_start self.price_precision = None # Initialize in on_start # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema_period) self.slow_ema = ExponentialMovingAverage(slow_ema_period) self.atr = AverageTrueRange(atr_period) # Users order management variables self.entry = None self.trailing_stop = None
def test_handle_bar_updates_indicator(self): # Arrange indicator = ExponentialMovingAverage(10) bar = TestStubs.bar_5decimal() # Act indicator.handle_bar(bar) # Assert self.assertTrue(indicator.has_inputs) self.assertEqual(1.00003, indicator.value)
def test_handle_trade_tick_updates_indicator(self): # Arrange indicator = ExponentialMovingAverage(10) tick = TestStubs.trade_tick_5decimal(AUDUSD_SIM.symbol) # Act indicator.handle_trade_tick(tick) # Assert self.assertTrue(indicator.has_inputs) self.assertEqual(1.00001, indicator.value)
def test_handle_quote_tick_updates_indicator(self): # Arrange indicator = ExponentialMovingAverage(10, PriceType.MID) tick = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act indicator.handle_quote_tick(tick) # Assert self.assertTrue(indicator.has_inputs) self.assertEqual(1.00002, indicator.value)
def __init__(self, bar_type: BarType): super().__init__() self.object_storer = ObjectStorer() self.bar_type = bar_type self.ema1 = ExponentialMovingAverage(10) self.ema2 = ExponentialMovingAverage(20) self.position_id: Optional[PositionId] = None self.calls: List[str] = []
def test_handle_bar_updates_indicator(self): # Arrange indicator = ExponentialMovingAverage(10) bar = TestDataStubs.bar_5decimal() # Act indicator.handle_bar(bar) # Assert assert indicator.has_inputs assert indicator.value == 1.00003
def test_handle_trade_tick_updates_indicator(self): # Arrange indicator = ExponentialMovingAverage(10) tick = TestDataStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act indicator.handle_trade_tick(tick) # Assert assert indicator.has_inputs assert indicator.value == 1.00001
def __init__(self, config: EMACrossConfig): super().__init__(config) # Configuration self.instrument_id = InstrumentId.from_str(config.instrument_id) self.bar_type = BarType.from_str(config.bar_type) self.trade_size = Decimal(config.trade_size) # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(config.fast_ema_period) self.slow_ema = ExponentialMovingAverage(config.slow_ema_period) self.instrument: Optional[Instrument] = None # Initialized in on_start
def __init__( self, symbol: Symbol, bar_spec: BarSpecification, trade_size: Decimal, fast_ema_period: int=10, slow_ema_period: int=20, atr_period: int=20, trail_atr_multiple: float=2.0, ): """ Initialize a new instance of the `EMACrossWithTrailingStop` class. Parameters ---------- symbol : Symbol The symbol for the strategy. bar_spec : BarSpecification The bar specification for the strategy. trade_size : Decimal The position size per trade. fast_ema_period : int The period for the fast EMA indicator. slow_ema_period : int The period for the slow EMA indicator. atr_period : int The period for the ATR indicator. trail_atr_multiple : float The ATR multiple for the trailing stop. """ # The order_id_tag should be unique at the 'trader level', here we are # just using the traded instruments symbol as the strategy order id tag. super().__init__(order_id_tag=symbol.code.replace('/', "")) # Custom strategy variables self.symbol = symbol self.bar_type = BarType(symbol, bar_spec) self.trade_size = trade_size self.trail_atr_multiple = trail_atr_multiple self.instrument = None # Initialize in on_start self.tick_size = None # Initialize in on_start # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema_period) self.slow_ema = ExponentialMovingAverage(slow_ema_period) self.atr = AverageTrueRange(atr_period) # Users order management variables self.entry = None self.trailing_stop = None
def __init__(self, bar_type, id_tag_strategy="001"): """Initialize a new instance of the TestStrategy1 class.""" super().__init__(order_id_tag=id_tag_strategy) self.object_storer = ObjectStorer() self.bar_type = bar_type self.ema1 = ExponentialMovingAverage(10) self.ema2 = ExponentialMovingAverage(20) self.register_indicator_for_bars(self.bar_type, self.ema1) self.register_indicator_for_bars(self.bar_type, self.ema2) self.position_id = None
def __init__(self, bar_type, id_tag_strategy='001', clock=TestClock()): """ Initializes a new instance of the TestStrategy1 class. """ super().__init__(order_id_tag=id_tag_strategy, clock=clock) self.object_storer = ObjectStorer() self.bar_type = bar_type self.ema1 = ExponentialMovingAverage(10) self.ema2 = ExponentialMovingAverage(20) self.register_indicator(self.bar_type, self.ema1, self.ema1.update) self.register_indicator(self.bar_type, self.ema2, self.ema2.update) self.position_id = None
def __init__( self, instrument_id: InstrumentId, bar_spec: BarSpecification, trade_size: Decimal, fast_ema: int = 10, slow_ema: int = 20, extra_id_tag: str = "", ): """ Initialize a new instance of the ``EMACross`` class. Parameters ---------- instrument_id : InstrumentId The instrument identifier for the strategy. bar_spec : BarSpecification The bar specification for the strategy. trade_size : Decimal The position size per trade. fast_ema : int The fast EMA period. slow_ema : int The slow EMA period. extra_id_tag : str An additional order identifier tag. """ if extra_id_tag is None: extra_id_tag = "" super().__init__( order_id_tag=instrument_id.symbol.value.replace("/", "") + extra_id_tag) # Custom strategy variables self.instrument_id = instrument_id self.instrument = None self.bar_type = BarType(instrument_id, bar_spec) self.trade_size = trade_size # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema) self.slow_ema = ExponentialMovingAverage(slow_ema)
def __init__(self, config: EMACrossWithTrailingStopConfig): super().__init__(config) # Configuration self.instrument_id = InstrumentId.from_str(config.instrument_id) self.bar_type = BarType.from_str(config.bar_type) self.trade_size = Decimal(config.trade_size) self.trail_atr_multiple = config.trail_atr_multiple # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(config.fast_ema_period) self.slow_ema = ExponentialMovingAverage(config.slow_ema_period) self.atr = AverageTrueRange(config.atr_period) self.instrument: Optional[Instrument] = None # Initialized in on_start self.tick_size = None # Initialized in on_start # Users order management variables self.entry = None self.trailing_stop = None
def __init__(self, bar_type: BarType): """ Initialize a new instance of the `MockStrategy` class. Parameters ---------- bar_type : BarType The bar type for the strategy. """ super().__init__(order_id_tag="001") self.object_storer = ObjectStorer() self.bar_type = bar_type self.ema1 = ExponentialMovingAverage(10) self.ema2 = ExponentialMovingAverage(20) self.position_id = None self.calls = []
def __init__( self, instrument_id: InstrumentId, bar_spec: BarSpecification, trade_size: Decimal, fast_ema_period: int, slow_ema_period: int, order_id_tag: str, # Must be unique at 'trader level' ): """ Initialize a new instance of the ``EMACross`` class. Parameters ---------- instrument_id : InstrumentId The instrument identifier for the strategy. bar_spec : BarSpecification The bar specification for the strategy. trade_size : Decimal The position size per trade. fast_ema_period : int The period for the fast EMA. slow_ema_period : int The period for the slow EMA. order_id_tag : str The unique order identifier tag for the strategy. Must be unique amongst all running strategies for a particular trader identifier. """ super().__init__(order_id_tag=order_id_tag) # Custom strategy variables self.instrument_id = instrument_id self.instrument = None # Initialize in on_start self.bar_type = BarType(instrument_id, bar_spec) self.trade_size = trade_size # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema_period) self.slow_ema = ExponentialMovingAverage(slow_ema_period)
def __init__(self, symbol: Symbol, bar_spec: BarSpecification, risk_bp: float = 10.0, fast_ema: int = 10, slow_ema: int = 20, atr_period: int = 20, sl_atr_multiple: float = 2.0, extra_id_tag: str = ''): """ Initializes a new instance of the EMACrossPy class. :param symbol: The symbol for the strategy. :param bar_spec: The bar specification for the strategy. :param risk_bp: The risk per trade (basis points). :param fast_ema: The fast EMA period. :param slow_ema: The slow EMA period. :param atr_period: The ATR period. :param sl_atr_multiple: The ATR multiple for stop-loss prices. :param extra_id_tag: An optional extra tag to append to order ids. """ super().__init__(order_id_tag=symbol.code + extra_id_tag, bar_capacity=40) # Custom strategy variables self.symbol = symbol self.bar_type = BarType(symbol, bar_spec) self.precision = 5 # dummy initial value for FX self.risk_bp = risk_bp self.entry_buffer = 0.0 # instrument.tick_size self.SL_buffer = 0.0 # instrument.tick_size * 10 self.SL_atr_multiple = sl_atr_multiple self.position_sizer = None # initialized in on_start() self.quote_currency = None # initialized in on_start() # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema) self.slow_ema = ExponentialMovingAverage(slow_ema) self.atr = AverageTrueRange(atr_period)
def test_register_indicator_for_trade_ticks_when_already_registered(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema1 = ExponentialMovingAverage(10) ema2 = ExponentialMovingAverage(10) # Act strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema1) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema2) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema2) assert len(strategy.registered_indicators) == 2 assert ema1 in strategy.registered_indicators assert ema2 in strategy.registered_indicators
def __init__( self, symbol: Symbol, bar_spec: BarSpecification, trade_size: Decimal, fast_ema: int = 10, slow_ema: int = 20, ): """ Initialize a new instance of the `EMACross` class. Parameters ---------- symbol : Symbol The symbol for the strategy. bar_spec : BarSpecification The bar specification for the strategy. trade_size : Decimal The position size per trade. fast_ema : int The fast EMA period. slow_ema : int The slow EMA period. """ # The order_id_tag should be unique at the 'trader level', here we are # just using the traded instruments symbol as the strategy order id tag. super().__init__(order_id_tag=symbol.code.replace('/', "")) # Custom strategy variables self.symbol = symbol self.bar_type = BarType(symbol, bar_spec) self.trade_size = trade_size # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema) self.slow_ema = ExponentialMovingAverage(slow_ema)
def test_register_indicator_for_bars_when_already_registered(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema1 = ExponentialMovingAverage(10) ema2 = ExponentialMovingAverage(10) bar_type = TestStubs.bartype_audusd_1min_bid() # Act strategy.register_indicator_for_bars(bar_type, ema1) strategy.register_indicator_for_bars(bar_type, ema2) strategy.register_indicator_for_bars(bar_type, ema2) assert len(strategy.registered_indicators) == 2 assert ema1 in strategy.registered_indicators assert ema2 in strategy.registered_indicators
def test_handle_trade_ticks_with_no_ticks_logs_and_continues(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) # Act strategy.handle_trade_ticks([]) # Assert assert ema.count == 0
def test_handle_bars_with_no_bars_logs_and_continues(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_bars(bar_type, ema) # Act strategy.handle_bars([]) # Assert assert ema.count == 0
def test_handle_bars_updates_indicator_registered_for_bars(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_bars(bar_type, ema) bar = TestStubs.bar_5decimal() # Act strategy.handle_bars([bar]) # Assert assert ema.count == 1
def test_register_indicator_for_multiple_data_sources(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) bar_type = TestStubs.bartype_audusd_1min_bid() # Act strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) strategy.register_indicator_for_quote_ticks(GBPUSD_SIM.id, ema) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) strategy.register_indicator_for_bars(bar_type, ema) assert len(strategy.registered_indicators) == 1 assert ema in strategy.registered_indicators
def test_handle_trade_ticks_updates_indicator_registered_for_trade_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_trade_ticks([tick]) # Assert assert ema.count == 1
class EMACrossWithTrailingStop(TradingStrategy): """ A simple moving average cross example strategy with a stop-market entry and trailing stop. When the fast EMA crosses the slow EMA then submits a stop-market order one tick above the current bar for BUY, or one tick below the current bar for SELL. If the entry order is filled then a trailing stop at a specified ATR distance is submitted and managed. Cancels all orders and flattens all positions on stop. Parameters ---------- config : EMACrossWithTrailingStopConfig The configuration for the instance. """ def __init__(self, config: EMACrossWithTrailingStopConfig): super().__init__(config) # Configuration self.instrument_id = InstrumentId.from_str(config.instrument_id) self.bar_type = BarType.from_str(config.bar_type) self.trade_size = Decimal(config.trade_size) self.trail_atr_multiple = config.trail_atr_multiple # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(config.fast_ema_period) self.slow_ema = ExponentialMovingAverage(config.slow_ema_period) self.atr = AverageTrueRange(config.atr_period) self.instrument: Optional[Instrument] = None # Initialized in on_start self.tick_size = None # Initialized in on_start # Users order management variables self.entry = None self.trailing_stop = None def on_start(self): """Actions to be performed on strategy start.""" self.instrument = self.cache.instrument(self.instrument_id) if self.instrument is None: self.log.error( f"Could not find instrument for {self.instrument_id}") self.stop() return self.tick_size = self.instrument.price_increment # Register the indicators for updating self.register_indicator_for_bars(self.bar_type, self.fast_ema) self.register_indicator_for_bars(self.bar_type, self.slow_ema) self.register_indicator_for_bars(self.bar_type, self.atr) # Get historical data self.request_bars(self.bar_type) # Subscribe to live data self.subscribe_bars(self.bar_type) def on_instrument(self, instrument: Instrument): """ Actions to be performed when the strategy is running and receives an instrument. Parameters ---------- instrument : Instrument The instrument received. """ pass def on_order_book(self, order_book: OrderBook): """ Actions to be performed when the strategy is running and receives an order book. Parameters ---------- order_book : OrderBook The order book received. """ # self.log.info(f"Received {order_book}") # For debugging (must add a subscription) def on_quote_tick(self, tick: QuoteTick): """ Actions to be performed when the strategy is running and receives a quote tick. Parameters ---------- tick : QuoteTick The quote tick received. """ pass def on_trade_tick(self, tick: TradeTick): """ Actions to be performed when the strategy is running and receives a trade tick. Parameters ---------- tick : TradeTick The tick received. """ pass def on_bar(self, bar: Bar): """ Actions to be performed when the strategy is running and receives a bar. Parameters ---------- bar : Bar The bar received. """ self.log.info(f"Received {repr(bar)}") # Check if indicators ready if not self.indicators_initialized(): self.log.info( f"Waiting for indicators to warm up " f"[{self.cache.bar_count(self.bar_type)}]...", color=LogColor.BLUE, ) return # Wait for indicators to warm up... if self.portfolio.is_flat(self.instrument_id): if self.fast_ema.value >= self.slow_ema.value: self.entry_buy() self.trailing_stop_sell(bar) else: # fast_ema.value < self.slow_ema.value self.entry_sell() self.trailing_stop_buy(bar) else: self.manage_trailing_stop(bar) def entry_buy(self): """ Users simple buy entry method (example). """ order = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=self.instrument.make_qty(self.trade_size), ) self.submit_order(order) def entry_sell(self): """ Users simple sell entry method (example). """ order = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.SELL, quantity=self.instrument.make_qty(self.trade_size), ) self.submit_order(order) def trailing_stop_buy(self, last_bar: Bar): """ Users simple trailing stop BUY for (``SHORT`` positions). Parameters ---------- last_bar : Bar The last bar received. """ price: Decimal = last_bar.high + (self.atr.value * self.trail_atr_multiple) order: StopMarketOrder = self.order_factory.stop_market( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=self.instrument.make_qty(self.trade_size), price=self.instrument.make_price(price), reduce_only=True, ) self.trailing_stop = order self.submit_order(order) def trailing_stop_sell(self, last_bar: Bar): """ Users simple trailing stop SELL for (LONG positions). """ price: Decimal = last_bar.low - (self.atr.value * self.trail_atr_multiple) order: StopMarketOrder = self.order_factory.stop_market( instrument_id=self.instrument_id, order_side=OrderSide.SELL, quantity=self.instrument.make_qty(self.trade_size), price=self.instrument.make_price(price), reduce_only=True, ) self.trailing_stop = order self.submit_order(order) def manage_trailing_stop(self, last_bar: Bar): """ Users simple trailing stop management method (example). Parameters ---------- last_bar : Bar The last bar received. """ if not self.trailing_stop: self.log.error("Trailing Stop order was None!") self.flatten_all_positions(self.instrument_id) return if self.trailing_stop.is_sell: new_trailing_price = last_bar.low - (self.atr.value * self.trail_atr_multiple) if new_trailing_price > self.trailing_stop.price: self.cancel_order(self.trailing_stop) self.trailing_stop_sell(last_bar) else: # trailing_stop.is_buy new_trailing_price = last_bar.high + (self.atr.value * self.trail_atr_multiple) if new_trailing_price < self.trailing_stop.price: self.cancel_order(self.trailing_stop) self.trailing_stop_buy(last_bar) def on_data(self, data: Data): """ Actions to be performed when the strategy is running and receives generic data. Parameters ---------- data : Data The data received. """ pass def on_event(self, event: Event): """ Actions to be performed when the strategy is running and receives an event. Parameters ---------- event : Event The event received. """ if isinstance(event, OrderFilled) and self.trailing_stop: if event.client_order_id == self.trailing_stop.client_order_id: last_bar = self.cache.bar(self.bar_type) if event.order_side == OrderSide.BUY: self.trailing_stop_sell(last_bar) elif event.order_side == OrderSide.SELL: self.trailing_stop_buy(last_bar) elif event.client_order_id == self.trailing_stop.client_order_id: self.trailing_stop = None def on_stop(self): """ Actions to be performed when the strategy is stopped. """ self.cancel_all_orders(self.instrument_id) self.flatten_all_positions(self.instrument_id) # Unsubscribe from data self.unsubscribe_bars(self.bar_type) def on_reset(self): """ Actions to be performed when the strategy is reset. """ # Reset indicators here self.fast_ema.reset() self.slow_ema.reset() self.atr.reset() def on_save(self) -> Dict[str, bytes]: """ Actions to be performed when the strategy is saved. Create and return a state dictionary of values to be saved. Returns ------- dict[str, bytes] The strategy state dictionary. """ return {} def on_load(self, state: Dict[str, bytes]): """ Actions to be performed when the strategy is loaded. Saved state values will be contained in the give state dictionary. Parameters ---------- state : dict[str, bytes] The strategy state dictionary. """ pass def on_dispose(self): """ Actions to be performed when the strategy is disposed. Cleanup any resources used by the strategy here. """ pass
class EMACrossBracket(TradingStrategy): """ A simple moving average cross example strategy. When the fast EMA crosses the slow EMA then enter a position in that direction. Cancels all orders and flattens all positions on stop. Parameters ---------- config : EMACrossConfig The configuration for the instance. """ def __init__(self, config: EMACrossBracketConfig): super().__init__(config) # Configuration self.instrument_id = InstrumentId.from_str(config.instrument_id) self.bar_type = BarType.from_str(config.bar_type) self.bracket_distance_atr = config.bracket_distance_atr self.trade_size = Decimal(config.trade_size) # Create the indicators for the strategy self.atr = AverageTrueRange(config.atr_period) self.fast_ema = ExponentialMovingAverage(config.fast_ema_period) self.slow_ema = ExponentialMovingAverage(config.slow_ema_period) self.instrument: Optional[Instrument] = None # Initialized in on_start def on_start(self): """Actions to be performed on strategy start.""" self.instrument = self.cache.instrument(self.instrument_id) if self.instrument is None: self.log.error(f"Could not find instrument for {self.instrument_id}") self.stop() return # Register the indicators for updating self.register_indicator_for_bars(self.bar_type, self.atr) self.register_indicator_for_bars(self.bar_type, self.fast_ema) self.register_indicator_for_bars(self.bar_type, self.slow_ema) # Get historical data self.request_bars(self.bar_type) # Subscribe to live data self.subscribe_bars(self.bar_type) def on_bar(self, bar: Bar): """ Actions to be performed when the strategy is running and receives a bar. Parameters ---------- bar : Bar The bar received. """ self.log.info(f"Received {repr(bar)}") # Check if indicators ready if not self.indicators_initialized(): self.log.info( f"Waiting for indicators to warm up " f"[{self.cache.bar_count(self.bar_type)}]...", color=LogColor.BLUE, ) return # Wait for indicators to warm up... # BUY LOGIC if self.fast_ema.value >= self.slow_ema.value: if self.portfolio.is_flat(self.instrument_id): self.buy(bar) elif self.portfolio.is_net_short(self.instrument_id): self.flatten_all_positions(self.instrument_id) self.cancel_all_orders(self.instrument_id) self.buy(bar) # SELL LOGIC elif self.fast_ema.value < self.slow_ema.value: if self.portfolio.is_flat(self.instrument_id): self.sell(bar) elif self.portfolio.is_net_long(self.instrument_id): self.flatten_all_positions(self.instrument_id) self.cancel_all_orders(self.instrument_id) self.sell(bar) def buy(self, last_bar: Bar): """ Users bracket buy method (example). """ bracket_distance: float = self.bracket_distance_atr * self.atr.value order_list: OrderList = self.order_factory.bracket_market( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=self.instrument.make_qty(self.trade_size), stop_loss=self.instrument.make_price(last_bar.close - bracket_distance), take_profit=self.instrument.make_price(last_bar.close + bracket_distance), ) self.submit_order_list(order_list) def sell(self, last_bar: Bar): """ Users bracket sell method (example). """ bracket_distance: float = self.bracket_distance_atr * self.atr.value order_list: OrderList = self.order_factory.bracket_market( instrument_id=self.instrument_id, order_side=OrderSide.SELL, quantity=self.instrument.make_qty(self.trade_size), stop_loss=self.instrument.make_price(last_bar.close + bracket_distance), take_profit=self.instrument.make_price(last_bar.close - bracket_distance), ) self.submit_order_list(order_list) def on_data(self, data: Data): """ Actions to be performed when the strategy is running and receives generic data. Parameters ---------- data : Data The data received. """ pass def on_event(self, event: Event): """ Actions to be performed when the strategy is running and receives an event. Parameters ---------- event : Event The event received. """ pass def on_stop(self): """ Actions to be performed when the strategy is stopped. """ self.cancel_all_orders(self.instrument_id) self.flatten_all_positions(self.instrument_id) # Unsubscribe from data self.unsubscribe_bars(self.bar_type) def on_reset(self): """ Actions to be performed when the strategy is reset. """ # Reset indicators here self.fast_ema.reset() self.slow_ema.reset() def on_save(self) -> Dict[str, bytes]: """ Actions to be performed when the strategy is saved. Create and return a state dictionary of values to be saved. Returns ------- dict[str, bytes] The strategy state dictionary. """ return {} def on_load(self, state: Dict[str, bytes]): """ Actions to be performed when the strategy is loaded. Saved state values will be contained in the give state dictionary. Parameters ---------- state : dict[str, bytes] The strategy state dictionary. """ pass def on_dispose(self): """ Actions to be performed when the strategy is disposed. Cleanup any resources used by the strategy here. """ pass
def setUp(self): # Arrange self.ema = ExponentialMovingAverage(10)
class ExponentialMovingAverageTests(unittest.TestCase): # Fixture Setup def setUp(self): # Arrange self.ema = ExponentialMovingAverage(10) def test_name_returns_expected_name(self): # Act # Assert self.assertEqual('ExponentialMovingAverage', self.ema.name) def test_str_returns_expected_string(self): # Act # Assert self.assertEqual('ExponentialMovingAverage(10)', str(self.ema)) def test_repr_returns_expected_string(self): # Act # Assert self.assertTrue( repr(self.ema).startswith( '<ExponentialMovingAverage(10) object at')) self.assertTrue(repr(self.ema).endswith('>')) def test_period_returns_expected_value(self): # Act # Assert self.assertEqual(10, self.ema.period) def test_multiplier_returns_expected_value(self): # Act # Assert self.assertEqual(0.18181818181818182, self.ema.alpha) def test_initialized_without_inputs_returns_false(self): # Act # Assert self.assertEqual(False, self.ema.initialized) def test_initialized_with_required_inputs_returns_true(self): # Arrange self.ema.update_raw(1.00000) self.ema.update_raw(2.00000) self.ema.update_raw(3.00000) self.ema.update_raw(4.00000) self.ema.update_raw(5.00000) self.ema.update_raw(6.00000) self.ema.update_raw(7.00000) self.ema.update_raw(8.00000) self.ema.update_raw(9.00000) self.ema.update_raw(10.00000) # Act # Assert self.assertEqual(True, self.ema.initialized) def test_value_with_one_input_returns_expected_value(self): # Arrange self.ema.update_raw(1.00000) # Act # Assert self.assertEqual(1.0, self.ema.value) def test_value_with_three_inputs_returns_expected_value(self): # Arrange self.ema.update_raw(1.00000) self.ema.update_raw(2.00000) self.ema.update_raw(3.00000) # Act # Assert self.assertEqual(1.5123966942148757, self.ema.value) def test_reset_successfully_returns_indicator_to_fresh_state(self): # Arrange for _i in range(1000): self.ema.update_raw(1.00000) # Act self.ema.reset() # Assert self.assertEqual(0.0, self.ema.value) # No assertion errors. def test_with_battery_signal(self): # Arrange battery_signal = BatterySeries.create() output = [] # Act for point in battery_signal: self.ema.update_raw(point) output.append(self.ema.value) # Assert self.assertEqual(len(battery_signal), len(output))
class EMACross(TradingStrategy): """ A simple moving average cross example strategy. When the fast EMA crosses the slow EMA then enter a position in that direction. """ def __init__( self, instrument_id: InstrumentId, bar_spec: BarSpecification, trade_size: Decimal, fast_ema: int = 10, slow_ema: int = 20, extra_id_tag: str = "", ): """ Initialize a new instance of the ``EMACross`` class. Parameters ---------- instrument_id : InstrumentId The instrument identifier for the strategy. bar_spec : BarSpecification The bar specification for the strategy. trade_size : Decimal The position size per trade. fast_ema : int The fast EMA period. slow_ema : int The slow EMA period. extra_id_tag : str An additional order identifier tag. """ if extra_id_tag is None: extra_id_tag = "" super().__init__( order_id_tag=instrument_id.symbol.value.replace("/", "") + extra_id_tag) # Custom strategy variables self.instrument_id = instrument_id self.instrument = None self.bar_type = BarType(instrument_id, bar_spec) self.trade_size = trade_size # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema) self.slow_ema = ExponentialMovingAverage(slow_ema) def on_start(self): """Actions to be performed on strategy start.""" self.instrument = self.cache.instrument(self.instrument_id) if self.instrument is None: self.log.error( f"Could not find instrument for {self.instrument_id}") self.stop() return # Register the indicators for updating self.register_indicator_for_bars(self.bar_type, self.fast_ema) self.register_indicator_for_bars(self.bar_type, self.slow_ema) # Get historical data self.request_bars(self.bar_type) # Subscribe to live data self.subscribe_bars(self.bar_type) def on_trade_tick(self, tick: TradeTick): """ Actions to be performed when the strategy is running and receives a trade tick. Parameters ---------- tick : TradeTick The tick received. """ pass def on_order_book(self, order_book: OrderBook): """ Actions to be performed when the strategy is running and receives an order book. Parameters ---------- order_book : OrderBook The order book received. """ pass def on_quote_tick(self, tick: QuoteTick): """ Actions to be performed when the strategy is running and receives a quote tick. Parameters ---------- tick : QuoteTick The quote tick received. """ pass def on_bar(self, bar: Bar): """ Actions to be performed when the strategy is running and receives a bar. Parameters ---------- bar : Bar The bar received. """ self.log.info(f"Received {repr(bar)}") # Check if indicators ready if not self.indicators_initialized(): self.log.info( f"Waiting for indicators to warm up " f"[{self.cache.bar_count(self.bar_type)}]...", color=LogColor.BLUE, ) return # Wait for indicators to warm up... # BUY LOGIC if self.fast_ema.value >= self.slow_ema.value: if self.portfolio.is_flat(self.instrument_id): self.buy() elif self.portfolio.is_net_short(self.instrument_id): self.flatten_all_positions(self.instrument_id) self.buy() # SELL LOGIC elif self.fast_ema.value < self.slow_ema.value: if self.portfolio.is_flat(self.instrument_id): self.sell() elif self.portfolio.is_net_long(self.instrument_id): self.flatten_all_positions(self.instrument_id) self.sell() def buy(self): """ Users simple buy method (example). """ order = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=self.instrument.make_qty(self.trade_size), ) self.submit_order(order) def sell(self): """ Users simple sell method (example). """ order = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.SELL, quantity=self.instrument.make_qty(self.trade_size), ) self.submit_order(order) def on_data(self, data): """ Actions to be performed when the strategy is running and receives a data object. Parameters ---------- data : object The data object received. """ pass def on_event(self, event): """ Actions to be performed when the strategy is running and receives an event. Parameters ---------- event : Event The event received. """ pass def on_stop(self): """ Actions to be performed when the strategy is stopped. """ self.cancel_all_orders(self.instrument_id) self.flatten_all_positions(self.instrument_id) def on_reset(self): """ Actions to be performed when the strategy is reset. """ # Reset indicators here self.fast_ema.reset() self.slow_ema.reset() def on_save(self) -> {}: """ Actions to be performed when the strategy is saved. Create and return a state dictionary of values to be saved. Returns ------- dict[str, bytes] The strategy state dictionary. """ return {"example": b"123456"} def on_load(self, state: {}): """ Actions to be performed when the strategy is loaded. Saved state values will be contained in the give state dictionary. Parameters ---------- state : dict[str, bytes] The strategy state dictionary. """ self.log.info(f"Loaded users state {state['example']}") def on_dispose(self): """ Actions to be performed when the strategy is disposed. Cleanup any resources used by the strategy here. """ self.unsubscribe_bars(self.bar_type)