def test_can_calculate_for_usdjpy(self): # Arrange sizer = FixedRiskSizer(TestStubs.instrument_usdjpy()) equity = Money(1000000, Currency.USD) # Act result = sizer.calculate( equity, 10, # 0.1% Price(107.703, 3), Price(107.403, 3), exchange_rate=0.0093, units=1, unit_batch_size=1000) # Assert self.assertEqual(Quantity(358000), result)
def test_calculate_for_usdjpy(self): # Arrange sizer = FixedRiskSizer( InstrumentLoader.default_fx_ccy(TestStubs.symbol_usdjpy_fxcm())) equity = Money(1000000, USD) # Act result = sizer.calculate( equity, 10, # 0.1% Price("107.703"), Price("107.403"), exchange_rate=0.0093, units=1, unit_batch_size=1000, ) # Assert self.assertEqual(Quantity(358000), result)
def test_calculate_for_usdjpy_with_commission(self): # Arrange sizer = FixedRiskSizer( TestInstrumentProvider.default_fx_ccy(TestStubs.symbol_usdjpy())) equity = Money(1_000_000, USD) # Act result = sizer.calculate( entry=Price("107.703"), stop_loss=Price("107.403"), equity=equity, risk=Decimal("0.01"), # 1% commission_rate=Decimal("0.0002"), exchange_rate=Decimal(str(1 / 107.403)), unit_batch_size=Decimal(1000), units=1, ) # Assert self.assertEqual(Quantity(3578000), result)
class FixedRiskSizerTests(unittest.TestCase): def setUp(self): # Fixture Setup self.sizer = FixedRiskSizer(USDJPY) def test_calculate_with_zero_equity_returns_quantity_zero(self): # Arrange equity = Money(0, USD) # No equity # Act result = self.sizer.calculate( entry=Price("1.00100"), stop_loss=Price("1.00000"), equity=equity, risk=Decimal("0.001"), # 0.1% unit_batch_size=Decimal(1000), ) # Assert self.assertEqual(Quantity(0), result) def test_calculate_with_zero_exchange_rate_returns_quantity_zero(self): # Arrange equity = Money(0, USD) # No equity # Act result = self.sizer.calculate( entry=Price("1.00100"), stop_loss=Price("1.00000"), equity=equity, risk=Decimal("0.001"), # 0.1% exchange_rate=Decimal("0"), ) # Assert self.assertEqual(Quantity(0), result) def test_calculate_with_zero_risk_returns_quantity_zero(self): # Arrange equity = Money(0, USD) # No equity # Act result = self.sizer.calculate( entry=Price("1.00100"), stop_loss=Price("1.00100"), equity=equity, risk=Decimal("0.001"), # 0.1% exchange_rate=Decimal("0"), ) # Assert self.assertEqual(Quantity(0), result) def test_calculate_single_unit_size(self): # Arrange equity = Money(1_000_000, USD) # Act result = self.sizer.calculate( entry=Price("1.00100"), stop_loss=Price("1.00000"), equity=equity, risk=Decimal("0.001"), # 0.1% unit_batch_size=Decimal(1000), ) # Assert self.assertEqual(Quantity(1_000_000), result) def test_calculate_single_unit_with_exchange_rate(self): # Arrange equity = Money(1_000_000, USD) # Act result = self.sizer.calculate( entry=Price("110.010"), stop_loss=Price("110.000"), equity=equity, risk=Decimal("0.001"), # 1% exchange_rate=Decimal(str(1 / 110)), ) # Assert self.assertEqual(Quantity(10000000), result) def test_calculate_single_unit_size_when_risk_too_high(self): # Arrange equity = Money(100000, USD) # Act result = self.sizer.calculate( entry=Price("3.00000"), stop_loss=Price("1.00000"), equity=equity, risk=Decimal("0.01"), # 1% unit_batch_size=Decimal(1000), ) # Assert self.assertEqual(Quantity(), result) def test_impose_hard_limit(self): # Arrange equity = Money(1_000_000, USD) # Act result = self.sizer.calculate( entry=Price("1.00010"), stop_loss=Price("1.00000"), equity=equity, risk=Decimal("0.01"), # 1% hard_limit=Decimal(500000), unit_batch_size=Decimal(1000), units=1, ) # Assert self.assertEqual(Quantity(500000), result) def test_calculate_multiple_unit_size(self): # Arrange equity = Money(1_000_000, USD) # Act result = self.sizer.calculate( entry=Price("1.00010"), stop_loss=Price("1.00000"), equity=equity, risk=Decimal("0.001"), # 0.1% unit_batch_size=Decimal(1000), units=3, ) # Assert self.assertEqual(Quantity(3333000), result) def test_calculate_multiple_unit_size_larger_batches(self): # Arrange equity = Money(1_000_000, USD) # Act result = self.sizer.calculate( entry=Price("1.00087"), stop_loss=Price("1.00000"), equity=equity, risk=Decimal("0.001"), # 0.1% unit_batch_size=Decimal(25000), units=4, ) # Assert self.assertEqual(Quantity(275000), result) def test_calculate_for_usdjpy_with_commission(self): # Arrange sizer = FixedRiskSizer( TestInstrumentProvider.default_fx_ccy(TestStubs.symbol_usdjpy())) equity = Money(1_000_000, USD) # Act result = sizer.calculate( entry=Price("107.703"), stop_loss=Price("107.403"), equity=equity, risk=Decimal("0.01"), # 1% commission_rate=Decimal("0.0002"), exchange_rate=Decimal(str(1 / 107.403)), unit_batch_size=Decimal(1000), units=1, ) # Assert self.assertEqual(Quantity(3578000), result)
class EMACross(TradingStrategy): """" A simple moving average cross example strategy. When the fast EMA crosses the slow EMA then a STOP entry atomic order is placed for that direction with a trailing stop and profit target at 1R risk. """ 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. """ super().__init__(order_id_tag=symbol.code + extra_id_tag, bar_capacity=40) # Custom strategy variables self.symbol = symbol self.bar_type = BarType(symbol, bar_spec) self.precision = 5 # dummy initial value for FX self.risk_bp = risk_bp self.entry_buffer = 0.0 # instrument.tick_size self.SL_buffer = 0.0 # instrument.tick_size * 10 self.SL_atr_multiple = sl_atr_multiple self.position_sizer = None # initialized in on_start() self.quote_currency = None # initialized in on_start() # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(fast_ema) self.slow_ema = ExponentialMovingAverage(slow_ema) self.atr = AverageTrueRange(atr_period) def on_start(self): """ This method is called when self.start() is called, and after internal start logic. """ # Put custom code to be run on strategy start here (or pass) instrument = self.get_instrument(self.symbol) self.precision = instrument.price_precision self.entry_buffer = instrument.tick_size.as_double() * 3.0 self.SL_buffer = instrument.tick_size * 10.0 self.position_sizer = FixedRiskSizer(instrument) self.quote_currency = instrument.quote_currency # Register the indicators for updating self.register_indicator(data_source=self.bar_type, indicator=self.fast_ema, update_method=self.fast_ema.update) self.register_indicator(data_source=self.bar_type, indicator=self.slow_ema, update_method=self.slow_ema.update) self.register_indicator(data_source=self.bar_type, indicator=self.atr, update_method=self.atr.update) # Get historical data self.get_ticks(self.symbol) self.get_bars(self.bar_type) # Subscribe to live data self.subscribe_instrument(self.symbol) self.subscribe_bars(self.bar_type) self.subscribe_ticks(self.symbol) def on_tick(self, tick: Tick): """ This method is called whenever a Tick is received by the strategy, and after the Tick has been processed by the base class. The received Tick object is then passed into this method. :param tick: The received tick. """ # self.log.info(f"Received Tick({tick})") # For debugging def on_bar(self, bar_type: BarType, bar: Bar): """ This method is called whenever the strategy receives a Bar, and after the Bar has been processed by the base class. The received BarType and Bar objects are then passed into this method. :param bar_type: The received bar type. :param bar: The received bar. """ self.log.info(f"Received {bar_type} Bar({bar})") # For debugging # Check if indicators ready if not self.indicators_initialized(): self.log.info(f"Waiting for indicators to warm up " f"[{self.bar_count(self.bar_type)}]...") return # Wait for indicators to warm up... # Check if tick data available if not self.has_ticks(self.symbol): self.log.info(f"Waiting for {self.symbol.value} ticks...") return # Wait for ticks... # Check average spread average_spread = self.spread_average(self.symbol) if average_spread == 0.0: self.log.warning( f"average_spread == {average_spread} (not initialized).") return # Protect divide by zero # Check liquidity liquidity_ratio = self.atr.value / average_spread if liquidity_ratio < 2.0: self.log.info( f"liquidity_ratio == {liquidity_ratio} (no liquidity).") return spread_buffer = max(average_spread, self.spread(self.symbol)) sl_buffer = self.atr.value * self.SL_atr_multiple if self.count_orders_working() == 0 and self.is_flat( ): # No active or pending positions # BUY LOGIC if self.fast_ema.value >= self.slow_ema.value: self._enter_long(bar, sl_buffer, spread_buffer) # SELL LOGIC elif self.fast_ema.value < self.slow_ema.value: self._enter_short(bar, sl_buffer, spread_buffer) self._check_trailing_stops(bar, sl_buffer, spread_buffer) def _enter_long(self, bar: Bar, sl_buffer: float, spread_buffer: float): price_entry = Price( bar.high.as_double() + self.entry_buffer + spread_buffer, self.precision) price_stop_loss = Price(bar.low.as_double() - sl_buffer, self.precision) risk = price_entry.as_double() - price_stop_loss.as_double() price_take_profit = Price(price_entry.as_double() + risk, self.precision) # Calculate exchange rate exchange_rate = 0.0 try: exchange_rate = self.get_exchange_rate_for_account( quote_currency=self.quote_currency, price_type=PriceType.ASK) except ValueError as ex: self.log.error(ex) if exchange_rate == 0.0: return position_size = self.position_sizer.calculate( equity=self.account().free_equity, risk_bp=self.risk_bp, entry=price_entry, stop_loss=price_stop_loss, exchange_rate=exchange_rate, commission_rate_bp=0.15, hard_limit=20000000, units=1, unit_batch_size=10000) if position_size > 0: atomic_order = self.order_factory.atomic_stop_market( symbol=self.symbol, order_side=OrderSide.BUY, quantity=position_size, entry=price_entry, stop_loss=price_stop_loss, take_profit=price_take_profit, time_in_force=TimeInForce.GTD, expire_time=bar.timestamp + timedelta(minutes=1)) self.submit_atomic_order(atomic_order, self.position_id_generator.generate()) else: self.log.info("Insufficient equity for BUY signal.") def _enter_short(self, bar: Bar, sl_buffer: float, spread_buffer: float): price_entry = Price(bar.low.as_double() - self.entry_buffer, self.precision) price_stop_loss = Price( bar.high.as_double() + sl_buffer + spread_buffer, self.precision) risk = price_stop_loss.as_double() - price_entry.as_double() price_take_profit = Price(price_entry.as_double() - risk, self.precision) # Calculate exchange rate exchange_rate = 0.0 try: exchange_rate = self.get_exchange_rate_for_account( quote_currency=self.quote_currency, price_type=PriceType.BID) except ValueError as ex: self.log.error(ex) if exchange_rate == 0.0: return position_size = self.position_sizer.calculate( equity=self.account().free_equity, risk_bp=self.risk_bp, entry=price_entry, stop_loss=price_stop_loss, exchange_rate=exchange_rate, commission_rate_bp=0.15, hard_limit=20000000, units=1, unit_batch_size=10000) if position_size > 0: # Sufficient equity for a position atomic_order = self.order_factory.atomic_stop_market( symbol=self.symbol, order_side=OrderSide.SELL, quantity=position_size, entry=price_entry, stop_loss=price_stop_loss, take_profit=price_take_profit, time_in_force=TimeInForce.GTD, expire_time=bar.timestamp + timedelta(minutes=1)) self.submit_atomic_order(atomic_order, self.position_id_generator.generate()) else: self.log.info("Insufficient equity for SELL signal.") def _check_trailing_stops(self, bar: Bar, sl_buffer: float, spread_buffer: float): for working_order in self.orders_working().values(): if working_order.purpose == OrderPurpose.STOP_LOSS: # SELL SIDE ORDERS if working_order.is_sell: temp_price = Price(bar.low.as_double() - sl_buffer, self.precision) if temp_price.gt(working_order.price): self.modify_order(working_order, working_order.quantity, temp_price) # BUY SIDE ORDERS elif working_order.is_buy: temp_price = Price( bar.high.as_double() + sl_buffer + spread_buffer, self.precision) if temp_price.lt(working_order.price): self.modify_order(working_order, working_order.quantity, temp_price) def on_data(self, data): """ This method is called whenever the strategy receives a data update. :param data: The received data. """ # Put custom code for data handling here (or pass) pass def on_event(self, event): """ This method is called whenever the strategy receives an Event object, and after the event has been processed by the TradingStrategy base class. These events could be AccountEvent, OrderEvent, PositionEvent, TimeEvent. :param event: The received event. """ # Put custom code for event handling here (or pass) pass def on_stop(self): """ This method is called when self.stop() is called and after internal stopping logic. """ # Put custom code to be run on strategy stop here (or pass) pass def on_reset(self): """ This method is called when self.reset() is called, and after internal reset logic such as clearing the internally held bars, ticks and resetting all indicators. """ # Put custom code to be run on a strategy reset here (or pass) pass def on_save(self) -> {}: # Put custom state to be saved here (or return empty dictionary) return {} def on_load(self, state: {}): # Put custom state to be loaded here (or pass) pass def on_dispose(self): """ This method is called when self.dispose() is called. Dispose of any resources that have been used by the strategy here. """ # Put custom code to be run on a strategy disposal here (or pass) self.unsubscribe_instrument(self.symbol) self.unsubscribe_bars(self.bar_type) self.unsubscribe_ticks(self.symbol)
class FixedRiskSizerTests(unittest.TestCase): def setUp(self): # Fixture Setup self.sizer = FixedRiskSizer(TestStubs.instrument_gbpusd()) def test_can_calculate_single_unit_size(self): # Arrange equity = Money(1000000, Currency.USD) # Act result = self.sizer.calculate( equity, 10, # 0.1% Price(1.00100, 5), Price(1.00000, 5), exchange_rate=1.0, unit_batch_size=1000) # Assert self.assertEqual(Quantity(1000000), result) def test_can_calculate_single_unit_with_exchange_rate(self): # Arrange equity = Money(1000000, Currency.USD) # Act result = self.sizer.calculate( equity, 10, # 0.1% Price(110.010, 3), Price(110.000, 3), exchange_rate=0.01) # Assert self.assertEqual(Quantity(10000000), result) def test_can_calculate_single_unit_size_when_risk_too_high(self): # Arrange equity = Money(100000, Currency.USD) # Act result = self.sizer.calculate( equity, 100, # 1% Price(3.00000, 5), Price(1.00000, 5), unit_batch_size=1000) # Assert self.assertEqual(Quantity(), result) def test_can_impose_hard_limit(self): # Arrange equity = Money(1000000, Currency.USD) # Act result = self.sizer.calculate( equity, 100, # 1% Price(1.00010, 5), Price(1.00000, 5), hard_limit=500000, units=1, unit_batch_size=1000) # Assert self.assertEqual(Quantity(500000), result) def test_can_calculate_multiple_unit_size(self): # Arrange equity = Money(1000000, Currency.USD) # Act result = self.sizer.calculate( equity, 10, # 0.1% Price(1.00010, 5), Price(1.00000, 5), units=3, unit_batch_size=1000) # Assert self.assertEqual(Quantity(3333000), result) def test_can_calculate_multiple_unit_size_larger_batches(self): # Arrange equity = Money(1000000, Currency.USD) # Act result = self.sizer.calculate( equity, 10, # 0.1% Price(1.00087, 5), Price(1.00000, 5), units=4, unit_batch_size=25000) # Assert self.assertEqual(Quantity(275000), result) def test_can_calculate_for_usdjpy(self): # Arrange sizer = FixedRiskSizer(TestStubs.instrument_usdjpy()) equity = Money(1000000, Currency.USD) # Act result = sizer.calculate( equity, 10, # 0.1% Price(107.703, 3), Price(107.403, 3), exchange_rate=0.0093, units=1, unit_batch_size=1000) # Assert self.assertEqual(Quantity(358000), result)
class FixedRiskSizerTests(unittest.TestCase): def setUp(self): # Fixture Setup self.sizer = FixedRiskSizer(USDJPY) def test_calculate_single_unit_size(self): # Arrange equity = Money(1000000, USD) # Act result = self.sizer.calculate( equity, 10, # 0.1% Price("1.00100"), Price("1.00000"), exchange_rate=1.0, unit_batch_size=1000, ) # Assert self.assertEqual(Quantity(999000), result) def test_calculate_single_unit_with_exchange_rate(self): # Arrange equity = Money(1000000, USD) # Act result = self.sizer.calculate( equity, 10, # 0.1% Price("110.010"), Price("110.000"), exchange_rate=0.01, ) # Assert self.assertEqual(Quantity(10000000), result) def test_calculate_single_unit_size_when_risk_too_high(self): # Arrange equity = Money(100000, USD) # Act result = self.sizer.calculate( equity, 100, # 1% Price("3.00000"), Price("1.00000"), unit_batch_size=1000, ) # Assert self.assertEqual(Quantity(), result) def test_impose_hard_limit(self): # Arrange equity = Money(1000000, USD) # Act result = self.sizer.calculate( equity, 100, # 1% Price("1.00010"), Price("1.00000"), hard_limit=500000, units=1, unit_batch_size=1000, ) # Assert self.assertEqual(Quantity(500000), result) def test_calculate_multiple_unit_size(self): # Arrange equity = Money(1000000, USD) # Act result = self.sizer.calculate( equity, 10, # 0.1% Price("1.00010"), Price("1.00000"), units=3, unit_batch_size=1000, ) # Assert self.assertEqual(Quantity(3333000), result) def test_calculate_multiple_unit_size_larger_batches(self): # Arrange equity = Money(1000000, USD) # Act result = self.sizer.calculate( equity, 10, # 0.1% Price("1.00087"), Price("1.00000"), units=4, unit_batch_size=25000, ) # Assert self.assertEqual(Quantity(275000), result) def test_calculate_for_usdjpy(self): # Arrange sizer = FixedRiskSizer( InstrumentLoader.default_fx_ccy(TestStubs.symbol_usdjpy_fxcm())) equity = Money(1000000, USD) # Act result = sizer.calculate( equity, 10, # 0.1% Price("107.703"), Price("107.403"), exchange_rate=0.0093, units=1, unit_batch_size=1000, ) # Assert self.assertEqual(Quantity(358000), result)