def test_exit_action_bytradename(mocker): with MockCalc(mocker): start_date = date(2021, 12, 6) end_date = date(2021, 12, 10) # Define trade irswap1 = IRSwap(PayReceive.Receive, '10y', Currency.USD, notional_amount=1e5, name='swap1') irswap2 = IRSwap(PayReceive.Pay, '5y', Currency.USD, notional_amount=1e5, name='swap2') trig_req_add = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b') trig_req_exit = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='2b') actions_add = AddTradeAction([irswap1, irswap2]) actions_exit = ExitTradeAction('swap1') triggers = [ PeriodicTrigger(trig_req_add, actions_add), PeriodicTrigger(trig_req_exit, actions_exit) ] strategy = Strategy(None, triggers) # run backtest daily engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) backtest = engine.run_backtest(strategy, states=[ date(2021, 12, 6), date(2021, 12, 7), date(2021, 12, 8), date(2021, 12, 9), date(2021, 12, 10) ], end=end_date, show_progress=True) trade_ledger = backtest.trade_ledger().to_dict('index') assert trade_ledger['Action1_swap1_2021-12-06']['Open'] == date( 2021, 12, 6) assert trade_ledger['Action1_swap1_2021-12-06']['Close'] == date( 2021, 12, 6) assert trade_ledger['Action1_swap1_2021-12-07']['Open'] == date( 2021, 12, 7) assert trade_ledger['Action1_swap1_2021-12-07']['Close'] == date( 2021, 12, 8) assert trade_ledger['Action1_swap2_2021-12-06']['Status'] == 'open' assert trade_ledger['Action1_swap2_2021-12-07']['Status'] == 'open' assert trade_ledger['Action1_swap2_2021-12-10']['Status'] == 'open'
def test_eq_vol_engine_result(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption('.STOXX50E', expirationDate='3m', strikePrice='ATM', optionType=OptionType.Call, optionStyle=OptionStyle.European) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='B'), actions=HedgeAction(EqDelta, priceables=option, trade_duration='B')) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() backtest_result = EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert response df = pd.DataFrame(api_mock_data().risks[FlowVolBacktestMeasure.PNL.value]) df.date = pd.to_datetime(df.date) expected_pnl = df.set_index('date').value assert expected_pnl.equals(backtest_result.get_measure_series(FlowVolBacktestMeasure.PNL))
def test_engine_mapping_trade_quantity_nav(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption('.STOXX50E', expirationDate='3m', strikePrice='ATM', optionType=OptionType.Call, optionStyle=OptionStyle.European) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', trade_quantity=12345, trade_quantity_type=BacktestTradingQuantityType.NAV) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='B'), actions=HedgeAction(EqDelta, priceables=option, trade_duration='B')) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest_parameter_args = { 'trading_parameters': BacktestTradingParameters( quantity=12345, quantity_type=BacktestTradingQuantityType.NAV.value, trade_in_method=TradeInMethod.FixedRoll.value, roll_frequency='1m'), 'underliers': [BacktestStrategyUnderlier( instrument=option, notional_percentage=100, hedge=BacktestStrategyUnderlierHedge(risk_details=DeltaHedgeParameters(frequency='Daily')), market_model='SFK') ], 'index_initial_value': 12345, "measures": [FlowVolBacktestMeasure.ALL_MEASURES] } backtest_parameters = VolatilityFlowBacktestParameters.from_dict(backtest_parameter_args) backtest = Backtest(name="Flow Vol Backtest", mq_symbol="Flow Vol Backtest", parameters=backtest_parameters, start_date=start_date, end_date=end_date, type='Volatility Flow', asset_class=AssetClass.Equity, currency=Currency.USD, cost_netting=False) mocker.assert_called_with(backtest, None)
def test_hedge_action_risk_trigger(mocker): with MockCalc(mocker): start_date = date(2021, 12, 1) # end_date = date(2021, 12, 3) # Define trade call = FXOption(buy_sell='Buy', option_type='Call', pair='USDJPY', strike_price='ATMF', notional_amount=1e5, expiration_date='2y', name='2y_call') hedge_risk = FXDelta(aggregation_level='Type') fwd_hedge = FXForward(pair='USDJPY', settlement_date='2y', notional_amount=1e5, name='2y_forward') trig_req = RiskTriggerRequirements(risk=hedge_risk, trigger_level=0, direction=TriggerDirection.ABOVE) action_hedge = HedgeAction(hedge_risk, fwd_hedge, '2b') triggers = StrategyRiskTrigger(trig_req, action_hedge) with PricingContext(pricing_date=start_date): fut = call.resolve(in_place=False) call = fut.result() strategy = Strategy(call, triggers) # run backtest daily engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) backtest = engine.run_backtest( strategy, states=[date(2021, 12, 1), date(2021, 12, 2), date(2021, 12, 3)], show_progress=True) summary = backtest.result_summary assert len(summary) == 3 assert round(summary[hedge_risk].sum()) == 0 assert round(summary['Cumulative Cash'].sum()) == -7090 assert Price in summary.columns
def test_mkt_trigger_data_sources(mocker): with MockCalc(mocker): s = pd.Series({ date(2021, 10, 1): 0.984274, date(2021, 10, 4): 1.000706, date(2021, 10, 5): 1.044055, date(2021, 10, 6): 1.095361, date(2021, 10, 7): 1.129336, date(2021, 10, 8): 1.182954, date(2021, 10, 12): 1.200108, date(2021, 10, 13): 1.220607, date(2021, 10, 14): 1.172837, date(2021, 10, 15): 1.163660, date(2021, 10, 18): 1.061084, date(2021, 10, 19): 1.025012, date(2021, 10, 20): 1.018035, date(2021, 10, 21): 1.080751, date(2021, 10, 22): 1.069340, date(2021, 10, 25): 1.033413 }) action = AddTradeAction( IRSwaption(notional_currency='USD', expiration_date='1y', termination_date='1y'), 'expiration_date') data_source = GenericDataSource(s, MissingDataStrategy.fill_forward) mkt_trigger = MktTrigger( MktTriggerRequirements(data_source, 1.1, TriggerDirection.ABOVE), action) strategy = Strategy(None, mkt_trigger) engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=date(2021, 10, 1), end=date(2021, 10, 25), frequency='1b', # show_progress=True) backtest = engine.run_backtest(strategy, states=s.index, show_progress=True) summary = backtest.result_summary ledger = backtest.trade_ledger() assert len(summary) == 12 assert len(ledger) == 6 assert round(summary[Price].sum()) == 25163614 assert round(summary['Cumulative Cash'].sum()) == -2153015
def test_backtest_predefined_timezone_aware(): tz = 'Europe/London' start_dt = '2021-01-01T08:00' end_dt = '2021-12-31T17:00' states = pd.bdate_range(start_dt, end_dt, freq='1H', tz=tz).to_series().between_time( '08:00', '17:00').index.tolist() trigger_dates = pd.bdate_range( start_dt, end_dt, freq='1H', tz=tz).to_series().at_time('17:00').index.tolist() data = np.random.randn(len(states)) s_rt = pd.Series(index=states, data=data) s_eod = s_rt.at_time('17:00') s_eod.index = s_eod.index.date generic_bond_future = IRBondFuture(currency='EUR') add_trade_action = AddTradeAction(generic_bond_future) simple_date_trigger_requirement = DateTriggerRequirements( dates=trigger_dates) simple_date_trigger = DateTrigger( trigger_requirements=simple_date_trigger_requirement, actions=[add_trade_action]) data_manager = DataManager() data_manager.add_data_source(pd.Series(index=states, data=data), DataFrequency.REAL_TIME, generic_bond_future, ValuationFixingType.PRICE) data_manager.add_data_source(s_eod, DataFrequency.DAILY, generic_bond_future, ValuationFixingType.PRICE) # instantiate a new strategy strategy = Strategy(None, triggers=simple_date_trigger) engine = PredefinedAssetEngine(data_mgr=data_manager, tz=timezone(tz), calendars='Weekend') backtest = engine.run_backtest(strategy=strategy, start=states[0], end=states[-1], states=states) assert len(backtest.trade_ledger()) == 364
def test_generic_engine_simple(mocker): with MockCalc(mocker): start_date = date(2021, 12, 1) # end_date = date(2021, 12, 3) # Define trade call = FXOption(buy_sell='Buy', option_type='Call', pair='USDJPY', strike_price='ATMF', notional_amount=1e5, expiration_date='2y', name='2y_call') # Periodic trigger: based on frequency freq = '1m' # trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq) trig_req = DateTriggerRequirements(dates=[start_date]) actions = AddTradeAction(call, freq) # starting with empty portfolio (first arg to Strategy), apply actions on trig_req triggers = DateTrigger(trig_req, actions) strategy = Strategy(None, triggers) # run backtest daily engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) backtest = engine.run_backtest( strategy, states=[date(2021, 12, 1), date(2021, 12, 2), date(2021, 12, 3)], show_progress=True) summary = backtest.result_summary assert len(summary) == 3 assert round(summary[Price].sum()) == 2424 assert round(summary['Cumulative Cash'].sum()) == 0
def test_backtest_predefined(): # Test simple MOC order trigger = ExampleTestTrigger() strategy = Strategy(initial_portfolio=None, triggers=[trigger]) start = dt.date(2021, 1, 4) mid = dt.date(2021, 1, 5) end = dt.date(2021, 1, 6) # these mocks are needed as the date functions need a GSSession gs_quant.backtests.predefined_asset_engine.is_business_day = mock.Mock( return_value=True) gs_quant.backtests.predefined_asset_engine.business_day_offset = mock.Mock( return_value=mid) data_mgr = DataManager() underlying = Security(ric='TestRic') close_prices = pd.Series(dtype=float) close_prices[start] = 1 close_prices[mid] = 1.5 close_prices[end] = 2 data_mgr.add_data_source(close_prices, DataFrequency.DAILY, underlying, ValuationFixingType.PRICE) engine = PredefinedAssetEngine(data_mgr=data_mgr) backtest = engine.run_backtest(strategy, start=start, end=end) perf = backtest.performance holdings = backtest.historical_holdings cash_asset = backtest.cash_asset # 100 on the initial date assert perf[start] == 100 # 100 on the next day as we traded MOC assert perf[mid] == 100 assert holdings[mid][cash_asset] == 100 - 1.5 assert holdings[mid][underlying] == 1 # 100.5 = 98.5 (cash) + 2 ( test asset) assert holdings[end][cash_asset] == 100 - 1.5 assert holdings[end][underlying] == 1 assert perf[end] == 100.5 # Test TWAP orders with no ON positions twap_entry_mid = 16 twap_exit_mid = 25 twap_entry_end = 30 twap_exit_end = 40 data_twap = { dt.datetime.combine(mid, dt.time(10, 30)): twap_entry_mid, dt.datetime.combine(mid, dt.time(14, 30)): twap_exit_mid, dt.datetime.combine(end, dt.time(10, 30)): twap_entry_end, dt.datetime.combine(end, dt.time(14, 30)): twap_exit_end } data_mgr.add_data_source(pd.Series(data_twap), DataFrequency.REAL_TIME, underlying, ValuationFixingType.PRICE) trigger = FuturesExample() strategy = Strategy(initial_portfolio=None, triggers=[trigger]) engine = PredefinedAssetEngine(data_mgr=data_mgr, tz=timezone('Europe/London')) backtest = engine.run_backtest(strategy, start=start, end=end) perf = backtest.performance holdings = backtest.historical_holdings weights = backtest.historical_weights cash_asset = backtest.cash_asset # start: 100 assert perf[start] == 100 # mid: 100 + (twap_exit - twap_entry) assert perf[mid] == 100 - twap_entry_mid + twap_exit_mid assert holdings[mid][cash_asset] == perf[mid] assert underlying not in holdings[mid] assert weights[mid][cash_asset] == 1 assert underlying not in weights[mid] # 100.5 = 98.5 (cash) + 2 ( test asset) assert perf[ end] == 100 - twap_entry_mid + twap_exit_mid - twap_entry_end + twap_exit_end assert holdings[end][cash_asset] == perf[end] assert underlying not in holdings[end] assert weights[end][cash_asset] == 1 assert underlying not in weights[end]
def test_supports_strategy(): # 1. Valid strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption('.STOXX50E', expirationDate='3m', strikePrice='ATM', optionType=OptionType.Call, optionStyle=OptionStyle.European) action = AddTradesQuantityScaledAction(priceables=option, trade_duration='1m') trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m'), actions=action) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='B'), actions=HedgeAction(EqDelta, priceables=option, trade_duration='B')) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert EquityVolEngine.supports_strategy(strategy) # 2. Invalid - no trade action trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m'), actions=None) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 3. Invalid - no trade quantity action = AddTradesQuantityScaledAction(priceables=option, trade_duration='1m', trade_quantity=None) trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m'), actions=action) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 4. Invalid - no trade quantity type action = AddTradesQuantityScaledAction(priceables=option, trade_duration='1m', trade_quantity_type=None) trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m'), actions=action) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 5. Invalid - mismatch trade duration and trigger period action = AddTradesQuantityScaledAction(priceables=option, trade_duration='2m', trade_quantity_type=None) trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m'), actions=action) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 6. Invalid - mismatch hedge trade duration and trigger period action = AddTradesQuantityScaledAction(priceables=option, trade_duration='1m', trade_quantity_type=None) trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m'), actions=action) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='B'), actions=HedgeAction(EqDelta, priceables=option, trade_duration='M')) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 6. Invalid - non-daily hedge trade action = AddTradesQuantityScaledAction(priceables=option, trade_duration='1m', trade_quantity_type=None) trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m'), actions=action) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='M'), actions=HedgeAction(EqDelta, priceables=option, trade_duration='M')) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert not EquityVolEngine.supports_strategy(strategy)
def test_engine_mapping_with_signals(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 27) option = EqOption('.STOXX50E', expirationDate='3m', strikePrice='ATM', optionType=OptionType.Call, optionStyle=OptionStyle.European) entry_action = EnterPositionQuantityScaledAction( priceables=option, trade_duration='1m', trade_quantity=12345, trade_quantity_type=BacktestTradingQuantityType.notional) entry_signal_series = pd.Series(data={dt.date(2019, 2, 19): 1}) entry_dates = entry_signal_series[entry_signal_series > 0].keys() entry_trigger = AggregateTrigger(triggers=[ DateTrigger(trigger_requirements=DateTriggerRequirements( dates=entry_dates), actions=entry_action), PortfolioTrigger(trigger_requirements=PortfolioTriggerRequirements( 'len', 0, TriggerDirection.EQUAL)) ]) exit_signal_series = pd.Series(data={dt.date(2019, 2, 20): 1}) exit_dates = exit_signal_series[exit_signal_series > 0].keys() exit_trigger = AggregateTrigger(triggers=[ DateTrigger(trigger_requirements=DateTriggerRequirements( dates=exit_dates), actions=ExitPositionAction()), PortfolioTrigger(trigger_requirements=PortfolioTriggerRequirements( 'len', 0, TriggerDirection.ABOVE)) ]) strategy = Strategy(initial_portfolio=None, triggers=[entry_trigger, exit_trigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest_parameter_args = { 'trading_parameters': BacktestTradingParameters( quantity=12345, quantity_type=BacktestTradingQuantityType.notional.value, trade_in_method=TradeInMethod.FixedRoll.value, roll_frequency='1m', trade_in_signals=list( map(lambda x: BacktestSignalSeriesItem(x[0], int(x[1])), zip(entry_signal_series.index, entry_signal_series.values))), trade_out_signals=list( map(lambda x: BacktestSignalSeriesItem(x[0], int(x[1])), zip(exit_signal_series.index, exit_signal_series.values)))), 'underliers': [ BacktestStrategyUnderlier(instrument=option, notional_percentage=100, hedge=BacktestStrategyUnderlierHedge(), market_model='SFK', expiry_date_mode='otc') ], 'index_initial_value': 0.0, "measures": [FlowVolBacktestMeasure.ALL_MEASURES] } backtest_parameters = VolatilityFlowBacktestParameters.from_dict( backtest_parameter_args) backtest = Backtest(name="Flow Vol Backtest", mq_symbol="Flow Vol Backtest", parameters=backtest_parameters, start_date=start_date, end_date=end_date, type='Volatility Flow', asset_class=AssetClass.Equity, currency=Currency.USD, cost_netting=False) mocker.assert_called_with(backtest, None)