Files
aiomql/docs/core/backtesting/backtest_engine.md
T
Ichinga Samuel 2a55f49f78 v4
2024-11-13 04:29:37 +01:00

30 KiB

Table of Contents

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:

  • data BackTestData, optional - The data to use for backtesting. Defaults to None.

  • speed int, optional - The speed of the backtest. Defaults to 60 seconds.

  • start float | datetime, optional - The start time of the backtest. Defaults to 0. If a float is passed, it is assumed to be a timestamp.

  • end float | datetime, optional - The end time of the backtest. Defaults to 0. If a float is passed, it is assumed to be a timestamp.

  • restart bool, 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_terminal bool, 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

  • name str, optional - The name of the backtest. Defaults to "". If not provided, it is generated from the start and end times.

  • stop_time float | 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_exit bool, optional - Whether to close all open positions when the backtest is stopped. Defaults to True.

  • preload bool, optional - Whether to preload the ticks for the backtest. Defaults to True.

  • assign_to_config bool, optional - Whether to assign the backtest engine to the global config instance. Defaults to True.

  • account_info dict, 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:

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

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:

  • start float | datetime, optional - The start time of the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp.

  • end float | datetime, optional - The end time of the backtest. Defaults to None. If a float is passed, it is assumed to be a timestamp.

  • speed int, optional - The speed of the backtest. Defaults to 60.

  • restart bool, 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:

  • restart bool, 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:

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

Arguments:

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

Arguments:

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

Arguments:

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

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:

  • ticket int - Position ticket
  • sl int - stop loss level
  • tp int - 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:

  • 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)

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:

  • 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:

  • 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:

  • 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:

  • 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:

  • group str - 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:

  • symbol str - 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:

  • symbol str - 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:

  • 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:

  • 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:

  • 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:

  • 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:

  • 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:

  • 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:

  • 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 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:

  • 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 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:

  • 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:

  • 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:

  • 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:

  • 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 name
  • group - Group name
  • ticket - 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 name
  • group - Group argument to filter by
  • ticket - 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 history
  • date_to - Date to which to end the history
  • group - group keyword to filter by
  • ticket - ticket id to filter by
  • position - 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 history
  • date_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 history
  • date_to - Date to which to end the history
  • group - group keyword to filter by
  • position - position id to filter by
  • ticket - ticket id to filter by

Returns:

tuple[TradeDeal, ...]: Deals in the history