30 KiB
Table of Contents
- backtest_engine
- BackTestEngine
- __init__
- setup_test_range
- setup_data
- next
- data
- reset
- go_to
- fast_forward
- tracker
- save_result_to_json
- close_all_open
- wrap_up
- preload_ticks
- get_price_tick
- check_order
- check_account
- check_position
- close_position_manually
- close_position
- modify_stops
- update_account
- deposit
- withdraw
- setup_account
- setup_account_sync
- prices
- ticks
- rates
- symbols
- order_send
- order_check
- get_terminal_info
- get_version
- get_symbols_total
- get_symbols
- get_account_info
- get_symbol_info_tick
- get_symbol_info
- get_rates_from
- get_rates_from_pos
- get_rates_range
- get_ticks_from
- get_ticks_range
- order_calc_margin
- order_calc_profit
- get_orders_total
- get_orders
- get_positions_total
- get_positions
- get_history_orders_total
- get_history_orders
- get_history_deals_total
- get_history_deals
- BackTestEngine
backtest_engine
BackTestEngine Objects
class BackTestEngine()
__init__
def __init__(*,
data: BackTestData = None,
speed: int = 60,
start: float | datetime = 0,
end: float | datetime = 0,
restart: bool = True,
use_terminal: bool = None,
name: str = "",
stop_time: float | datetime = None,
close_open_positions_on_exit: bool = True,
preload=True,
assign_to_config: bool = True,
account_info: dict = None)
The BackTestEngine class is used to simulate trading strategies on historical data. It can accept already saved data or create new data for backtesting on the fly. Ideally only one instance of this class should be created per session. By default it is automatically assigned to the global config instance during instantiation, replacing any existing backtest engine instance. But this is a configurable behaviour. The start and end time can still be specified even when test data is provided. In that case it will be used to set the range of the backtest.
Arguments:
-
dataBackTestData, optional - The data to use for backtesting. Defaults to None. -
speedint, optional - The speed of the backtest. Defaults to 60 seconds. -
startfloat | datetime, optional - The start time of the backtest. Defaults to 0. If a float is passed, it is assumed to be a timestamp. -
endfloat | datetime, optional - The end time of the backtest. Defaults to 0. If a float is passed, it is assumed to be a timestamp. -
restartbool, optional - Whether to restart the backtest from the beginning. Defaults to True. This is useful when resuming a backtest using a saved BackTestData instance. -
use_terminalbool, optional - Whether to use the terminal for backtesting. Defaults to None. If None, it uses the global config setting. If use terminal is true, the backtest engine will use the terminal to get price data, compute margins, profit and check order viability. If false, it will use the data provided in the BackTestData instance and default algorithm for the calculations -
namestr, optional - The name of the backtest. Defaults to "". If not provided, it is generated from the start and end times. -
stop_timefloat | datetime, optional - The time to stop the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp. If not given it is assumed to be the end of the backtest range. -
close_open_positions_on_exitbool, optional - Whether to close all open positions when the backtest is stopped. Defaults to True. -
preloadbool, optional - Whether to preload the ticks for the backtest. Defaults to True. -
assign_to_configbool, optional - Whether to assign the backtest engine to the global config instance. Defaults to True. -
account_infodict, optional - A dictionary of account information to use for the backtest. Defaults to None. Use this to set the account information for the backtest.
Attributes:
-
_dataBackTestData - The data used for backtesting. This is the data that is saved to disk when the backtest is stopped. -
mt5MetaTrader - The MetaTrader instance for the backtest engine. -
configConfig - The global configuration instance. -
namestr - The name of the backtest. -
stop_testingbool - Whether to stop the backtest. -
use_terminalbool - Whether to use the terminal for backtesting. -
close_open_positions_on_exitbool - Whether to close all open positions when the backtest is stopped. -
stop_timeint - The time to stop the backtest. -
preloadbool - Whether to preload the ticks for the backtest. -
preloaded_ticksdict - A dictionary of preloaded ticks for the backtest. -
account_lockRLock - A reentrant lock for the account data. -
account_infodict - A dictionary of account information for the backtest.
setup_test_range
def setup_test_range(*,
start: float | datetime = None,
end: float | datetime = None,
speed: int = 60,
restart: bool = True)
Setup the test range for the backtest engine. This is used to set the range of the backtest and the speed at which it runs.
Arguments:
-
startfloat | datetime, optional - The start time of the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp. -
endfloat | datetime, optional - The end time of the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp. -
speedint, optional - The speed of the backtest. Defaults to 60. -
restartbool, optional - Whether to restart the backtest. Defaults to True. This is useful when resuming a backtest using a saved BackTestData.
setup_data
def setup_data(*, restart: bool = True)
Sets up the data for the backtest engine. This includes the orders, positions, deals and account information. This data is handled by specialized classes such as the BackTestAccount and the TradeManager classes.
Arguments:
restartbool, optional - Whether to restart the data. Defaults to True.
next
def next() -> Cursor
Move the cursor to the next time step in the backtest range.
data
@property
def data()
The BackTestData instance used for the backtest. If not provided, a new instance is created, and the data is made persistent when the backtest is stopped.
reset
def reset(clear_data: bool = False)
Reset the backtest engine. This is useful when restarting the backtest from the beginning.
go_to
def go_to(*, time: datetime | float)
Move the cursor to a specific time in the backtest range. You can pass a datetime object or a timestamp. You can't go back in time or beyond the limits of the range.
fast_forward
def fast_forward(*, steps: int)
Fast-forward the backtester by the given steps.
tracker
async def tracker()
The tracker monitors and updates open positions on every iteration. It is called by the controller.
save_result_to_json
@error_handler_sync
def save_result_to_json()
Saves the result to a json file at the end of testing.
close_all_open
async def close_all_open()
Closes all open position at the end of testing
wrap_up
@error_handler
async def wrap_up()
Wraps up the backtest. This is called at the end of testing to save the results and close all open positions.
preload_ticks
async def preload_ticks(*, symbol: str)
Pull a month data on ticks from the terminal. Starting from the current time.
Arguments:
symbolstr - The symbol to preload ticks for.
get_price_tick
@async_cache
async def get_price_tick(*, symbol: str, time: int) -> Tick | None
Get the price tick for a symbol at a given time. If the preload option is set to True, it will use the preloaded ticks when available.
Arguments:
symbolstr - The symbol to get the price tick for.timeint - The time to get the price tick.
check_order
@error_handler
async def check_order(*, ticket: int)
" Check if the order has reached its take profit or stop loss levels and close the order if it has. Checks only OrderType.BUY and OrderType.SELL orders that have reached their take profit or stop loss levels.
Arguments:
ticketint - Order ticket
check_account
def check_account()
Checks an account status. This method is called at each iteration to check if the account has burned out.
check_position
async def check_position(*, ticket: int)
Update the profit of an open position based on the current price of the symbol. It is called by the tracker to update the profit of open positions.
Arguments:
ticketint - Position ticket
close_position_manually
@error_handler_sync
async def close_position_manually(*, ticket: int)
Close a position manually without. Usually at the end of testing.
close_position
async def close_position(*, ticket: int) -> bool
Close an open position for the trading account using the position ticket.
Arguments:
ticket- Position ticket
Returns:
bool- True if the position is closed successfully, False otherwise
modify_stops
@error_handler(response=False)
def modify_stops(*, ticket: int, sl: int, tp: int) -> bool
Modify the stop loss and take profit levels of an open position.
Arguments:
ticketint - Position ticketslint - stop loss leveltpint - Take profit level
Returns:
bool- True if the stops are modified successfully, False otherwise
update_account
def update_account(*,
profit: float = None,
margin: float = 0,
gain: float = 0)
Update the account. This method is protected by thread lock.
Arguments:
profitfloat - The current profit of one or more open positions. Can be positive or negative.marginfloat - The margin set aside for a trade. It is released when the trade is closed.gaingain - The gain realized when the trade is closed.
deposit
def deposit(*, amount: float)
Make deposit to the trading account
withdraw
def withdraw(*, amount: float)
Make a withdrawal from the trading account. You can not withdraw more than what you have
setup_account
@error_handler
async def setup_account(**kwargs)
Setup the trading account before the begining of a backtesting session.
Arguments:
(**kwargs, Any): Attributes for the backetest account object can be set here.
setup_account_sync
@error_handler_sync
def setup_account_sync(**kwargs)
Set up the backtesting account in sync mode
prices
@cached_property
def prices() -> dict[str, DataFrame]
Get the prices for instruments used in the backtesting. This class is called when the use_terminal option is set to False and trading data is provided in the data attribute. It makes sure that there is a price for each symbol for every second covered in the backtesting range, by reindexing the price ticks using the backtesting time span and filling up missing data using the nearest method. This method returns a dictionaries of dataframe containing the prices for each symbol. It's cached and there computed only once per backtesting session.
Returns:
dict[str, DataFrame]: A dictionary mapping dataframe of prices to symbols.
ticks
@cached_property
def ticks() -> dict[str, DataFrame]
Similar to prices above, but returns prices exactly as they are without reindexing and filling up.
Returns:
dict[str, DataFrame]: A dictionary mapping symbols to dataframes of ticks.
rates
@cached_property
def rates() -> dict[str, dict[int, DataFrame]]
This property is useful when backtesting with the use_terminal option set to false. It returns a nested dict that maps symbols to a dict mapping timeframes to rates. The timeframes are mapped using their integer values.
Returns:
dict[str, dict[int, DataFrame]]: A dictionary containing the symbol rates.
symbols
@cached_property
def symbols() -> dict[str, SymbolInfo]
A dictionary of symbols and SymbolInfo object. Used when use_terminal is set to false.
Returns:
dict[str, SymbolInfo]
order_send
@error_handler
async def order_send(*, request: dict, use_terminal=False) -> OrderSendResult
Simulates the sending of an order to the broker. An OrderSendResult is object is created at the end of this operation as would be created if it was done in live trading. When an order is successful a positions object is created, an order and deal object is created as well. When use_terminal is set to true the margin and profit are calculated by sending to the broker. This increases accuracy but slows down the backtester. Check order is called to make sure the order is valid and would go through if it was a live trade.
Arguments:
requestdict - The order request as a dict.use_terminalbool - A flag to override the use_terminal attribute. If true, the terminal will be used even if the use_terminal attribute is True.
Returns:
OrderSendResult- An object containing the result of the order send operation.
order_check
@error_handler
async def order_check(*,
request: dict,
use_terminal: bool = False) -> OrderCheckResult
Checks the order before placing it. If use_terminal, the order is checked with the broker, but the entire result is not used. Details such as balance, profit, equity, margin, and margin level are calculated by the backtester.
Arguments:
requestdict - The order request as a dict.use_terminalbool - A flag to override the use_terminal attribute. If true, the terminal will used.
Returns:
OrderCheckResult- The result of the order check.
get_terminal_info
@error_handler
async def get_terminal_info() -> TerminalInfo
Get the terminal information
Returns:
TerminalInfo- The terminal information
get_version
@error_handler
async def get_version() -> tuple[int, int, str]
Get the version of the terminal.
Returns:
tuple[int, int, str]: The version of the terminal
get_symbols_total
@error_handler
async def get_symbols_total() -> int
Get the total number of symbols available in the terminal.
Returns:
int- The total number of symbols available.
get_symbols
@error_handler
async def get_symbols(*, group: str = "") -> tuple[SymbolInfo, ...]
Get the symbols available in the terminal. Filter by group if provided.
Arguments:
groupstr - The group to filter by (default is "")
Returns:
tuple[SymbolInfo, ...]: A tuple of symbol information
get_account_info
@error_handler_sync
def get_account_info() -> AccountInfo
Get the account information
Returns:
AccountInfo- The account information
get_symbol_info_tick
@error_handler
async def get_symbol_info_tick(*, symbol: str) -> Tick
Get the price tick for a symbol at the current time
Arguments:
symbolstr - The symbol
Returns:
Tick- The price tick
get_symbol_info
@error_handler
async def get_symbol_info(*, symbol: str) -> SymbolInfo
Get the symbol information
Arguments:
symbolstr - The symbol to get information for
Returns:
SymbolInfo- The symbol information
get_rates_from
@error_handler
async def get_rates_from(*, symbol: str, timeframe: TimeFrame,
date_from: datetime | float,
count: int) -> np.ndarray
Get rates from a specific date to the current date. Used by the backtester to get rates for a symbol
Arguments:
symbolstr - The symbol to get rates fortimeframeTimeFrame - The timeframe of the ratesdate_fromdatetime | float - The date from which to get the ratescountint - The number of rates to get
Returns:
np.ndarray- An array of rates
get_rates_from_pos
@error_handler
async def get_rates_from_pos(*, symbol: str, timeframe: TimeFrame,
start_pos: int, count: int) -> np.ndarray
Get a number of rates counting from a specific position. With position zero being the current time.
Arguments:
symbolstr - The symbol to get rates fortimeframeTimeFrame - The timeframe of the ratesstart_posint - The position to start fromcountint - The number of rates to get
Returns:
np.ndarray- An array of rates
get_rates_range
@error_handler
async def get_rates_range(*, symbol: str, timeframe: TimeFrame,
date_from: datetime | float,
date_to: datetime | float) -> np.ndarray
Get rates within a specific date range. Used by the backtester to get rates for a symbol
Arguments:
symbolstr - The symbol to get rates fortimeframeTimeFrame - The timeframe of the ratesdate_fromdatetime | float - The date from which to get the ratesdate_todatetime | float - The date to which to get the rates
Returns:
np.ndarray- An array of rates
get_ticks_from
@error_handler
async def get_ticks_from(*,
symbol: str,
date_from: datetime | float,
count: int,
flags: CopyTicks = CopyTicks.ALL) -> np.ndarray
Get a specified number of ticks counting from a specific date.
Arguments:
symbolstr - The symbol to get ticks fordate_fromdatetime | float - The date from which to get the tickscountint - The number of ticks to getflagsCopyTicks - The flags to use when getting the ticks
Returns:
np.ndarray- An array of ticks
get_ticks_range
@error_handler
async def get_ticks_range(*,
symbol: str,
date_from: datetime | float,
date_to: datetime | float,
flags: CopyTicks = CopyTicks.ALL) -> np.ndarray
Get ticks within a specific date range.
Arguments:
symbolstr - The symbol to get ticks fordate_fromdatetime | float - The date from which to get the ticksdate_todatetime | float - The date to which to get the ticksflagsCopyTicks - The flags to use when getting the ticks
Returns:
np.ndarray- An array of ticks
order_calc_margin
@error_handler
async def order_calc_margin(*,
action: Literal[OrderType.BUY, OrderType.SELL],
symbol: str,
volume: float,
price: float,
use_terminal: bool = None)
Calculate the margin required for a trade.
Arguments:
actionLiteral[OrderType.BUY, OrderType.SELL] - Type of ordersymbolstr - Symbol namevolumefloat - Volume of the tradepricefloat - The price at which the trade is openeduse_terminalbool - A flag to override the use_terminal attribute. If true, the terminal will be used even if the use_terminal attribute is True.
Returns:
float- The margin required for the trade
order_calc_profit
@error_handler
async def order_calc_profit(*,
action: Literal[OrderType.BUY, OrderType.SELL],
symbol: str,
volume: float,
price_open: float,
price_close: float,
use_terminal=None)
Calculate the profit for a trade.
Arguments:
actionLiteral[OrderType.BUY, OrderType.SELL] - Type of ordersymbolstr - Symbol namevolumefloat - Volume of the tradeprice_openfloat - The price at which the trade is openedprice_closefloat - The price at which the trade is closeduse_terminalbool - A flag to override the use_terminal attribute. If true, the terminal will be used even if the use_terminal attribute is True.
Returns:
float- The profit of the trade
get_orders_total
@error_handler_sync
def get_orders_total() -> int
Get the total number of pending orders.
Returns:
int- Total number of pending orders
get_orders
@error_handler_sync
def get_orders(*,
symbol: str = "",
group: str = "",
ticket: int = None) -> tuple[TradeOrder, ...]
Get pending orders from the terminal history. This has to do with pending orders, which this backtester doesn't support yet.
Arguments:
symbol- Symbol namegroup- Group nameticket- Order ticket
Returns:
tuple[TradeOrder, ...]: Pending orders
get_positions_total
@error_handler_sync
def get_positions_total() -> int
Get the total number of open positions.
Returns:
int- Total number of open positions
get_positions
@error_handler_sync
def get_positions(*,
symbol: str = None,
group: str = None,
ticket: int = None) -> tuple[TradePosition, ...]
Get open positions from the terminal history.
Arguments:
symbol- The symbol namegroup- Group argument to filter byticket- Position ticket
Returns:
tuple[TradePosition, ...]: Open positions
get_history_orders_total
@error_handler_sync
def get_history_orders_total(*, date_from: datetime | float,
date_to: datetime | float) -> int
Get the total number of orders in the terminal history.
Arguments:
-
date_from- The start date of the history -
date_to- The end date of the history
Returns:
int- Total number of orders in the history
get_history_orders
@error_handler_sync
def get_history_orders(*,
date_from: datetime | float = None,
date_to: datetime | float = None,
group: str = "",
ticket: int = None,
position: int = None) -> tuple[TradeOrder, ...]
Get orders from the terminal history.
Arguments:
date_from- Date from which to start the historydate_to- Date to which to end the historygroup- group keyword to filter byticket- ticket id to filter byposition- position id to filter by
Returns:
tuple[TradeOrder, ...]: Orders in the history
get_history_deals_total
@error_handler_sync
def get_history_deals_total(*, date_from: datetime | float,
date_to: datetime | float) -> int
Get the total number of deals in the terminal history.
Arguments:
date_from- Date from which to start the historydate_to- Date to which to end the history
Returns:
int- Total number of deals in the history
get_history_deals
@error_handler_sync
def get_history_deals(*,
date_from: datetime | float = None,
date_to: datetime | float = None,
group: str = None,
position: int = None,
ticket: int = None) -> tuple[TradeDeal, ...]
Get deals from the terminal history.
Arguments:
date_from- Date from which to start the historydate_to- Date to which to end the historygroup- group keyword to filter byposition- position id to filter byticket- ticket id to filter by
Returns:
tuple[TradeDeal, ...]: Deals in the history