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 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 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
class EMACrossStopEntryTrail(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. """ 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 on_start(self): """Actions to be performed on strategy start.""" self.instrument = self.data.instrument(self.symbol) if self.instrument is None: self.log.error(f"Could not find instrument for {self.symbol}") self.stop() return self.tick_size = self.instrument.tick_size self.price_precision = self.instrument.price_precision # 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_type: BarType, bar: Bar): """ Actions to be performed when the strategy is running and receives a bar. Parameters ---------- bar_type : BarType The bar type received. bar : Bar The bar received. """ self.log.info(f"Received {bar_type} {repr(bar)}") # Check if indicators ready if not self.indicators_initialized(): self.log.info(f"Waiting for indicators to warm up " f"[{self.data.bar_count(self.bar_type)}]...") return # Wait for indicators to warm up... if self.portfolio.is_flat(self.symbol): if self.entry is not None: self.cancel_order(self.entry) # BUY LOGIC if self.fast_ema.value >= self.slow_ema.value: self.entry_buy(bar) else: # fast_ema.value < self.slow_ema.value self.entry_sell(bar) else: self.manage_trailing_stop(bar) def entry_buy(self, last_bar: Bar): """ Users simple buy entry method (example). """ order: StopMarketOrder = self.order_factory.stop_market( symbol=self.symbol, order_side=OrderSide.BUY, quantity=Quantity(self.trade_size), price=Price(last_bar.high + (self.tick_size * 2)), ) self.entry = order self.submit_order(order) def entry_sell(self, last_bar: Bar): """ Users simple sell entry method (example). Parameters ---------- last_bar : Bar The last bar received. """ order: StopMarketOrder = self.order_factory.stop_market( symbol=self.symbol, order_side=OrderSide.SELL, quantity=Quantity(self.trade_size), price=Price(last_bar.low - (self.tick_size * 2)), ) self.entry = order 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. """ # Round price to nearest 0.5 (for XBT/USD) price = round((last_bar.high + (self.atr.value * self.trail_atr_multiple)) * 2) / 2 order: StopMarketOrder = self.order_factory.stop_market( symbol=self.symbol, order_side=OrderSide.BUY, quantity=Quantity(self.trade_size), price=Price(price, self.instrument.price_precision), 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). """ # Round price to nearest 0.5 (for XBT/USD) price = round((last_bar.low - (self.atr.value * self.trail_atr_multiple)) * 2) / 2 order: StopMarketOrder = self.order_factory.stop_market( symbol=self.symbol, order_side=OrderSide.SELL, quantity=Quantity(self.trade_size), price=Price(price, self.instrument.price_precision), 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. """ self.log.info("Managing trailing stop...") if not self.trailing_stop: self.log.error("Trailing Stop order was None!") self.flatten_all_positions(self.symbol) return if self.trailing_stop.is_sell: new_trailing_price = round( (last_bar.low - (self.atr.value * self.trail_atr_multiple)) * 2) / 2 if new_trailing_price > self.trailing_stop.price: self.log.info( f"Moving SELL trailing stop to {new_trailing_price}.") self.cancel_order(self.trailing_stop) self.trailing_stop_sell(last_bar) else: # trailing_stop.is_buy new_trailing_price = round( (last_bar.high + (self.atr.value * self.trail_atr_multiple)) * 2) / 2 if new_trailing_price < self.trailing_stop.price: self.log.info( f"Moving BUY trailing stop to {new_trailing_price}.") self.cancel_order(self.trailing_stop) self.trailing_stop_buy(last_bar) 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. """ if isinstance(event, OrderFilled): if self.entry: if event.cl_ord_id == self.entry.cl_ord_id: last_bar = self.data.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) if self.trailing_stop: if event.cl_ord_id == self.trailing_stop.cl_ord_id: self.trailing_stop = None def on_stop(self): """ Actions to be performed when the strategy is stopped. """ self.cancel_all_orders(self.symbol) self.flatten_all_positions(self.symbol) # 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) -> {}: """ 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: {}): """ 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 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)
class ExponentialMovingAverageTests(unittest.TestCase): def setUp(self): # Fixture Setup self.ema = ExponentialMovingAverage(10) def test_name_returns_expected_string(self): # Arrange # Act # Assert self.assertEqual('ExponentialMovingAverage', self.ema.name) def test_str_repr_returns_expected_string(self): # Arrange # Act # Assert self.assertEqual('ExponentialMovingAverage(10)', str(self.ema)) self.assertEqual('ExponentialMovingAverage(10)', repr(self.ema)) def test_period_returns_expected_value(self): # Arrange # Act # Assert self.assertEqual(10, self.ema.period) def test_multiplier_returns_expected_value(self): # Arrange # Act # Assert self.assertEqual(0.18181818181818182, self.ema.alpha) def test_initialized_without_inputs_returns_false(self): # Arrange # 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_handle_quote_tick_updates_indicator(self): # Arrange indicator = ExponentialMovingAverage(10, PriceType.MID) tick = TestStubs.quote_tick_5decimal(AUDUSD_SIM.symbol) # Act indicator.handle_quote_tick(tick) # Assert self.assertTrue(indicator.has_inputs) self.assertEqual(1.00002, 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_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_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.assertFalse(self.ema.initialized) self.assertEqual(0.0, self.ema.value)
class EMACross(TradingStrategy): """ A simple moving average cross example strategy. When the fast EMA crosses the slow EMA then enter a position at the market in that direction. Cancels all orders and flattens all positions on stop. Parameters ---------- config : EMACrossConfig The configuration for the instance. """ 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 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) # self.request_quote_ticks(self.instrument_id) # self.request_trade_ticks(self.instrument_id) # Subscribe to live data self.subscribe_bars(self.bar_type) self.subscribe_quote_ticks(self.instrument_id) # self.subscribe_trade_ticks(self.instrument_id) # self.subscribe_ticker(self.instrument_id) # For debugging # self.subscribe_order_book_deltas(self.instrument_id, depth=20) # For debugging # self.subscribe_order_book_snapshots(self.instrument_id, depth=20) # For debugging 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_delta(self, data: OrderBookData): """ Actions to be performed when the strategy is running and receives order data. Parameters ---------- data : OrderBookData The order book data received. """ self.log.info(f"Received {repr(data)}" ) # For debugging (must add a subscription) 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 {repr(order_book)}" ) # For debugging (must add a subscription) self.log.info(f"Bid count = {len(order_book.bids.levels)}") self.log.info(f"Ask count = {len(order_book.asks.levels)}") def on_ticker(self, ticker: Ticker): """ Actions to be performed when the strategy is running and receives a ticker. Parameters ---------- ticker : Ticker The ticker received. """ self.log.info(f"Received {repr(ticker)}" ) # 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. """ # self.log.info(f"Received {repr(tick)}") # For debugging (must add a subscription) 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. """ self.log.info(f"Received {repr(tick)}" ) # For debugging (must add a subscription) 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: MarketOrder = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=self.instrument.make_qty(self.trade_size), # time_in_force=TimeInForce.FOK, ) self.submit_order(order) def sell(self): """ Users simple sell method (example). """ order: MarketOrder = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.SELL, quantity=self.instrument.make_qty(self.trade_size), # time_in_force=TimeInForce.FOK, ) self.submit_order(order) 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) self.unsubscribe_quote_ticks(self.instrument_id) # self.unsubscribe_trade_ticks(self.instrument_id) # self.unsubscribe_ticker(self.instrument_id) # self.unsubscribe_order_book_deltas(self.instrument_id) # self.unsubscribe_order_book_snapshots(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) -> 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 EMACross(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. """ 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.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 on_start(self): """Actions to be performed on strategy start.""" # 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) # self.subscribe_order_book(self.instrument_id, level=2, depth=25, interval=5) # For debugging # self.subscribe_quote_ticks(self.instrument_id) # For debugging # self.subscribe_trade_ticks(self.instrument_id) # For debugging 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 {repr(order_book)}") # For debugging (must add a subscription) # self.log.info(str(order_book.asks())) # self.log.info(str(order_book.bids())) 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. """ # self.log.info(f"Received {repr(tick)}") # For debugging (must add a subscription) 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. """ # self.log.info(f"Received {repr(tick)}") # For debugging (must add a subscription) 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.data.bar_count(self.bar_type)}]...") 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: MarketOrder = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=Quantity(self.trade_size), # time_in_force=TimeInForce.FOK, ) self.submit_order(order) def sell(self): """ Users simple sell method (example). """ order: MarketOrder = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.SELL, quantity=Quantity(self.trade_size), # time_in_force=TimeInForce.FOK, ) self.submit_order(order) def on_data(self, data: GenericData): """ Actions to be performed when the strategy is running and receives generic data. Parameters ---------- data : GenericData 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) # self.unsubscribe_order_book(self.instrument_id, interval=5) # self.unsubscribe_quote_ticks(self.instrument_id) # self.unsubscribe_trade_ticks(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 {} 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. """ pass def on_dispose(self): """ Actions to be performed when the strategy is disposed. Cleanup any resources used by the strategy here. """ pass
class TestExponentialMovingAverage: def setup(self): # Fixture Setup self.ema = ExponentialMovingAverage(10) def test_name_returns_expected_string(self): # Arrange, Act, Assert assert self.ema.name == "ExponentialMovingAverage" def test_str_repr_returns_expected_string(self): # Arrange, Act, Assert assert str(self.ema) == "ExponentialMovingAverage(10)" assert repr(self.ema) == "ExponentialMovingAverage(10)" def test_period_returns_expected_value(self): # Arrange, Act, Assert assert self.ema.period == 10 def test_multiplier_returns_expected_value(self): # Arrange, Act, Assert assert self.ema.alpha == 0.18181818181818182 def test_initialized_without_inputs_returns_false(self): # Arrange, Act, Assert assert self.ema.initialized is False 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 assert self.ema.initialized is True def test_handle_quote_tick_updates_indicator(self): # Arrange indicator = ExponentialMovingAverage(10, PriceType.MID) tick = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act indicator.handle_quote_tick(tick) # Assert assert indicator.has_inputs assert indicator.value == 1.00002 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 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_value_with_one_input_returns_expected_value(self): # Arrange self.ema.update_raw(1.00000) # Act, Assert assert self.ema.value == 1.0 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 assert self.ema.value == 1.5123966942148757 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 assert not self.ema.initialized assert self.ema.value == 0.0
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. Cancels all orders and flattens all positions on stop. """ def __init__( self, symbol: Symbol, bar_spec: BarSpecification, trade_size: Decimal, fast_ema_period: int=10, slow_ema_period: 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_period : int The period for the fast EMA. slow_ema_period : int The period for the slow EMA. """ # 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_period) self.slow_ema = ExponentialMovingAverage(slow_ema_period) def on_start(self): """Actions to be performed on strategy start.""" # 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) # self.subscribe_quote_ticks(self.symbol) # For debugging # self.subscribe_trade_ticks(self.symbol) # For debugging 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_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. """ # self.log.info(f"Received {tick}") # For debugging (must add a subscription) 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. """ # self.log.info(f"Received {tick}") # For debugging (must add a subscription) pass def on_bar(self, bar_type: BarType, bar: Bar): """ Actions to be performed when the strategy is running and receives a bar. Parameters ---------- bar_type : BarType The bar type received. bar : Bar The bar received. """ self.log.info(f"Received {bar_type} {repr(bar)}") # Check if indicators ready if not self.indicators_initialized(): self.log.info(f"Waiting for indicators to warm up " f"[{self.data.bar_count(self.bar_type)}]...") return # Wait for indicators to warm up... # BUY LOGIC if self.fast_ema.value >= self.slow_ema.value: if self.portfolio.is_flat(self.symbol): self.buy() elif self.portfolio.is_net_short(self.symbol): self.flatten_all_positions(self.symbol) self.buy() # SELL LOGIC elif self.fast_ema.value < self.slow_ema.value: if self.portfolio.is_flat(self.symbol): self.sell() elif self.portfolio.is_net_long(self.symbol): self.flatten_all_positions(self.symbol) self.sell() def buy(self): """ Users simple buy method (example). """ order: MarketOrder = self.order_factory.market( symbol=self.symbol, order_side=OrderSide.BUY, quantity=Quantity(self.trade_size), # time_in_force=TimeInForce.FOK, ) self.submit_order(order) def sell(self): """ Users simple sell method (example). """ order: MarketOrder = self.order_factory.market( symbol=self.symbol, order_side=OrderSide.SELL, quantity=Quantity(self.trade_size), # time_in_force=TimeInForce.FOK, ) 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.symbol) self.flatten_all_positions(self.symbol) # Unsubscribe from data self.unsubscribe_bars(self.bar_type) # self.unsubscribe_quote_ticks(self.symbol) # self.unsubscribe_trade_ticks(self.symbol) 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 Notes ----- "OrderIdCount' is a reserved key for the returned state dictionary. """ return {} 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 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