Exemplo n.º 1
0
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
Exemplo n.º 2
0
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))
Exemplo n.º 3
0
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
Exemplo n.º 5
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.
    """
    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)
Exemplo n.º 6
0
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)
Exemplo n.º 7
0
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
Exemplo n.º 9
0
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
Exemplo n.º 10
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