def setup(self): # Fixture Setup config = BacktestEngineConfig( bypass_logging=False, run_analysis=False, risk_engine={ "bypass": True, # Example of bypassing pre-trade risk checks for backtests "max_notional_per_order": { "GBP/USD.SIM": 2_000_000 }, }, ) self.engine = BacktestEngine(config=config) self.venue = Venue("SIM") self.gbpusd = TestInstrumentProvider.default_fx_ccy("GBP/USD") # Setup wranglers bid_wrangler = BarDataWrangler( bar_type=BarType.from_str("GBP/USD.SIM-1-MINUTE-BID-EXTERNAL"), instrument=self.gbpusd, ) ask_wrangler = BarDataWrangler( bar_type=BarType.from_str("GBP/USD.SIM-1-MINUTE-ASK-EXTERNAL"), instrument=self.gbpusd, ) # Setup data provider = TestDataProvider() # Build externally aggregated bars bid_bars = bid_wrangler.process( data=provider.read_csv_bars("fxcm-gbpusd-m1-bid-2012.csv"), ) ask_bars = ask_wrangler.process( data=provider.read_csv_bars("fxcm-gbpusd-m1-ask-2012.csv"), ) self.engine.add_instrument(self.gbpusd) self.engine.add_bars(bid_bars) self.engine.add_bars(ask_bars) interest_rate_data = pd.read_csv( os.path.join(PACKAGE_ROOT, "data", "short-term-interest.csv")) fx_rollover_interest = FXRolloverInterestModule( rate_data=interest_rate_data) self.engine.add_venue( venue=self.venue, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], modules=[fx_rollover_interest], )
def test_bar_type_equality(self): # Arrange instrument_id1 = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) instrument_id2 = InstrumentId(Symbol("GBP/USD"), Venue("SIM")) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_type1 = BarType(instrument_id1, bar_spec) bar_type2 = BarType(instrument_id1, bar_spec) bar_type3 = BarType(instrument_id2, bar_spec) # Act, Assert assert bar_type1 == bar_type1 assert bar_type1 == bar_type2 assert bar_type1 != bar_type3
def setup(self): # Fixture Setup config = BacktestEngineConfig( bypass_logging=False, run_analysis=False, ) self.engine = BacktestEngine(config=config) self.venue = Venue("SIM") # Setup data bid_bar_type = BarType( instrument_id=GBPUSD_SIM.id, bar_spec=TestDataStubs.bar_spec_1min_bid(), aggregation_source=AggregationSource.EXTERNAL, # <-- important ) ask_bar_type = BarType( instrument_id=GBPUSD_SIM.id, bar_spec=TestDataStubs.bar_spec_1min_ask(), aggregation_source=AggregationSource.EXTERNAL, # <-- important ) bid_wrangler = BarDataWrangler( bar_type=bid_bar_type, instrument=GBPUSD_SIM, ) ask_wrangler = BarDataWrangler( bar_type=ask_bar_type, instrument=GBPUSD_SIM, ) provider = TestDataProvider() bid_bars = bid_wrangler.process( provider.read_csv_bars("fxcm-gbpusd-m1-bid-2012.csv")) ask_bars = ask_wrangler.process( provider.read_csv_bars("fxcm-gbpusd-m1-ask-2012.csv")) # Add data self.engine.add_instrument(GBPUSD_SIM) self.engine.add_bars(bid_bars) self.engine.add_bars(ask_bars) self.engine.add_venue( venue=self.venue, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], )
def test_binance_bar_repr(self): # Arrange bar = BinanceBar( bar_type=BarType( instrument_id=TestStubs.btcusdt_binance_id(), bar_spec=TestStubs.bar_spec_1min_last(), ), open=Price.from_str("0.01634790"), high=Price.from_str("0.80000000"), low=Price.from_str("0.01575800"), close=Price.from_str("0.01577100"), volume=Quantity.from_str("148976.11427815"), quote_volume=Quantity.from_str("2434.19055334"), count=100, taker_buy_base_volume=Quantity.from_str("1756.87402397"), taker_buy_quote_volume=Quantity.from_str("28.46694368"), ts_event=1500000000000, ts_init=1500000000000, ) # Act, Assert assert ( repr(bar) == "BinanceBar(bar_type=BTC/USDT.BINANCE-1-MINUTE-LAST-EXTERNAL, open=0.01634790, high=0.80000000, low=0.01575800, close=0.01577100, volume=148976.11427815, quote_volume=2434.19055334, count=100, taker_buy_base_volume=1756.87402397, taker_buy_quote_volume=28.46694368, taker_sell_base_volume=147219.24025418, taker_sell_quote_volume=2405.72360966, ts_event=1500000000000,ts_init=1500000000000)" # noqa )
def test_from_str_given_various_valid_string_returns_expected_specification( self, value, expected): # Arrange, Act bar_type = BarType.from_str(value) # Assert assert expected == bar_type
def test_load_pickled_data(self): # Arrange bar_type = BarType( instrument_id=GBPUSD_SIM.id, bar_spec=TestDataStubs.bar_spec_1min_bid(), aggregation_source=AggregationSource.EXTERNAL, # <-- important ) config = EMACrossConfig( instrument_id=str(GBPUSD_SIM.id), bar_type=str(bar_type), trade_size=Decimal(100_000), fast_ema=10, slow_ema=20, ) strategy = EMACross(config=config) self.engine.add_strategy(strategy) data = self.engine.dump_pickled_data() # Act self.engine.load_pickled_data(data) self.engine.run() # Assert assert strategy.fast_ema.count == 30117 assert self.engine.iteration == 60234 assert self.engine.portfolio.account( self.venue).balance_total(USD) == Money(1001736.86, USD)
def from_dict(values: Dict[str, Any]) -> "BinanceBar": """ Return a `Binance` bar parsed from the given values. Parameters ---------- values : dict[str, Any] The values for initialization. Returns ------- BinanceBar """ return BinanceBar( bar_type=BarType.from_str(values["bar_type"]), open=Price.from_str(values["open"]), high=Price.from_str(values["high"]), low=Price.from_str(values["low"]), close=Price.from_str(values["close"]), volume=Quantity.from_str(values["volume"]), quote_volume=Quantity.from_str(values["quote_volume"]), count=values["count"], taker_buy_base_volume=Quantity.from_str(values["taker_buy_base_volume"]), taker_buy_quote_volume=Quantity.from_str(values["taker_buy_quote_volume"]), ts_event=values["ts_event"], ts_init=values["ts_init"], )
def test_handle_trade_tick_when_value_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.LAST) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("15000.00"), size=Quantity.from_str("3.5"), aggressor_side=AggressorSide.BUY, trade_id="123456", ts_event=0, ts_init=0, ) # Act aggregator.handle_trade_tick(tick1) # Assert assert len(bar_store.get_store()) == 0 assert aggregator.get_cumulative_value() == Decimal("52500.000")
def test_add_bars_adds_to_engine(self, capsys): # Arrange engine = BacktestEngine() bar_spec = BarSpecification( step=1, aggregation=BarAggregation.MINUTE, price_type=PriceType.BID, ) bar_type = BarType( instrument_id=USDJPY_SIM.id, bar_spec=bar_spec, aggregation_source=AggregationSource.EXTERNAL, # <-- important ) wrangler = BarDataWrangler( bar_type=bar_type, instrument=USDJPY_SIM, ) provider = TestDataProvider() bars = wrangler.process( provider.read_csv_bars("fxcm-usdjpy-m1-bid-2013.csv")[:2000]) # Act engine.add_instrument(USDJPY_SIM) engine.add_bars(data=bars) # Assert log = "".join(capsys.readouterr()) assert "Added USD/JPY.SIM Instrument." in log assert "Added 2,000 USD/JPY.SIM-1-MINUTE-BID-EXTERNAL Bar elements." in log
def test_run_trade_ticks_through_aggregator_results_in_expected_bars(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = ETHUSDT_BINANCE bar_spec = BarSpecification(1000, BarAggregation.VOLUME, PriceType.LAST) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) wrangler = TradeTickDataWrangler(instrument=ETHUSDT_BINANCE) provider = TestDataProvider() ticks = wrangler.process( provider.read_csv_ticks("binance-ethusdt-trades.csv")[:10000]) # Act for tick in ticks: aggregator.handle_trade_tick(tick) # Assert last_bar = bar_store.get_store()[-1] assert len(bar_store.get_store()) == 26 assert last_bar.open == Price.from_str("425.17") assert last_bar.high == Price.from_str("425.24") assert last_bar.low == Price.from_str("424.69") assert last_bar.close == Price.from_str("425.14") assert last_bar.volume == Quantity.from_int(1000)
def test_handle_quote_tick_when_value_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.BID) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(3000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) # Act aggregator.handle_quote_tick(tick1) # Assert assert len(bar_store.get_store()) == 0 assert aggregator.get_cumulative_value() == Decimal("3000.03000")
def test_handle_trade_tick_when_volume_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(10000, BarAggregation.VOLUME, PriceType.LAST) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) tick1 = TradeTick( instrument_id=instrument.id, price=Price.from_str("1.00001"), size=Quantity.from_int(1), aggressor_side=AggressorSide.BUY, trade_id="123456", ts_event=0, ts_init=0, ) # Act aggregator.handle_trade_tick(tick1) # Assert assert len(bar_store.get_store()) == 0
def test_run_quote_ticks_through_aggregator_results_in_expected_bars(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(1000, BarAggregation.VOLUME, PriceType.MID) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) # Setup data wrangler = QuoteTickDataWrangler(instrument) provider = TestDataProvider() ticks = wrangler.process( data=provider.read_csv_ticks("truefx-audusd-ticks.csv")[:10000], default_volume=1, ) # Act for tick in ticks: aggregator.handle_quote_tick(tick) # Assert last_bar = bar_store.get_store()[-1] assert len(bar_store.get_store()) == 10 assert last_bar.open == Price.from_str("0.670635") assert last_bar.high == Price.from_str("0.670705") assert last_bar.low == Price.from_str("0.670370") assert last_bar.close == Price.from_str("0.670655") assert last_bar.volume == Quantity.from_int(1000)
def subscribe_bars(self, bar_type: BarType): PyCondition.true(bar_type.is_externally_aggregated(), "aggregation_source is not EXTERNAL") if not bar_type.spec.is_time_aggregated(): self._log.error( f"Cannot subscribe to {bar_type}: only time bars are aggregated by Binance.", ) return if bar_type.spec.aggregation == BarAggregation.SECOND: self._log.error( f"Cannot subscribe to {bar_type}: second bars are not aggregated by Binance.", ) return if bar_type.spec.aggregation == BarAggregation.MINUTE: resolution = "m" elif bar_type.spec.aggregation == BarAggregation.HOUR: resolution = "h" elif bar_type.spec.aggregation == BarAggregation.DAY: resolution = "d" else: # pragma: no cover (design-time error) raise RuntimeError( f"invalid aggregation type, " f"was {BarAggregationParser.to_str_py(bar_type.spec.aggregation)}", ) self._ws_client.subscribe_bars( symbol=bar_type.instrument_id.symbol.value, interval=f"{bar_type.spec.step}{resolution}", ) self._add_subscription_bars(bar_type)
def test_handle_quote_tick_when_volume_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(10000, BarAggregation.VOLUME, PriceType.BID) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=instrument.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(3000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) # Act aggregator.handle_quote_tick(tick1) # Assert assert len(bar_store.get_store()) == 0
def test_update_timed_with_test_clock_sends_single_bar_to_handler(self): # Arrange clock = TestClock() bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id, bar_spec) aggregator = TimeBarAggregator( AUDUSD_SIM, bar_type, handler, TestClock(), Logger(clock), ) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) tick2 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00002"), ask=Price.from_str("1.00005"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) tick3 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00000"), ask=Price.from_str("1.00003"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=2 * 60 * 1_000_000_000, # 2 minutes in nanoseconds ts_init=2 * 60 * 1_000_000_000, # 2 minutes in nanoseconds ) # Act aggregator.handle_quote_tick(tick1) aggregator.handle_quote_tick(tick2) aggregator.handle_quote_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert Price.from_str("1.000025") == bar_store.get_store()[0].open assert Price.from_str("1.000035") == bar_store.get_store()[0].high assert Price.from_str("1.000025") == bar_store.get_store()[0].low assert Price.from_str("1.000035") == bar_store.get_store()[0].close assert Quantity.from_int(2) == bar_store.get_store()[0].volume assert 60_000_000_000 == bar_store.get_store()[0].ts_init
def test_handle_quote_tick_when_value_beyond_threshold_sends_bar_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.BID) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(20000), ask_size=Quantity.from_int(20000), ts_event=0, ts_init=0, ) tick2 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00002"), ask=Price.from_str("1.00005"), bid_size=Quantity.from_int(60000), ask_size=Quantity.from_int(20000), ts_event=0, ts_init=0, ) tick3 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00000"), ask=Price.from_str("1.00003"), bid_size=Quantity.from_int(30500), ask_size=Quantity.from_int(20000), ts_event=0, ts_init=0, ) # Act aggregator.handle_quote_tick(tick1) aggregator.handle_quote_tick(tick2) aggregator.handle_quote_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert bar_store.get_store()[0].open == Price.from_str("1.00001") assert bar_store.get_store()[0].high == Price.from_str("1.00002") assert bar_store.get_store()[0].low == Price.from_str("1.00000") assert bar_store.get_store()[0].close == Price.from_str("1.00000") assert bar_store.get_store()[0].volume == Quantity.from_str("99999") assert aggregator.get_cumulative_value() == Decimal("10501.400")
def test_handle_trade_tick_when_volume_at_threshold_sends_bar_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(10000, BarAggregation.VOLUME, PriceType.LAST) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) tick1 = TradeTick( instrument_id=instrument.id, price=Price.from_str("1.00001"), size=Quantity.from_int(3000), aggressor_side=AggressorSide.BUY, trade_id="123456", ts_event=0, ts_init=0, ) tick2 = TradeTick( instrument_id=instrument.id, price=Price.from_str("1.00002"), size=Quantity.from_int(4000), aggressor_side=AggressorSide.BUY, trade_id="123457", ts_event=0, ts_init=0, ) tick3 = TradeTick( instrument_id=instrument.id, price=Price.from_str("1.00000"), size=Quantity.from_int(3000), aggressor_side=AggressorSide.BUY, trade_id="123458", ts_event=0, ts_init=0, ) # Act aggregator.handle_trade_tick(tick1) aggregator.handle_trade_tick(tick2) aggregator.handle_trade_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert bar_store.get_store()[0].open == Price.from_str("1.00001") assert bar_store.get_store()[0].high == Price.from_str("1.00002") assert bar_store.get_store()[0].low == Price.from_str("1.00000") assert bar_store.get_store()[0].close == Price.from_str("1.00000") assert bar_store.get_store()[0].volume == Quantity.from_int(10000)
def test_handle_quote_tick_when_volume_at_threshold_sends_bar_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(10000, BarAggregation.VOLUME, PriceType.BID) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=instrument.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(3000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) tick2 = QuoteTick( instrument_id=instrument.id, bid=Price.from_str("1.00002"), ask=Price.from_str("1.00005"), bid_size=Quantity.from_int(4000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) tick3 = QuoteTick( instrument_id=instrument.id, bid=Price.from_str("1.00000"), ask=Price.from_str("1.00003"), bid_size=Quantity.from_int(3000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) # Act aggregator.handle_quote_tick(tick1) aggregator.handle_quote_tick(tick2) aggregator.handle_quote_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert bar_store.get_store()[0].open == Price.from_str("1.00001") assert bar_store.get_store()[0].high == Price.from_str("1.00002") assert bar_store.get_store()[0].low == Price.from_str("1.00000") assert bar_store.get_store()[0].close == Price.from_str("1.00000") assert bar_store.get_store()[0].volume == Quantity.from_int(10000)
def test_bar_type_hash_str_and_repr(self): # Arrange instrument_id = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_type = BarType(instrument_id, bar_spec) # Act, Assert assert isinstance(hash(bar_type), int) assert str(bar_type) == "AUD/USD.SIM-1-MINUTE-BID-EXTERNAL" assert repr(bar_type) == "BarType(AUD/USD.SIM-1-MINUTE-BID-EXTERNAL)"
def __init__(self, config: EMACrossConfig): super().__init__(config) # Configuration self.instrument_id = InstrumentId.from_str(config.instrument_id) self.bar_type = BarType.from_str(config.bar_type) self.trade_size = Decimal(config.trade_size) # Create the indicators for the strategy self.fast_ema = ExponentialMovingAverage(config.fast_ema_period) self.slow_ema = ExponentialMovingAverage(config.slow_ema_period) self.instrument: Optional[Instrument] = None # Initialized in on_start
def __init__(self, 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 test_instantiate_given_invalid_bar_spec_raises_value_error(self): # Arrange clock = TestClock() bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(100, BarAggregation.TICK, PriceType.MID) bar_type = BarType(instrument.id, bar_spec) # Act, Assert with pytest.raises(ValueError): TimeBarAggregator( instrument, bar_type, handler, clock, Logger(clock), )
def parse_bar_ws( instrument_id: InstrumentId, kline: Dict, ts_event: int, ts_init: int, ) -> BinanceBar: interval = kline["i"] resolution = interval[1] if resolution == "m": aggregation = BarAggregation.MINUTE elif resolution == "h": aggregation = BarAggregation.HOUR elif resolution == "d": aggregation = BarAggregation.DAY else: # pragma: no cover (design-time error) raise RuntimeError( f"unsupported time aggregation resolution, was {resolution}") bar_spec = BarSpecification( step=int(interval[0]), aggregation=aggregation, price_type=PriceType.LAST, ) bar_type = BarType( instrument_id=instrument_id, bar_spec=bar_spec, aggregation_source=AggregationSource.EXTERNAL, ) return BinanceBar( bar_type=bar_type, open=Price.from_str(kline["o"]), high=Price.from_str(kline["h"]), low=Price.from_str(kline["l"]), close=Price.from_str(kline["c"]), volume=Quantity.from_str(kline["v"]), quote_volume=Quantity.from_str(kline["q"]), count=kline["n"], taker_buy_base_volume=Quantity.from_str(kline["V"]), taker_buy_quote_volume=Quantity.from_str(kline["Q"]), ts_event=ts_event, ts_init=ts_init, )
def request_bars( self, bar_type: BarType, from_datetime: pd.Timestamp, to_datetime: pd.Timestamp, limit: int, correlation_id: UUID4, ): if bar_type.is_internally_aggregated(): self._log.error( f"Cannot request {bar_type}: " f"only historical bars with EXTERNAL aggregation available from Binance.", ) return if not bar_type.spec.is_time_aggregated(): self._log.error( f"Cannot request {bar_type}: only time bars are aggregated by Binance.", ) return if bar_type.spec.aggregation == BarAggregation.SECOND: self._log.error( f"Cannot request {bar_type}: second bars are not aggregated by Binance.", ) return if bar_type.spec.price_type != PriceType.LAST: self._log.error( f"Cannot request {bar_type}: " f"only historical bars for LAST price type available from Binance.", ) return self._loop.create_task( self._request_bars( bar_type=bar_type, from_datetime=from_datetime, to_datetime=to_datetime, limit=limit, correlation_id=correlation_id, ) )
def test_instantiate_with_various_bar_specs(self, bar_spec, expected): # Arrange clock = TestClock() bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_type = BarType(instrument_id, bar_spec) # Act aggregator = TimeBarAggregator( AUDUSD_SIM, bar_type, handler, clock, Logger(clock), ) # Assert assert aggregator.next_close_ns == expected
def parse_bar_ws( instrument_id: InstrumentId, data: BinanceCandlestick, ts_init: int, ) -> BinanceBar: resolution = data.i[1] if resolution == "m": aggregation = BarAggregation.MINUTE elif resolution == "h": aggregation = BarAggregation.HOUR elif resolution == "d": aggregation = BarAggregation.DAY else: # pragma: no cover (design-time error) raise RuntimeError( f"unsupported time aggregation resolution, was {resolution}") bar_spec = BarSpecification( step=int(data.i[0]), aggregation=aggregation, price_type=PriceType.LAST, ) bar_type = BarType( instrument_id=instrument_id, bar_spec=bar_spec, aggregation_source=AggregationSource.EXTERNAL, ) return BinanceBar( bar_type=bar_type, open=Price.from_str(data.o), high=Price.from_str(data.h), low=Price.from_str(data.l), close=Price.from_str(data.c), volume=Quantity.from_str(data.v), quote_volume=Quantity.from_str(data.q), count=data.n, taker_buy_base_volume=Quantity.from_str(data.V), taker_buy_quote_volume=Quantity.from_str(data.Q), ts_event=millis_to_nanos(data.T), ts_init=ts_init, )
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 test_binance_bar_to_from_dict(self): # Arrange bar = BinanceBar( bar_type=BarType( instrument_id=TestStubs.btcusdt_binance_id(), bar_spec=TestStubs.bar_spec_1min_last(), ), open=Price.from_str("0.01634790"), high=Price.from_str("0.80000000"), low=Price.from_str("0.01575800"), close=Price.from_str("0.01577100"), volume=Quantity.from_str("148976.11427815"), quote_volume=Quantity.from_str("2434.19055334"), count=100, taker_buy_base_volume=Quantity.from_str("1756.87402397"), taker_buy_quote_volume=Quantity.from_str("28.46694368"), ts_event=1500000000000, ts_init=1500000000000, ) # Act values = bar.to_dict(bar) # Assert BinanceBar.from_dict(values) assert values == { "type": "BinanceBar", "bar_type": "BTC/USDT.BINANCE-1-MINUTE-LAST-EXTERNAL", "open": "0.01634790", "high": "0.80000000", "low": "0.01575800", "close": "0.01577100", "volume": "148976.11427815", "quote_volume": "2434.19055334", "count": 100, "taker_buy_base_volume": "1756.87402397", "taker_buy_quote_volume": "28.46694368", "ts_event": 1500000000000, "ts_init": 1500000000000, }
def test_run_ema_cross_with_minute_trade_bars(self): # Arrange wrangler = BarDataWrangler( bar_type=BarType.from_str( "BTCUSDT.BINANCE-1-MINUTE-LAST-EXTERNAL"), instrument=self.btcusdt, ) provider = TestDataProvider() # Build externally aggregated bars bars = wrangler.process(data=provider.read_csv_bars( "ftx-btc-perp-20211231-20220201_1m.csv")[:10000], ) self.engine.add_bars(bars) config = EMACrossConfig( instrument_id=str(self.btcusdt.id), bar_type="BTCUSDT.BINANCE-1-MINUTE-LAST-EXTERNAL", trade_size=Decimal(0.001), fast_ema=10, slow_ema=20, ) strategy = EMACross(config=config) self.engine.add_strategy(strategy) # Act self.engine.run() # Assert assert strategy.fast_ema.count == 10000 assert self.engine.iteration == 10000 btc_ending_balance = self.engine.portfolio.account( self.venue).balance_total(BTC) usdt_ending_balance = self.engine.portfolio.account( self.venue).balance_total(USDT) assert btc_ending_balance == Money(9.57200000, BTC) assert usdt_ending_balance == Money(10017114.27716700, USDT)