def test_static_universe(assets, dt, expected): """ Checks that the StaticUniverse correctly returns the list of assets for a particular datetime. """ universe = StaticUniverse(assets) assert universe.get_assets(dt) == expected
def test_pcm_fixed_weight_optimiser_fixed_alpha_weights_call_end_to_end( helpers): """ Tests the full portfolio base class logic for carrying out rebalancing. TODO: DataHandler is mocked. A non-disk based data source should be utilised instead. """ first_dt = pd.Timestamp('2019-01-01 15:00:00', tz=pytz.utc) asset_list = ['EQ:SPY', 'EQ:AGG', 'EQ:TLT', 'EQ:GLD'] initial_funds = 1e6 account_id = '1234' port_id = '1234' cash_buffer_perc = 0.05 exchange = SimulatedExchange(first_dt) universe = StaticUniverse(asset_list) mock_asset_prices_first = { 'EQ:SPY': 56.87, 'EQ:AGG': 219.45, 'EQ:TLT': 178.33, 'EQ:GLD': 534.21 } data_handler = Mock() data_handler.get_asset_latest_ask_price.side_effect = \ lambda self, x: mock_asset_prices_first[x] broker = SimulatedBroker(first_dt, exchange, data_handler, account_id, initial_funds=initial_funds) broker.create_portfolio(port_id, 'Portfolio') broker.subscribe_funds_to_portfolio(port_id, initial_funds) order_sizer = DollarWeightedCashBufferedOrderSizeGeneration( broker, port_id, data_handler, cash_buffer_perc) optimiser = FixedWeightPortfolioOptimiser(data_handler) alpha_weights = { 'EQ:SPY': 0.345, 'EQ:AGG': 0.611, 'EQ:TLT': 0.870, 'EQ:GLD': 0.0765 } alpha_model = FixedSignalsAlphaModel(alpha_weights) pcm = PortfolioConstructionModel(broker, port_id, universe, order_sizer, optimiser, alpha_model) result_first = pcm(first_dt) expected_first = [ Order(first_dt, 'EQ:AGG', 1390), Order(first_dt, 'EQ:GLD', 71), Order(first_dt, 'EQ:SPY', 3029), Order(first_dt, 'EQ:TLT', 2436) ] helpers.assert_order_lists_equal(result_first, expected_first)
def test_backtest_sixty_forty(etf_filepath): """ Ensures that a full end-to-end weekly rebalanced backtested trading session with fixed proportion weights produces the correct rebalance orders as well as correctly calculated market values after a single month's worth of daily backtesting. """ os.environ['QSTRADER_CSV_DATA_DIR'] = etf_filepath assets = ['EQ:ABC', 'EQ:DEF'] universe = StaticUniverse(assets) signal_weights = {'EQ:ABC': 0.6, 'EQ:DEF': 0.4} alpha_model = FixedSignalsAlphaModel(signal_weights) start_dt = pd.Timestamp('2019-01-01 00:00:00', tz=pytz.UTC) end_dt = pd.Timestamp('2019-01-31 23:59:00', tz=pytz.UTC) backtest = BacktestTradingSession( start_dt, end_dt, universe, alpha_model, portfolio_id='000001', rebalance='weekly', rebalance_weekday='WED', long_only=True, cash_buffer_percentage=0.05 ) backtest.run(results=False) portfolio = backtest.broker.portfolios['000001'] portfolio_dict = portfolio.portfolio_to_dict() expected_dict = { 'EQ:ABC': { 'unrealised_pnl': -31121.26203538094, 'realised_pnl': 0.0, 'total_pnl': -31121.26203538094, 'market_value': 561680.8382534103, 'quantity': 4674 }, 'EQ:DEF': { 'unrealised_pnl': 18047.831359406424, 'realised_pnl': 613.3956570402925, 'total_pnl': 18661.227016446715, 'market_value': 376203.80367208034, 'quantity': 1431.0 } } history_df = portfolio.history_to_df().reset_index() expected_df = pd.read_csv(os.path.join(etf_filepath, 'sixty_forty_history.dat')) pd.testing.assert_frame_equal(history_df, expected_df) assert portfolio_dict == expected_dict
def test_backtest_long_short_leveraged(etf_filepath): """ Ensures that a full end-to-end daily rebalanced backtested trading session of a leveraged long short portfolio with fixed proportion weights produces the correct rebalance orders as well as correctly calculated market values after a single month's worth of daily backtesting. """ os.environ['QSTRADER_CSV_DATA_DIR'] = etf_filepath assets = ['EQ:ABC', 'EQ:DEF'] universe = StaticUniverse(assets) signal_weights = {'EQ:ABC': 1.0, 'EQ:DEF': -0.7} alpha_model = FixedSignalsAlphaModel(signal_weights) start_dt = pd.Timestamp('2019-01-01 00:00:00', tz=pytz.UTC) end_dt = pd.Timestamp('2019-01-31 23:59:00', tz=pytz.UTC) backtest = BacktestTradingSession( start_dt, end_dt, universe, alpha_model, portfolio_id='000001', rebalance='daily', long_only=False, gross_leverage=2.0 ) backtest.run(results=False) portfolio = backtest.broker.portfolios['000001'] portfolio_dict = portfolio.portfolio_to_dict() expected_dict = { 'EQ:ABC': { 'unrealised_pnl': -48302.832839363175, 'realised_pnl': -3930.9847615026706, 'total_pnl': -52233.81760086585, 'market_value': 1055344.698660986, 'quantity': 8782.0 }, 'EQ:DEF': { 'unrealised_pnl': -42274.737165376326, 'realised_pnl': -9972.897320721153, 'total_pnl': -52247.63448609748, 'market_value': -742417.5692312752, 'quantity': -2824.0 } } history_df = portfolio.history_to_df().reset_index() expected_df = pd.read_csv(os.path.join(etf_filepath, 'long_short_history.dat')) pd.testing.assert_frame_equal(history_df, expected_df) assert portfolio_dict == expected_dict
def test_backtest_sixty_forty_no_corp_actions(etf_filepath): """ Ensures that a full end-to-end weekly rebalanced backtested trading session with fixed proportion weights produces the correct rebalance orders as well as correctly calculated market values after a single month's worth of daily backtesting. """ os.environ['QSTRADER_CSV_DATA_DIR'] = etf_filepath assets = ['EQ:ABC', 'EQ:DEF'] universe = StaticUniverse(assets) signal_weights = {'EQ:ABC': 0.6, 'EQ:DEF': 0.4} alpha_model = FixedSignalsAlphaModel(signal_weights) start_dt = pd.Timestamp('2019-01-01 00:00:00', tz=pytz.UTC) end_dt = pd.Timestamp('2019-01-31 23:59:00', tz=pytz.UTC) backtest = BacktestTradingSession(start_dt, end_dt, universe, alpha_model, portfolio_id='000001', rebalance='weekly', rebalance_weekday='WED', cash_buffer_percentage=0.05) backtest.run(results=False) portfolio = backtest.broker.portfolios['000001'] portfolio_dict = portfolio.portfolio_to_dict() expected_dict = { 'EQ:ABC': { 'book_cost': 592802.1002887912, 'gain': -31121.262035380933, 'market_value': 561680.8382534103, 'perc_gain': -5.249856911812527, 'quantity': 4674.0 }, 'EQ:DEF': { 'book_cost': 358155.9723126739, 'gain': 18047.83135940641, 'market_value': 376203.80367208034, 'perc_gain': 5.03909825735098, 'quantity': 1431.0 } } history_df = portfolio.history_to_df().reset_index() expected_df = pd.read_csv(os.path.join(etf_filepath, 'history.dat')) pd.testing.assert_frame_equal(history_df, expected_df) assert portfolio_dict == expected_dict
from qstrader.asset.equity import Equity from qstrader.asset.universe.static import StaticUniverse from qstrader.data.backtest_data_handler import BacktestDataHandler from qstrader.data.daily_bar_csv import CSVDailyBarDataSource from qstrader.statistics.tearsheet import TearsheetStatistics from qstrader.trading.backtest import BacktestTradingSession if __name__ == "__main__": start_dt = pd.Timestamp('2003-09-30 14:30:00', tz=pytz.UTC) end_dt = pd.Timestamp('2019-12-31 23:59:00', tz=pytz.UTC) # Construct the symbols and assets necessary for the backtest strategy_symbols = ['SPY', 'AGG'] strategy_assets = ['EQ:%s' % symbol for symbol in strategy_symbols] strategy_universe = StaticUniverse(strategy_assets) # To avoid loading all CSV files in the directory, set the # data source to load only those provided symbols csv_dir = os.environ.get('QSTRADER_CSV_DATA_DIR') data_source = CSVDailyBarDataSource(csv_dir, Equity, csv_symbols=strategy_symbols) data_handler = BacktestDataHandler(strategy_universe, data_sources=[data_source]) # Construct an Alpha Model that simply provides # static allocations to a universe of assets # In this case 60% SPY ETF, 40% AGG ETF, # rebalanced at the end of each month strategy_alpha_model = FixedSignalsAlphaModel({'EQ:SPY': 0.6, 'EQ:AGG': 0.4}) strategy_backtest = BacktestTradingSession( start_dt, end_dt,
from qstrader.alpha_model.fixed_signals import FixedSignalsAlphaModel from qstrader.asset.equity import Equity from qstrader.asset.universe.static import StaticUniverse from qstrader.data.backtest_data_handler import BacktestDataHandler from qstrader.data.daily_bar_csv import CSVDailyBarDataSource from qstrader.statistics.tearsheet import TearsheetStatistics from qstrader.trading.backtest import BacktestTradingSession if __name__ == "__main__": start_dt = pd.Timestamp('2004-11-19 14:30:00', tz=pytz.UTC) end_dt = pd.Timestamp('2019-12-31 23:59:00', tz=pytz.UTC) # Construct the symbol and asset necessary for the backtest strategy_symbols = ['GLD'] strategy_assets = ['EQ:GLD'] strategy_universe = StaticUniverse(strategy_assets) # To avoid loading all CSV files in the directory, set the # data source to load only those provided symbols csv_dir = os.environ.get('QSTRADER_CSV_DATA_DIR') data_source = CSVDailyBarDataSource(csv_dir, Equity, csv_symbols=strategy_symbols) data_handler = BacktestDataHandler(strategy_universe, data_sources=[data_source]) # Construct an Alpha Model that simply provides a fixed # signal for the single GLD ETF at 100% allocation # with a backtest that does not rebalance strategy_alpha_model = FixedSignalsAlphaModel({'EQ:GLD': 1.0}) strategy_backtest = BacktestTradingSession(start_dt,
def cli(start_date, end_date, allocations, strat_title, strat_id, tearsheet): csv_dir = os.environ.get('QSTRADER_CSV_DATA_DIR', '.') start_dt = pd.Timestamp('%s 00:00:00' % start_date, tz=pytz.UTC) if end_date is None: # Use yesterday's date yesterday = (datetime.now() - timedelta(1)).strftime('%Y-%m-%d') end_dt = pd.Timestamp('%s 23:59:00' % yesterday, tz=pytz.UTC) else: end_dt = pd.Timestamp('%s 23:59:00' % end_date, tz=pytz.UTC) alloc_dict = obtain_allocations(allocations) # Assets and Data Handling strategy_assets = list(alloc_dict.keys()) strategy_symbols = [ symbol.replace('EQ:', '') for symbol in strategy_assets ] strategy_universe = StaticUniverse(strategy_assets) strategy_data_source = CSVDailyBarDataSource(csv_dir, Equity, csv_symbols=strategy_symbols) strategy_data_handler = BacktestDataHandler( strategy_universe, data_sources=[strategy_data_source]) strategy_assets = alloc_dict.keys() strategy_alpha_model = FixedSignalsAlphaModel(alloc_dict) strategy_backtest = BacktestTradingSession( start_dt, end_dt, strategy_universe, strategy_alpha_model, rebalance='end_of_month', account_name=strat_title, portfolio_id='STATIC001', portfolio_name=strat_title, long_only=True, cash_buffer_percentage=0.01, data_handler=strategy_data_handler) strategy_backtest.run() # Benchmark: 60/40 US Equities/Bonds benchmark_symbols = ['SPY', 'AGG'] benchmark_assets = ['EQ:SPY', 'EQ:AGG'] benchmark_universe = StaticUniverse(benchmark_assets) benchmark_data_source = CSVDailyBarDataSource( csv_dir, Equity, csv_symbols=benchmark_symbols) benchmark_data_handler = BacktestDataHandler( benchmark_universe, data_sources=[benchmark_data_source]) benchmark_signal_weights = {'EQ:SPY': 0.6, 'EQ:AGG': 0.4} benchmark_title = '60/40 US Equities/Bonds' benchmark_alpha_model = FixedSignalsAlphaModel(benchmark_signal_weights) benchmark_backtest = BacktestTradingSession( start_dt, end_dt, benchmark_universe, benchmark_alpha_model, rebalance='end_of_month', account_name='60/40 US Equities/Bonds', portfolio_id='6040EQBD', portfolio_name=benchmark_title, long_only=True, cash_buffer_percentage=0.01, data_handler=benchmark_data_handler) benchmark_backtest.run() output_filename = ('%s_monthly.json' % strat_id).replace('-', '_') stats = JSONStatistics( equity_curve=strategy_backtest.get_equity_curve(), target_allocations=strategy_backtest.get_target_allocations(), strategy_id=strat_id, strategy_name=strat_title, benchmark_curve=benchmark_backtest.get_equity_curve(), benchmark_id='6040-us-equitiesbonds', benchmark_name=benchmark_title, output_filename=output_filename) stats.to_file() if tearsheet: tearsheet = TearsheetStatistics( strategy_equity=strategy_backtest.get_equity_curve(), benchmark_equity=benchmark_backtest.get_equity_curve(), title=strat_title) tearsheet.plot_results()
start_dt, end_dt, strategy_universe, strategy_alpha_model, signals=signals, rebalance='end_of_month', long_only=True, cash_buffer_percentage=0.01, burn_in_dt=burn_in_dt, data_handler=strategy_data_handler) strategy_backtest.run() # Construct benchmark assets (buy & hold SPY) benchmark_symbols = ['SPY'] benchmark_assets = ['EQ:SPY'] benchmark_universe = StaticUniverse(benchmark_assets) benchmark_data_source = CSVDailyBarDataSource( csv_dir, Equity, csv_symbols=benchmark_symbols) benchmark_data_handler = BacktestDataHandler( benchmark_universe, data_sources=[benchmark_data_source]) # Construct a benchmark Alpha Model that provides # 100% static allocation to the SPY ETF, with no rebalance benchmark_alpha_model = FixedSignalsAlphaModel({'EQ:SPY': 1.0}) benchmark_backtest = BacktestTradingSession( burn_in_dt, end_dt, benchmark_universe, benchmark_alpha_model, rebalance='buy_and_hold', long_only=True,