Exemplo n.º 1
0
    def test_floor_with_ten_ones_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        for _i in range(20):
            floored_atr.update_raw(1.00000, 1.00000, 1.00000)

        # Act, Assert
        assert floored_atr.value == 5e-05
Exemplo n.º 2
0
    def test_floor_with_ten_ones_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        for i in range(20):
            floored_atr.update(1.00000, 1.00000, 1.00000)

        # Act
        # Assert
        self.assertEqual(5e-05, floored_atr.value)
    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
Exemplo n.º 4
0
    def test_handle_bar_updates_indicator(self):
        # Arrange
        indicator = AverageTrueRange(10)

        bar = TestDataStubs.bar_5decimal()

        # Act
        indicator.handle_bar(bar)

        # Assert
        assert indicator.has_inputs
        assert indicator.value == 2.999999999997449e-05
Exemplo n.º 5
0
    def test_handle_bar_updates_indicator(self):
        # Arrange
        indicator = AverageTrueRange(10)

        bar = TestStubs.bar_5decimal()

        # Act
        indicator.handle_bar(bar)

        # Assert
        self.assertTrue(indicator.has_inputs)
        self.assertEqual(2.999999999997449e-05, indicator.value)
Exemplo n.º 6
0
    def __init__(
        self,
        symbol: Symbol,
        bar_spec: BarSpecification,
        trade_size: Decimal,
        fast_ema_period: int=10,
        slow_ema_period: int=20,
        atr_period: int=20,
        trail_atr_multiple: float=2.0,
    ):
        """
        Initialize a new instance of the `EMACrossWithTrailingStop` class.

        Parameters
        ----------
        symbol : Symbol
            The symbol for the strategy.
        bar_spec : BarSpecification
            The bar specification for the strategy.
        trade_size : Decimal
            The position size per trade.
        fast_ema_period : int
            The period for the fast EMA indicator.
        slow_ema_period : int
            The period for the slow EMA indicator.
        atr_period : int
            The period for the ATR indicator.
        trail_atr_multiple : float
            The ATR multiple for the trailing stop.

        """
        # The order_id_tag should be unique at the 'trader level', here we are
        # just using the traded instruments symbol as the strategy order id tag.
        super().__init__(order_id_tag=symbol.code.replace('/', ""))

        # Custom strategy variables
        self.symbol = symbol
        self.bar_type = BarType(symbol, bar_spec)
        self.trade_size = trade_size
        self.trail_atr_multiple = trail_atr_multiple
        self.instrument = None  # Initialize in on_start
        self.tick_size = None   # Initialize in on_start

        # Create the indicators for the strategy
        self.fast_ema = ExponentialMovingAverage(fast_ema_period)
        self.slow_ema = ExponentialMovingAverage(slow_ema_period)
        self.atr = AverageTrueRange(atr_period)

        # Users order management variables
        self.entry = None
        self.trailing_stop = None
Exemplo n.º 7
0
    def test_floor_with_exponentially_decreasing_high_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        high = 1.00020
        low = 1.00000
        close = 1.00000

        for _i in range(20):
            high -= (high - low) / 2
            floored_atr.update_raw(high, low, close)

        # Act, Assert
        assert floored_atr.value == 5e-05
    def __init__(self, config: VolatilityMarketMakerConfig):
        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.atr_multiple = config.atr_multiple

        # Create the indicators for the strategy
        self.atr = AverageTrueRange(config.atr_period)

        self.instrument: Optional[Instrument] = None  # Initialized in on_start

        # Users order management variables
        self.buy_order: Union[LimitOrder, None] = None
        self.sell_order: Union[LimitOrder, None] = None
    def __init__(
            self,
            instrument_id: InstrumentId,
            bar_spec: BarSpecification,
            trade_size: Decimal,
            atr_period: int,
            atr_multiple: float,
            order_id_tag: str,  # Must be unique at 'trader level'
    ):
        """
        Initialize a new instance of the `VolatilityMarketMaker` 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.
        atr_period : int
            The period for the ATR indicator.
        atr_multiple : float
            The ATR multiple for bracketing limit orders.
        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
        self.atr_multiple = atr_multiple
        self.instrument = None  # Request on start instead
        self.price_precision = None  # Initialized on start

        # Create the indicators for the strategy
        self.atr = AverageTrueRange(atr_period)

        # Users order management variables
        self.buy_order: Union[LimitOrder, None] = None
        self.sell_order: Union[LimitOrder, None] = None
    def __init__(
        self,
        symbol: Symbol,
        bar_spec: BarSpecification,
        trade_size: Decimal,
        atr_period: int = 20,
        atr_multiple: float = 2.0,
    ):
        """
        Initialize a new instance of the `VolatilityMarketMaker` 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.
        atr_period : int
            The period for the ATR indicator.
        atr_multiple : float
            The ATR multiple for bracketing limit orders.

        """
        # The order_id_tag should be unique at the 'trader level', here we are
        # just using the traded instruments symbol as the strategy order id tag.
        super().__init__(order_id_tag=symbol.code.replace('/', ""))

        # Custom strategy variables
        self.symbol = symbol
        self.bar_type = BarType(symbol, bar_spec)
        self.trade_size = trade_size
        self.atr_multiple = atr_multiple
        self.instrument = None  # Request on start instead
        self.price_precision = None  # Initialized on start

        # Create the indicators for the strategy
        self.atr = AverageTrueRange(atr_period)

        # Users order management variables
        self.buy_order: Union[LimitOrder, None] = None
        self.sell_order: Union[LimitOrder, None] = None
Exemplo n.º 11
0
    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
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
    def __init__(self,
                 symbol: Symbol,
                 bar_spec: BarSpecification,
                 risk_bp: float = 10.0,
                 fast_ema: int = 10,
                 slow_ema: int = 20,
                 atr_period: int = 20,
                 sl_atr_multiple: float = 2.0,
                 extra_id_tag: str = ''):
        """
        Initializes a new instance of the EMACrossPy class.

        :param symbol: The symbol for the strategy.
        :param bar_spec: The bar specification for the strategy.
        :param risk_bp: The risk per trade (basis points).
        :param fast_ema: The fast EMA period.
        :param slow_ema: The slow EMA period.
        :param atr_period: The ATR period.
        :param sl_atr_multiple: The ATR multiple for stop-loss prices.
        :param extra_id_tag: An optional extra tag to append to order ids.
        """
        super().__init__(order_id_tag=symbol.code + extra_id_tag,
                         bar_capacity=40)

        # Custom strategy variables
        self.symbol = symbol
        self.bar_type = BarType(symbol, bar_spec)
        self.precision = 5  # dummy initial value for FX
        self.risk_bp = risk_bp
        self.entry_buffer = 0.0  # instrument.tick_size
        self.SL_buffer = 0.0  # instrument.tick_size * 10
        self.SL_atr_multiple = sl_atr_multiple

        self.position_sizer = None  # initialized in on_start()
        self.quote_currency = None  # initialized in on_start()

        # Create the indicators for the strategy
        self.fast_ema = ExponentialMovingAverage(fast_ema)
        self.slow_ema = ExponentialMovingAverage(slow_ema)
        self.atr = AverageTrueRange(atr_period)
Exemplo n.º 14
0
class AverageTrueRangeTests(unittest.TestCase):
    def setUp(self):
        # Fixture Setup
        self.atr = AverageTrueRange(10)

    def test_name_returns_expected_string(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual("AverageTrueRange", self.atr.name)

    def test_str_repr_returns_expected_string(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual("AverageTrueRange(10, SIMPLE, True, 0.0)",
                         str(self.atr))
        self.assertEqual("AverageTrueRange(10, SIMPLE, True, 0.0)",
                         repr(self.atr))

    def test_period(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual(10, self.atr.period)

    def test_initialized_without_inputs_returns_false(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual(False, self.atr.initialized)

    def test_initialized_with_required_inputs_returns_true(self):
        # Arrange
        # Act
        for _i in range(10):
            self.atr.update_raw(1.00000, 1.00000, 1.00000)

        # Assert
        self.assertEqual(True, self.atr.initialized)

    def test_handle_bar_updates_indicator(self):
        # Arrange
        indicator = AverageTrueRange(10)

        bar = TestStubs.bar_5decimal()

        # Act
        indicator.handle_bar(bar)

        # Assert
        self.assertTrue(indicator.has_inputs)
        self.assertEqual(2.999999999997449e-05, indicator.value)

    def test_value_with_no_inputs_returns_zero(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual(0.0, self.atr.value)

    def test_value_with_epsilon_input(self):
        # Arrange
        epsilon = sys.float_info.epsilon
        self.atr.update_raw(epsilon, epsilon, epsilon)

        # Act
        # Assert
        self.assertEqual(0.0, self.atr.value)

    def test_value_with_one_ones_input(self):
        # Arrange
        self.atr.update_raw(1.00000, 1.00000, 1.00000)

        # Act
        # Assert
        self.assertEqual(0.0, self.atr.value)

    def test_value_with_one_input(self):
        # Arrange
        self.atr.update_raw(1.00020, 1.00000, 1.00010)

        # Act
        # Assert
        self.assertAlmostEqual(0.00020, self.atr.value)

    def test_value_with_three_inputs(self):
        # Arrange
        self.atr.update_raw(1.00020, 1.00000, 1.00010)
        self.atr.update_raw(1.00020, 1.00000, 1.00010)
        self.atr.update_raw(1.00020, 1.00000, 1.00010)

        # Act
        # Assert
        self.assertAlmostEqual(0.00020, self.atr.value)

    def test_value_with_close_on_high(self):
        # Arrange
        high = 1.00010
        low = 1.00000

        # Act
        for _i in range(1000):
            high += 0.00010
            low += 0.00010
            close = high
            self.atr.update_raw(high, low, close)

        # Assert
        self.assertAlmostEqual(0.00010, self.atr.value, 2)

    def test_value_with_close_on_low(self):
        # Arrange
        high = 1.00010
        low = 1.00000

        # Act
        for _i in range(1000):
            high -= 0.00010
            low -= 0.00010
            close = low
            self.atr.update_raw(high, low, close)

        # Assert
        self.assertAlmostEqual(0.00010, self.atr.value)

    def test_floor_with_ten_ones_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        for _i in range(20):
            floored_atr.update_raw(1.00000, 1.00000, 1.00000)

        # Act
        # Assert
        self.assertEqual(5e-05, floored_atr.value)

    def test_floor_with_exponentially_decreasing_high_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        high = 1.00020
        low = 1.00000
        close = 1.00000

        for _i in range(20):
            high -= (high - low) / 2
            floored_atr.update_raw(high, low, close)

        # Act
        # Assert
        self.assertEqual(5e-05, floored_atr.value)

    def test_reset_successfully_returns_indicator_to_fresh_state(self):
        # Arrange
        for _i in range(1000):
            self.atr.update_raw(1.00010, 1.00000, 1.00005)

        # Act
        self.atr.reset()

        # Assert
        self.assertFalse(self.atr.initialized)
        self.assertEqual(0, self.atr.value)
class VolatilityMarketMaker(TradingStrategy):
    """
    A very dumb market maker which brackets the current market based on
    volatility measured by an ATR indicator.

    Cancels all orders and flattens all positions on stop.
    """
    def __init__(
            self,
            instrument_id: InstrumentId,
            bar_spec: BarSpecification,
            trade_size: Decimal,
            atr_period: int,
            atr_multiple: float,
            order_id_tag: str,  # Must be unique at 'trader level'
    ):
        """
        Initialize a new instance of the `VolatilityMarketMaker` 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.
        atr_period : int
            The period for the ATR indicator.
        atr_multiple : float
            The ATR multiple for bracketing limit orders.
        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
        self.atr_multiple = atr_multiple
        self.instrument = None  # Request on start instead
        self.price_precision = None  # Initialized on start

        # Create the indicators for the strategy
        self.atr = AverageTrueRange(atr_period)

        # Users order management variables
        self.buy_order: Union[LimitOrder, None] = None
        self.sell_order: Union[LimitOrder, None] = None

    def on_start(self):
        """Actions to be performed on strategy start."""
        self.instrument = self.data.instrument(self.instrument_id)
        self.price_precision = self.instrument.price_precision

        # Register the indicators for updating
        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)
        self.subscribe_quote_ticks(self.instrument_id)
        # self.subscribe_order_book(self.instrument_id, level=2, depth=5, interval=5)  # 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_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...

        last: QuoteTick = self.data.quote_tick(self.instrument_id)
        if last is None:
            self.log.error("No quotes yet.")
            return

        # Maintain buy orders
        if self.buy_order and self.buy_order.is_working:
            self.cancel_order(self.buy_order)
        self.create_buy_order(last)

        # Maintain sell orders
        if self.sell_order and self.sell_order.is_working:
            self.cancel_order(self.sell_order)
        self.create_sell_order(last)

    def create_buy_order(self, last: QuoteTick):
        """
        A market makers simple buy limit method (example).
        """
        price: Decimal = last.bid - (self.atr.value * self.atr_multiple)
        order: LimitOrder = self.order_factory.limit(
            instrument_id=self.instrument_id,
            order_side=OrderSide.BUY,
            quantity=Quantity(self.trade_size),
            price=Price(price, self.price_precision),
            time_in_force=TimeInForce.GTC,
            post_only=True,  # Default value is True
            hidden=False,  # Default value is False
        )

        self.buy_order = order
        self.submit_order(order)

    def create_sell_order(self, last: QuoteTick):
        """
        A market makers simple sell limit method (example).
        """
        price: Decimal = last.ask + (self.atr.value * self.atr_multiple)
        order: LimitOrder = self.order_factory.limit(
            instrument_id=self.instrument_id,
            order_side=OrderSide.SELL,
            quantity=Quantity(self.trade_size),
            price=Price(price, self.price_precision),
            time_in_force=TimeInForce.GTC,
            post_only=True,  # Default value is True
            hidden=False,  # Default value is False
        )

        self.sell_order = order
        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 object 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.

        """
        last: QuoteTick = self.data.quote_tick(self.instrument_id)
        if last is None:
            self.log.error("No quotes yet.")
            return

        # If order filled then replace order at atr multiple distance from the market
        if isinstance(event, OrderFilled):
            if event.order_side == OrderSide.BUY:
                if self.buy_order.is_completed:
                    self.create_buy_order(last)
            elif event.order_side == OrderSide.SELL:
                if self.sell_order.is_completed:
                    self.create_sell_order(last)

    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_order_book(self.instrument_id, interval=5)
        # self.unsubscribe_trade_ticks(self.instrument_id)

    def on_reset(self):
        """
        Actions to be performed when the strategy is reset.
        """
        # Reset indicators here
        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.º 16
0
 def setUp(self):
     # Arrange
     self.atr = AverageTrueRange(10)
Exemplo n.º 17
0
class AverageTrueRangeTests(unittest.TestCase):

    # Fixture Setup
    def setUp(self):
        # Arrange
        self.atr = AverageTrueRange(10)

    def test_name(self):
        # Act
        # Assert
        self.assertEqual('AverageTrueRange', self.atr.name)

    def test_str(self):
        # Act
        # Assert
        self.assertEqual('AverageTrueRange(10, SIMPLE, True, 0.0)',
                         str(self.atr))

    def test_repr(self):
        # Act
        # Assert
        self.assertTrue(
            repr(self.atr).startswith(
                '<AverageTrueRange(10, SIMPLE, True, 0.0) object at'))
        self.assertTrue(repr(self.atr).endswith('>'))

    def test_period(self):
        # Act
        # Assert
        self.assertEqual(10, self.atr.period)

    def test_initialized_without_inputs_returns_false(self):
        # Act
        # Assert
        self.assertEqual(False, self.atr.initialized)

    def test_initialized_with_required_inputs_returns_true(self):
        # Act
        for i in range(10):
            self.atr.update(1.00000, 1.00000, 1.00000)

        # Assert
        self.assertEqual(True, self.atr.initialized)

    def test_initialized_with_required_mid_inputs_returns_true(self):
        # Act
        for i in range(10):
            self.atr.update_mid(1.00000)

        # Assert
        self.assertEqual(True, self.atr.initialized)

    def test_value_with_no_inputs_returns_zero(self):
        # Act
        # Assert
        self.assertEqual(0.0, self.atr.value)

    def test_value_with_epsilon_input(self):
        # Arrange
        epsilon = sys.float_info.epsilon
        self.atr.update(epsilon, epsilon, epsilon)

        # Act
        # Assert
        self.assertEqual(0.0, self.atr.value)

    def test_value_with_one_ones_input(self):
        # Arrange
        self.atr.update(1.00000, 1.00000, 1.00000)

        # Act
        # Assert
        self.assertEqual(0.0, self.atr.value)

    def test_value_with_one_input(self):
        # Arrange
        self.atr.update(1.00020, 1.00000, 1.00010)

        # Act
        # Assert
        self.assertAlmostEqual(0.00020, self.atr.value)

    def test_value_with_three_inputs(self):
        # Arrange
        self.atr.update(1.00020, 1.00000, 1.00010)
        self.atr.update(1.00020, 1.00000, 1.00010)
        self.atr.update(1.00020, 1.00000, 1.00010)

        # Act
        # Assert
        self.assertAlmostEqual(0.00020, self.atr.value)

    def test_value_with_three_mid_inputs(self):
        # Arrange
        self.atr.update_mid(1.00000)
        self.atr.update_mid(1.00020)
        self.atr.update_mid(1.00040)

        # Act
        # Assert
        self.assertAlmostEqual(0.00013333333333331865, self.atr.value)

    def test_value_with_close_on_high(self):
        # Arrange
        high = 1.00010
        low = 1.00000

        # Act
        for i in range(1000):
            high += 0.00010
            low += 0.00010
            close = high
            self.atr.update(high, low, close)

        # Assert
        self.assertAlmostEqual(0.00010, self.atr.value, 2)

    def test_value_with_close_on_low(self):
        # Arrange
        high = 1.00010
        low = 1.00000

        # Act
        for i in range(1000):
            high -= 0.00010
            low -= 0.00010
            close = low
            self.atr.update(high, low, close)

        # Assert
        self.assertAlmostEqual(0.00010, self.atr.value)

    def test_floor_with_ten_ones_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        for i in range(20):
            floored_atr.update(1.00000, 1.00000, 1.00000)

        # Act
        # Assert
        self.assertEqual(5e-05, floored_atr.value)

    def test_floor_with_exponentially_decreasing_high_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        high = 1.00020
        low = 1.00000
        close = 1.00000

        for i in range(20):
            high -= (high - low) / 2
            floored_atr.update(high, low, close)

        # Act
        # Assert
        self.assertEqual(5e-05, floored_atr.value)

    def test_reset_successfully_returns_indicator_to_fresh_state(self):
        # Arrange
        for i in range(1000):
            self.atr.update(1.00010, 1.00000, 1.00005)

        # Act
        self.atr.reset()

        # Assert
        self.assertEqual(0.0, self.atr.value)  # No assertion errors.

    def test_with_battery_signal(self):
        # Arrange
        battery_signal = BatterySeries.create()
        output = []

        # Act
        for point in BatterySeries.create():
            self.atr.update(point, sys.float_info.epsilon,
                            sys.float_info.epsilon)
            output.append(self.atr.value)

        # Assert
        self.assertEqual(len(battery_signal), len(output))
Exemplo n.º 18
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
class VolatilityMarketMaker(TradingStrategy):
    """
    A very dumb market maker which brackets the current market based on
    volatility measured by an ATR indicator.

    Cancels all orders and flattens all positions on stop.

    Parameters
    ----------
    config : VolatilityMarketMakerConfig
        The configuration for the instance.
    """

    def __init__(self, config: VolatilityMarketMakerConfig):
        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.atr_multiple = config.atr_multiple

        # Create the indicators for the strategy
        self.atr = AverageTrueRange(config.atr_period)

        self.instrument: Optional[Instrument] = None  # Initialized in on_start

        # Users order management variables
        self.buy_order: Union[LimitOrder, None] = None
        self.sell_order: Union[LimitOrder, None] = 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

        # Register the indicators for updating
        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)
        self.subscribe_quote_ticks(self.instrument_id)
        # self.subscribe_trade_ticks(self.instrument_id)
        # self.subscribe_order_book_deltas(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(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(str(order_book))  # For debugging (must add a subscription)
        pass

    def on_order_book_delta(self, delta: OrderBookDelta):
        """
        Actions to be performed when the strategy is running and receives an order book delta.

        Parameters
        ----------
        delta : OrderBookDelta
            The order book delta received.

        """
        # self.log.info(str(delta), LogColor.GREEN)  # For debugging (must add a subscription)
        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.cache.bar_count(self.bar_type)}]...",
                color=LogColor.BLUE,
            )
            return  # Wait for indicators to warm up...

        last: QuoteTick = self.cache.quote_tick(self.instrument_id)
        if last is None:
            self.log.info("No quotes yet...")
            return

        # Maintain buy orders
        if self.buy_order and self.buy_order.is_open:
            self.cancel_order(self.buy_order)
        self.create_buy_order(last)

        # Maintain sell orders
        if self.sell_order and self.sell_order.is_open:
            self.cancel_order(self.sell_order)
        self.create_sell_order(last)

    def create_buy_order(self, last: QuoteTick):
        """
        Market maker simple buy limit method (example).
        """
        price: Decimal = last.bid - (self.atr.value * self.atr_multiple)
        order: LimitOrder = self.order_factory.limit(
            instrument_id=self.instrument_id,
            order_side=OrderSide.BUY,
            quantity=self.instrument.make_qty(self.trade_size),
            price=self.instrument.make_price(price),
            time_in_force=TimeInForce.GTC,
            post_only=True,  # default value is True
            # display_qty=self.instrument.make_qty(self.trade_size / 2),  # iceberg
        )

        self.buy_order = order
        self.submit_order(order)

    def create_sell_order(self, last: QuoteTick):
        """
        Market maker simple sell limit method (example).
        """
        price: Decimal = last.ask + (self.atr.value * self.atr_multiple)
        order: LimitOrder = self.order_factory.limit(
            instrument_id=self.instrument_id,
            order_side=OrderSide.SELL,
            quantity=self.instrument.make_qty(self.trade_size),
            price=self.instrument.make_price(price),
            time_in_force=TimeInForce.GTC,
            post_only=True,  # default value is True
            # display_qty=self.instrument.make_qty(self.trade_size / 2),  # iceberg
        )

        self.sell_order = order
        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.

        """
        last: QuoteTick = self.cache.quote_tick(self.instrument_id)
        if last is None:
            self.log.info("No quotes yet...")
            return

        # If order filled then replace order at atr multiple distance from the market
        if isinstance(event, OrderFilled):
            if event.order_side == OrderSide.BUY:
                if self.buy_order.is_closed:
                    self.create_buy_order(last)
            elif event.order_side == OrderSide.SELL:
                if self.sell_order.is_closed:
                    self.create_sell_order(last)

    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)

    def on_reset(self):
        """
        Actions to be performed when the strategy is reset.
        """
        # Reset indicators here
        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.º 20
0
 def setUp(self):
     # Fixture Setup
     self.atr = AverageTrueRange(10)
Exemplo n.º 21
0
class TestAverageTrueRange:
    def setup(self):
        # Fixture Setup
        self.atr = AverageTrueRange(10)

    def test_name_returns_expected_string(self):
        # Arrange, Act, Assert
        assert self.atr.name == "AverageTrueRange"

    def test_str_repr_returns_expected_string(self):
        # Arrange, Act, Assert
        assert str(self.atr) == "AverageTrueRange(10, SIMPLE, True, 0.0)"
        assert repr(self.atr) == "AverageTrueRange(10, SIMPLE, True, 0.0)"

    def test_period(self):
        # Arrange, Act, Assert
        assert self.atr.period == 10

    def test_initialized_without_inputs_returns_false(self):
        # Arrange, Act, Assert
        assert self.atr.initialized is False

    def test_initialized_with_required_inputs_returns_true(self):
        # Arrange, Act
        for _i in range(10):
            self.atr.update_raw(1.00000, 1.00000, 1.00000)

        # Assert
        assert self.atr.initialized is True

    def test_handle_bar_updates_indicator(self):
        # Arrange
        indicator = AverageTrueRange(10)

        bar = TestDataStubs.bar_5decimal()

        # Act
        indicator.handle_bar(bar)

        # Assert
        assert indicator.has_inputs
        assert indicator.value == 2.999999999997449e-05

    def test_value_with_no_inputs_returns_zero(self):
        # Arrange, Act, Assert
        assert self.atr.value == 0.0

    def test_value_with_epsilon_input(self):
        # Arrange
        epsilon = sys.float_info.epsilon
        self.atr.update_raw(epsilon, epsilon, epsilon)

        # Act, Assert
        assert self.atr.value == 0.0

    def test_value_with_one_ones_input(self):
        # Arrange
        self.atr.update_raw(1.00000, 1.00000, 1.00000)

        # Act, Assert
        assert self.atr.value == 0.0

    def test_value_with_one_input(self):
        # Arrange
        self.atr.update_raw(1.00020, 1.00000, 1.00010)

        # Act, Assert
        assert self.atr.value == pytest.approx(0.00020)

    def test_value_with_three_inputs(self):
        # Arrange
        self.atr.update_raw(1.00020, 1.00000, 1.00010)
        self.atr.update_raw(1.00020, 1.00000, 1.00010)
        self.atr.update_raw(1.00020, 1.00000, 1.00010)

        # Act, Assert
        assert self.atr.value == pytest.approx(0.00020)

    def test_value_with_close_on_high(self):
        # Arrange
        high = 1.00010
        low = 1.00000

        # Act
        for _i in range(1000):
            high += 0.00010
            low += 0.00010
            close = high
            self.atr.update_raw(high, low, close)

        # Assert
        assert self.atr.value == pytest.approx(0.00010, 2)

    def test_value_with_close_on_low(self):
        # Arrange
        high = 1.00010
        low = 1.00000

        # Act
        for _i in range(1000):
            high -= 0.00010
            low -= 0.00010
            close = low
            self.atr.update_raw(high, low, close)

        # Assert
        assert self.atr.value == pytest.approx(0.00010)

    def test_floor_with_ten_ones_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        for _i in range(20):
            floored_atr.update_raw(1.00000, 1.00000, 1.00000)

        # Act, Assert
        assert floored_atr.value == 5e-05

    def test_floor_with_exponentially_decreasing_high_inputs(self):
        # Arrange
        floor = 0.00005
        floored_atr = AverageTrueRange(10, value_floor=floor)

        high = 1.00020
        low = 1.00000
        close = 1.00000

        for _i in range(20):
            high -= (high - low) / 2
            floored_atr.update_raw(high, low, close)

        # Act, Assert
        assert floored_atr.value == 5e-05

    def test_reset_successfully_returns_indicator_to_fresh_state(self):
        # Arrange
        for _i in range(1000):
            self.atr.update_raw(1.00010, 1.00000, 1.00005)

        # Act
        self.atr.reset()

        # Assert
        assert not self.atr.initialized
        assert self.atr.value == 0
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