Files
aiomql/docs/core/backtesting/backtest_engine.md
T
Ichinga Samuel 2e7aa73aec v4
2024-11-15 21:13:19 +01:00

42 KiB

BackTestEngine

Table of Contents

BackTestEngine

class BackTestEngine

The BackTestEngine class is used to simulate trading strategies on historical data that is either preloaded or provided at runtime during the test.

Attributes:

Name Type Description
_data BackTestData The data used for backtesting. This is the data that is saved to disk when the backtest is stopped.
mt5 MetaTrader The MetaTrader instance for the backtest engine.
config Config The global configuration instance.
name str The name of the backtest.
stop_testing bool Whether to stop the backtest.
use_terminal bool Whether to use the terminal for backtesting.
close_open_positions_on_exit bool Whether to close all open positions when the backtest is stopped.
stop_time int The time to stop the backtest.
preload bool Whether to preload the ticks for the backtest.
preloaded_ticks dict A dictionary of preloaded ticks for the backtest.
account_lock RLock A reentrant lock for the account data.
account_info dict A dictionary of account information for the backtest.

_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.

Parameters:

Name Type Description
data BackTestData The data to use for backtesting. Defaults to None.
speed int The speed of the backtest. Defaults to 60 seconds.
start float | datetime The start time of the backtest. Defaults to 0. If a float is passed, it is assumed to be a timestamp.
end float | datetime The end time of the backtest. Defaults to 0. If a float is passed, it is assumed to be a timestamp.
restart bool Whether to restart the backtest from the beginning. Defaults to True. This is useful when resuming a backtest using a saved BackTestData instance.
use_terminal bool 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
name str The name of the backtest. Defaults to "". If not provided, it is generated from the start and end times.
stop_time float | datetime 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_exit bool Whether to close all open positions when the backtest is stopped. Defaults to True.
preload bool Whether to preload the ticks for the backtest. Defaults to True.
assign_to_config bool Whether to assign the backtest engine to the global config instance. Defaults to True.
account_info dict A dictionary of account information to use for the backtest. Defaults to None. Use this to set the 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.

Parameters:

Name Type Description
start float | datetime The start time of the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp.
end float | datetime The end time of the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp.
speed int The speed of the backtest. Defaults to 60 seconds.
restart bool 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.

Parameters:

Name Type Description
restart bool 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. Clear trade data if any when the clear_data parameter is true

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.

Parameters:

Name Type Description
symbol str 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.

Parameters:

Name Type Description
symbol str The symbol to get the price tick for.
time int 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.

Parameters:

Name Type Description
ticket int 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.

Parameters:

Name Type Description
ticket int 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.

Parameters:

Name Type Description
ticket int Position ticket

Returns:

Type Description
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.

Parameters:

Name Type Description
ticket int Position ticket
sl int stop loss level

Returns:

Type Description
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.

Parameters:

Name Type Description
profit float The current profit of one or more open positions. Can be positive or negative.
margin float The margin set aside for a trade. It is released when the trade is closed.
gain gain 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)

Set up the trading account before the beginning of a backtesting session.

Parameters:

Name Type Description
kwargs dict Attributes for the backtest 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:

Type Description
dict 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:

Type Description
dict A dictionary mapping dataframe of prices to symbols.

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:

Type Description
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:

Type Description
dict[str, SymbolInfo] A dictionary of symbols and SymbolInfo object.

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. The margin and profit are calculated by sending to the broker if use_terminal is true. This increases accuracy but slows down the backtester. The check_order method is called to make sure the order is valid and would go through if it was a live trade.

Parameters:

Name Type Description
request dict The order request as a dict.
use_terminal bool A flag to override the use_terminal attribute. If true, the terminal will be used even if the use_terminal attribute is True.

Returns:

Type Description
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 is true, 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.

Parameters:

Name Type Description
request dict The order request as a dict.
use_terminal bool A flag to override the use_terminal attribute. If true, the terminal will used.

Returns:

Type Description
OrderCheckResult The result of the order check.

get_terminal_info

@error_handler
async def get_terminal_info() -> TerminalInfo

Get the terminal information

Returns:

Type Description
TerminalInfo The terminal information

get_version

@error_handler
async def get_version() -> tuple[int, int, str]

Get the version of the terminal.

Returns:

Type Description
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:

Type Description
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.

Parameters:

Name Type Description
group str The group to filter by (default is "")

Returns:

Type Description
tuple[SymbolInfo, ...] A tuple of symbol information

get_account_info

@error_handler_sync
def get_account_info() -> AccountInfo

Get the account information

Returns:

Type Description
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

Parameters:

Name Type Description
symbol str The symbol to get the price tick for.

Returns:

Type Description
Tick The price tick

get_symbol_info

@error_handler
async def get_symbol_info(*, symbol: str) -> SymbolInfo

Get the symbol information

Parameters:

Name Type Description
symbol str The symbol to get information for

Returns:

Type Description
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

Parameters:

Name Type Description
symbol str The symbol to get rates for
timeframe TimeFrame The timeframe of the rates
date_from datetime | float The date from which to get the rates
count int The number of rates to get

Returns:

Type Description
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.

Parameters:

Name Type Description
symbol str The symbol to get rates for
timeframe TimeFrame The timeframe of the rates
start_pos int The position to start from
count int The number of rates to get

Returns:

Type Description
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

Parameters:

Name Type Description
symbol str The symbol to get rates for
timeframe TimeFrame The timeframe of the rates
date_from datetime | float The date from which to get the rates
date_to datetime | float The date to which to get the rates

Returns:

Type Description
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.

Parameters:

Name Type Description
symbol str The symbol to get ticks for
date_from datetime | float The date from which to get the ticks
count int The number of ticks to get
flags CopyTicks The flags to use when getting ticks

Returns:

Type Description
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.

Parameters:

Name Type Description
symbol str The symbol to get ticks for
date_from datetime | float The date from which to get the ticks
date_to datetime | float The date to which to get the ticks
flags CopyTicks The flags to use when getting ticks

Returns:

Type Description
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.

Parameters:

Name Type Description
action Literal[OrderType.BUY, OrderType.SELL] Type of order
symbol str Symbol name
volume float Volume of the trade
price float The price at which the trade is opened
use_terminal bool A flag to override the use_terminal attribute. If true, the terminal will be used even if the use_terminal attribute is True.

Returns:

Type Description
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.

Parameters:

Name Type Description
action Literal[OrderType.BUY, OrderType.SELL] Type of order
symbol str Symbol name
volume float Volume of the trade
price_open float The price at which the trade is opened
price_close float The price at which the trade is closed
use_terminal bool A flag to override the use_terminal attribute. If true, the terminal will be used even if the use_terminal attribute is True.

Returns:

Type Description
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:

Type Description
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.

Parameters:

Name Type Description
symbol str Symbol name
group str Group name
ticket int Order ticket

Returns:

Type Description
tuple[TradeOrder, ...] Pending orders

get_positions_total

@error_handler_sync
def get_positions_total() -> int

Get the total number of open positions.

Returns:

Type Description
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.

Parameters:

Name Type Description
symbol str Symbol name
group str Group name
ticket int Position ticket

Returns:

Type Description
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.

Parameters:

Name Type Description
date_from datetime | float The start date of the history
date_to datetime | float The end date of the history

Returns:

Type Description
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.

Parameters:

Name Type Description
date_from datetime | float Date from which to start the history
date_to datetime | float Date to which to end the history
group str group keyword to filter by
ticket int ticket id to filter by
position int position id to filter by

Returns:

Type Description
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.

Parameters:

Name Type Description
date_from datetime | float Date from which to start the history
date_to datetime | float Date to which to end the history

Returns:

Type Description
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.

Parameters:

Name Type Description
date_from datetime | float Date from which to start the history
date_to datetime | float Date to which to end the history
group str group keyword to filter by
position int position id to filter by
ticket int ticket id to filter by

Returns:

Type Description
tuple[TradeDeal, ...] Deals in the history