Module engine
Classes
class Backtest (data: List[Dict[str, TSData]], strategy: Strategy, *, metadata: Metadata = <backtest.engine.metadata.Metadata object>, market_index: TSData = None, main_timestep: int = 0, initial_cash: float = 100000, commission: float = None, relative_commission: float = None, margin_interest: float = 0, min_initial_margin: float = 50, min_maintenance_margin: float = 25, liquidation_delay: int = 2, min_initial_margin_short: float = 50, min_maintenance_margin_short: float = 25, broker: Type[Broker] = backtest.engine.broker.Broker, account: Type[Account] = backtest.engine.account.Account, window: int = 50, default_marginable: bool = False, default_shortable: bool = False, risk_free_rate: float = 1.5, default_short_rate: float = 1.5, sell_at_the_end: bool = True, cash_controller: CashControllerBase = <backtest.engine.cashController.CashControllerBase object>, verbose=3, time_res_extender: Optional[TimeResExtender] = None, indicators: Union[IndicatorSet, List[IndicatorSet], Dict[int, IndicatorSet]] = IndicatorSet(n_indicators=0))-
Args:
data: The data on which to run the backtest. It is a list of dictionaries where each dictionary represents a group of time series data. The key is the ticker and the value is the TSData object. IMPORTANT: The length of each time series (charts) must be the same with the same index. If they are not, you need to pad the data with nan.
strategy: The strategy to test
metadata: A metadata object. This is useful to store information about the backtest to add context.
market_index: The TSData object containing the reference market index. (Used to compare the strategy performances to the market)
main_timestep: The index of the timeseries data in data list to use as the main series. i.e. the frequency at which the strategy is run. The other timeseries will be passed to the strategy as additional data. For example, if our data is a list of two dictionaries, the first one containing hourly data and the second one containing daily data, and we extend the daily data to weekly data, we would set main_timestep to 1. (The second dictionary) to run our strategy at each open market days.
initial_cash: The initial cash in bank account
commission: The absolute commission. In $ per trade
relative_commission: The relative commission. In % of the trade value. Example: 5% would be 5
margin_interest: The interest rate on margin. In % of the margin value. Example: 5% would be 5
min_initial_margin: The minimum initial margin. In % of the trade value. Example: 5% would be 5
min_maintenance_margin: The minimum maintenance margin. In % of the trade value. Example: 5% would be 5
liquidation_delay: The delay before liquidating a position in margin call. In timestep. Example, if the delay is 2 and the main time resolution is daily, the position will be liquidated 2 days
min_initial_margin_short: The minimum initial margin for short positions. In % of the trade value.
min_maintenance_margin_short: The minimum maintenance margin for short positions. In % of the trade value.
broker: The Broker class to use. This is useful to use a custom broker. The broker class must inherit from the Broker class.
account: The Account class to use. This is useful to use a custom account. The account class must inherit from the Account class.
window: The lookback period for the strategy. The number of datapoint to pass to the strategy at each timestep. (The number of datapoint in the main timestep. There might be more or less for others)
default_marginable: In case there is no column "Marginable" in the data, this value will be used to know if the security is marginable.
default_shortable: In case there is no column "Shortable" in the data, this value will be used to know if the security is shortable.
risk_free_rate: The risk-free rate in %. Used to calculate performance ratios like the Sharpe ratio.
default_short_rate: The short rate in % in case there is no column 'Short_rate' in the data.
sell_at_the_end: Whether to sell every open position at the end or not. If True, the strategy will sell the positions at the end of the backtest.
cash_controller: A cash controller object to control the money flow in and out of the bank account during the backtest. This is useful to simulate the effect of, for example, weekly deposit or monthly withdrawal.
verbose: 0: No print, 1: Only errors, 2: Errors and warnings, 3: All
time_res_extender: A time resolution extender object. This is useful to add time resolutions to the data
indicators: An Indicator set object. This is useful to add indicators to the data. The indicators are calculated at each timestep and passed to the strategy. It can also be a list with the same length as the number of time resolution. Each element of the list will be an IndicatorSet object to use for the corresponding time resolution. If it is a dictionary, the keys will be the index of the time resolution and the values will be the IndicatorSet object to use for the corresponding time resolution. If a dictionary is used, make sure there is at least a key for the main time resolution. If the streaming parameter is True for one of the IndicatorSet, the streaming will be activated for every supported indicators.Static methods
def default_forge_last_candle(data: List[Dict[str, TSData]], current_time_res: int, timestep: datetime.datetime, main_timestep: int) ‑> List[Tuple[float, float, float, float, float, float, str]]-
Forge new candle by cropping the last candle for the data with bigger time resolution than the main timestep to avoid peeking into the future. (Data leaking) How this default method works:
For each Tickers: 1. Find the last candle compared to current time step with the current resolution. (Get index and Open) 2. Find the last candle for the ticker with main resolution. (Get index and Close) 3. Using the main resolution, get the series between the beginning of the candle (Current resolution) and the current timestep. (This will be a series of intra-candle data) This is where it can get tricky because, depending on your data, the start idx (computed in step 1) might not align with any timesteps of the series in the main resolution. In this default method, we will use the next candle the nearest of the desired start idx of the main resolution data. (Default behavior of pandas) 4. Get High by computing the max of the maxes of the intra-candle data and the Low by computing the min of the mins of the intra-candle data. The volume is obtained by summing the volume of the intra-candle data. 5. Return the new candle by using computed High Low Open Close and Volume.
DO NOT OVERRIDE THIS METHOD: If you need to override a candle forging method, override the method 'forge_last_candle' instead.
Args:
data: The data
current_time_res: The current time resolution (to forge)
timestep: The current timestep
main_timestep: The main timestepReturns:
- A list of the forged candles: List[Tuple[Open, High, Low, Close, Volume, Stock Split, ticker]]
def run_indicator(data: pandas.core.frame.DataFrame, previous_data: Optional[pandas.core.frame.DataFrame], indicators: IndicatorSet, streaming: bool, bigger_res: bool = False) ‑> pandas.core.frame.DataFrame-
Run the indicators on a single chart.
Args:
data: The chart data OHLCV
previous_data: The previously cached data. Used when streaming is True. If streaming is False, this parameter is ignored. This should be a dataframe with the same index as the data. This means that the indicators points that needs to be calculated should be nan, and one already calculated should be the previously calculated values. The columns names should be the output columns names of the indicators.
indicators: The indicatorSet to use.
streaming: Whether to use the streaming capabilities of the indicators or not.
bigger_res: Whether we are computing the indicator for a bigger time resolution or not. If True and we are in streaming mode, the last cache datapoint will be set to nan in order to recompute it.Returns:
- The new data
def stadardize_timesteps(timesteps_list: List[datetime.datetime]) ‑> List[datetime.datetime]-
Optionnaly reformat timesteps_list. Can be useful when indexes are variable across stocks
Args:
timesteps_list: The list of timestepsReturns:
- The reformatted list of timesteps
Methods
def apply_indicators(self, data: List[Record], time_res_idx: int, bigger_res: bool = False) ‑> List[Record]-
Apply the indicators to the data (All tickers in time res)
Args:
data: The list of records to apply the indicators to
time_res_idx: The current time resolution
bigger_res: Whether we are computing the indicator for a bigger time resolution or not. If True and we are in streaming mode, the last cache datapoint will be set to nan in order to recompute it.Returns:
- The updated data
def forge_last_candle(self, data: List[Dict[str, TSData]], prepared_data: List[List[Record]], current_time_res: int, timestep: datetime.datetime) ‑> List[Tuple[float, float, float, float, float, float, str]]-
Forge new candle by cropping the last candle for the data with bigger time resolution than the main timestep to avoid peeking into the future. (Data leaking) How this default method works: (Read the warning below if your data contains higher resolution than main res)
For each Tickers: 1. Find the last candle compared to current time step with the current resolution. (Get index and Open) 2. Find the last candle for the ticker with main resolution. (Get index and Close) 3. Using the main resolution, get the series between the beginning of the candle (Current resolution) and the current timestep. (This will be a series of intra-candle data) This is where it can get tricky because, depending on your data, the start idx (computed in step 1) might not align with any timesteps of the series in the main resolution. In this default method, we will use the next candle the nearest of the desired start idx of the main resolution data. (Default behavior of pandas) 4. Get High by computing the max of the maxes of the intra-candle data and the Low by computing the min of the mins of the intra-candle data. The volume is obtained by summing the volume of the intra-candle data. 5. Return the new candle by using computed High Low Open Close and Volume.
Important
If you need this method in your setup, you should override it since it may not work for your setup. In fact, it is hard to forge new candles for any arbitrary time resolution from discrete timeseries where timesteps might not correctly align. You should take this into account when making this method.
TO OVERRIDE THIS METHOD: Just override it like any other method. You can also use the default method by calling 'self.default_forge_last_candle' and complement it. To suppress the warning, you can override this method by only calling the default 'self.default_forge_last_candle'
Args:
data: The data
prepared_data: The already prepared data.
current_time_res: The current time resolution
timestep: The current timestepReturns:
- A list of the forged candles: List[Tuple[Open, High, Low, Close, Volume, Stock Split, ticker]]
def run(self) ‑> BackTestResult-
Run the backtest simulation with the given data and strategy. It will show a progress bar showing the current progress. It will save the results and all the statistics into a BackTestResult object in the results attribute, or returned value of this method.
Returns:
- BackTestResult object containing all the results, statistics, debug info, etc.