Skip to content

Commit

Permalink
Added roll costs in backtest
Browse files Browse the repository at this point in the history
  • Loading branch information
saeedamen committed Jul 23, 2021
1 parent ce4bb45 commit 59e340e
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 12 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ In finmarketpy/examples you will find several examples, including some simple tr

# finmarketpy log

* 23 Jul 2021
* Added roll costs in backtest
* 30 May 2021
* Added S3 Jupyter notebook for use with findatapy
* 21 May 2021
Expand Down
9 changes: 6 additions & 3 deletions finmarketpy/backtest/backtestengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ def calculate_trading_PnL(self, br, asset_a_df, signal_df, contract_value_df, ru
signal_df = signal_df.mask(non_trading_days) # fill asset holidays with NaN signals
signal_df = signal_df.fillna(method='ffill') # fill these down

# Transaction costs and roll costs
tc = br.spot_tc_bp
rc = br.spot_rc_bp

signal_cols = signal_df.columns.values
asset_df_cols = asset_df.columns.values
Expand Down Expand Up @@ -245,7 +247,7 @@ def calculate_trading_PnL(self, br, asset_a_df, signal_df, contract_value_df, ru
= self.calculate_exposures(portfolio_signal)

# Calculate final portfolio returns with the amended portfolio leverage (by default just 1s)
portfolio = calculations.calculate_signal_returns_with_tc_matrix(portfolio_leverage_df, portfolio, tc=tc)
portfolio = calculations.calculate_signal_returns_with_tc_matrix(portfolio_leverage_df, portfolio, tc=tc, rc=rc)

# Assign all the property variables
# Trim them if we have asked for a different plot start/finish
Expand Down Expand Up @@ -284,7 +286,7 @@ def calculate_trading_PnL(self, br, asset_a_df, signal_df, contract_value_df, ru

# P&L components of individual assets after all the portfolio level risk signals and position limits have been applied
self._components_pnl = self._filter_by_plot_start_finish_date(calculations.calculate_signal_returns_with_tc_matrix(portfolio_signal_before_weighting,
returns_df, tc=tc), br)
returns_df, tc=tc, rc=rc), br)
self._components_pnl.columns = pnl_cols

# TODO FIX very slow - hence only calculate on demand
Expand Down Expand Up @@ -1997,6 +1999,7 @@ def optimize_portfolio_weights(self, returns_df, signal_df, signal_pnl_cols, br=
br = self._br

tc = br.spot_tc_bp
rc = br.spot_rc_bp

individual_leverage_df = None

Expand All @@ -2015,7 +2018,7 @@ def optimize_portfolio_weights(self, returns_df, signal_df, signal_pnl_cols, br=

individual_leverage_df = leverage_df # Contains leverage of individual signal (before portfolio vol target)

signal_pnl = self._calculations.calculate_signal_returns_with_tc_matrix(signal_df, returns_df, tc=tc)
signal_pnl = self._calculations.calculate_signal_returns_with_tc_matrix(signal_df, returns_df, tc=tc, rc=rc)
signal_pnl.columns = signal_pnl_cols

adjusted_weights_matrix = None
Expand Down
40 changes: 31 additions & 9 deletions finmarketpy/backtest/backtestrequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self):

self.__signal_name = None

# output parameters for backtest (should we add returns statistics on legends, write CSVs with returns etc.)
# Output parameters for backtest (should we add returns statistics on legends, write CSVs with returns etc.)
self.__plot_start = None
self.__plot_finish = None
self.__calc_stats = True
Expand All @@ -46,7 +46,7 @@ def __init__(self):

self.__portfolio_weight_construction = None

# default parameters for portfolio level vol adjustment
# Default parameters for portfolio level vol adjustment
self.__portfolio_vol_adjust = False
self.__portfolio_vol_period_shift = 0
self.__portfolio_vol_rebalance_freq = None
Expand All @@ -57,7 +57,7 @@ def __init__(self):
self.__portfolio_vol_periods = 20
self.__portfolio_vol_obs_in_year = 252

# default parameters for signal level vol adjustment
# Default parameters for signal level vol adjustment
self.__signal_vol_adjust = False
self.__signal_vol_period_shift = 0
self.__signal_vol_rebalance_freq = None
Expand All @@ -68,12 +68,12 @@ def __init__(self):
self.__signal_vol_periods = 20
self.__signal_vol_obs_in_year = 252

# portfolio notional size
# Portfolio notional size
self.__portfolio_notional_size = None
self.__portfolio_combination = None
self.__portfolio_combination_weights = None

# parameters for maximum position limits (expressed as whole portfolio)
# Parameters for maximum position limits (expressed as whole portfolio)
self.__max_net_exposure = None
self.__max_abs_exposure = None

Expand All @@ -82,18 +82,21 @@ def __init__(self):
self.__position_clip_resample_type = 'mean'
self.__position_clip_period_shift = 0

# take profit and stop loss parameters
# Take profit and stop loss parameters
self.__take_profit = None
self.__stop_loss = None

# should we delay the signal?
# Should we delay the signal?
self.__signal_delay = 0

# annualization factor for return stats (and should we resample data first before calculating it?)
# Annualization factor for return stats (and should we resample data first before calculating it?)
self.__ann_factor = 252
self.__resample_ann_factor = None

# how do we create a cumulative index of strategy returns

self.__spot_rc_bp = None

# How do we create a cumulative index of strategy returns
# either multiplicative starting a 100
# or additive starting at 0
self.__cum_index = 'mult' # 'mult' or 'add'
Expand Down Expand Up @@ -366,6 +369,25 @@ def spot_tc_bp(self, spot_tc_bp):
else:
self.__spot_tc_bp = float(spot_tc_bp) / (2.0 * 100.0 * 100.0)

@property
def spot_rc_bp(self):
return self.__spot_rc_bp

@spot_rc_bp.setter
def spot_rc_bp(self, spot_rc_bp):
if isinstance(spot_rc_bp, dict):
spot_rc_bp = spot_rc_bp.copy()

for k in spot_rc_bp.keys():
spot_rc_bp[k] = float(spot_rc_bp[k]) / (100.0 * 100.0)

self.__spot_rc_bp = spot_rc_bp

elif isinstance(spot_rc_bp, DataFrame):
self.__spot_rc_bp = spot_rc_bp # assume that DataFrame is in the percentage form (bid to mid)
else:
self.__spot_rc_bp = float(spot_rc_bp) / (100.0 * 100.0)

#### FOR FUTURE USE ###

@property
Expand Down

0 comments on commit 59e340e

Please sign in to comment.