Giter Club home page Giter Club logo

gym-mtsim's Introduction

gym-mtsim: OpenAI Gym - MetaTrader 5 Simulator

MtSim is a simulator for the MetaTrader 5 trading platform alongside an OpenAI Gym environment for reinforcement learning-based trading algorithms. MetaTrader 5 is a multi-asset platform that allows trading Forex, Stocks, Crypto, and Futures. It is one of the most popular trading platforms and supports numerous useful features, such as opening demo accounts on various brokers.

The simulator is separated from the Gym environment and can work independently. Although the Gym environment is designed to be suitable for RL frameworks, it is also proper for backtesting and classic analysis.

The goal of this project was to provide a general-purpose, flexible, and easy-to-use library with a focus on code readability that enables users to do all parts of the trading process through it from 0 to 100. So, gym-mtsim is not just a testing tool or a Gym environment. It is a combination of a real-world simulator, a backtesting tool with high detail visualization, and a Gym environment appropriate for RL/classic algorithms.

Note: For beginners, it is recommended to check out the gym-anytrading project.

Prerequisites

Install MetaTrader 5

Download and install MetaTrader 5 software from here.

Open a demo account on any broker. By default, the software opens a demo account automatically after installation.

Explore the software and try to get familiar with it by trading different symbols in both hedged and unhedged accounts.

Install gym-mtsim

Via PIP

pip install gym-mtsim

From Repository

git clone https://github.com/AminHP/gym-mtsim
cd gym-mtsim
pip install -e .

## or

pip install --upgrade --no-deps --force-reinstall https://github.com/AminHP/gym-mtsim/archive/main.zip

Install stable-baselines3

This package is required to run some examples. Install it from here.

Components

1. SymbolInfo

This is a data class that contains the essential properties of a symbol. Try to get fully acquainted with these properties in case they are unfamiliar. There are plenty of resources that provide good explanations.

2. Order

This is another data class that consists of information of an order. Each order has the following properties:

id: A unique number that helps with tracking orders.

type: An enum that specifies the type of the order. It can be either Buy or Sell.

symbol: The symbol selected for the order.

volume: The volume chose for the order. It can be a multiple of volume_step between volume_min and volume_max.

fee: It is a tricky property. In MetaTrader, there is no such concept called fee. Each symbol has bid and ask prices, the difference between which represents the fee. Although MetaTrader API provides these bid/ask prices for the recent past, it is not possible to access them for the distant past. Therefore, the fee property helps to manage the mentioned difference.

entry_time: The time when the order was placed.

entry_price: The close price when the order was placed.

exit_time: The time when the order was closed.

exit_price: The close price when the order was closed.

profit: The amount of profit earned by this order so far.

margin: The required amount of margin for this order.

closed: A boolean that specifies whether this order is closed or not.

3. MtSimulator

This is the core class that simulates the main parts of MetaTrader. Most of its public properties and methods are explained here. But feel free to take a look at the complete source code.

  • Properties:

    unit: The unit currency. It is usually USD, but it can be anything the broker allows, such as EUR.

    balance: The amount of money before taking into account any open positions.

    equity: The amount of money, including the value of any open positions.

    margin: The amount of money which is required for having positions opened.

    leverage: The leverage ratio.

    free_margin: The amount of money that is available to open new positions.

    margin_level: The ratio between equity and margin.

    stop_out_level: If the margin_level drops below stop_out_level, the most unprofitable position will be closed automatically by the broker.

    hedge: A boolean that specifies whether hedging is enabled or not.

    symbols_info: A dictionary that contains symbols' information.

    symbols_data: A dictionary that contains symbols' OHLCV data.

    orders: The list of open orders.

    closed_orders: The list of closed orders.

    current_time: The current time of the system.

  • Methods:

    download_data: Downloads required data from MetaTrader for a list of symbols in a time range. This method can be overridden in order to download data from servers other than MetaTrader. Note that this method only works on Windows, as the MetaTrader5 Python package is not available on other platforms.

    save_symbols: Saves the downloaded symbols' data to a file.

    load_symbols: Loads the symbols' data from a file.

    tick: Moves forward in time (by a delta time) and updates orders and other related properties.

    create_order: Creates a Buy or Sell order and updates related properties.

    close_order: Closes an order and updates related properties.

    get_state: Returns the state of the system. The result is similar to the Trading tab and History tab of the Toolbox window in MetaTrader software.

4. MtEnv

This is the Gym environment that works on top of the MtSim. Most of its public properties and methods are explained here. But feel free to take a look at the complete source code.

  • Properties:

    original_simulator: An instance of MtSim class as a baseline for simulating the system.

    simulator: The current simulator in use. It is a copy of the original_simulator.

    trading_symbols: The list of symbols to trade.

    time_points: A list of time points based on which the simulator moves time. The default value is taken from the pandas DataFrame.Index of the first symbol in the trading_symbols list.

    hold_threshold: A probability threshold that controls holding or placing a new order.

    close_threshold: A probability threshold that controls closing an order.

    fee: A constant number or a callable that takes a symbol as input and returns the fee based on that.

    symbol_max_orders: Specifies the maximum number of open positions per symbol in hedge trading.

    multiprocessing_processes: Specifies the maximum number of processes used for parallel processing.

    prices: The symbol prices over time. It is used to calculate signal features and render the environment.

    signal_features: The extracted features over time. It is used to generate Gym observations.

    window_size: The number of time points (current and previous points) as the length of each observation's features.

    features_shape: The shape of a single observation's features.

    action_space: The Gym action_space property. It has a complex structure since stable-baselines does not support Dict or 2D Box action spaces. The action space is a 1D vector of size count(trading_symbols) * (symbol_max_orders + 2). For each symbol, two types of actions can be performed, closing previous orders and placing a new order. The former is controlled by the first symbol_max_orders elements and the latter is controlled by the last two elements. Therefore, the action for each symbol is [probability of closing order 1, probability of closing order 2, ..., probability of closing order symbol_max_orders, probability of holding or creating a new order, volume of the new order]. The last two elements specify whether to hold or place a new order and the volume of the new order (positive volume indicates buy and negative volume indicates sell). These elements are a number in range (-∞, ∞), but the probability values must be in the range [0, 1]. This is a problem with stable-baselines as mentioned earlier. To overcome this problem, it is assumed that the probability values belong to the logit function. So, applying the expit function on them gives the desired probability values in the range [0, 1]. This function is applied in the step method of the environment.

    observation_space: The Gym observation_space property. Each observation contains information about balance, equity, margin, features, and orders. The features is a window on the signal_features from index current_tick - window_size + 1 to current_tick. The orders is a 3D array. Its first dimension specifies the symbol index in the trading_symbols list. The second dimension specifies the order number (each symbol can have more than one open order at the same time in hedge trading). The last dimension has three elements, entry_price, volume, and profit of corresponding order.

    history: Stores the information of all steps.

  • Methods:

    seed: The typical Gym seed method.

    reset: The typical Gym reset method.

    step: The typical Gym step method.

    render: The typical Gym render method. It can render in three modes, human, simple_figure, and advanced_figure.

    close: The typical Gym close method.

  • Virtual Methods:

    _get_prices: It is called in the constructor and calculates symbol prices.

    _process_data: It is called in the constructor and calculates signal_features.

    _calculate_reward: The reward function for the RL agent.

A Simple Example

MtSim

Create a simulator with custom parameters

import pytz
from datetime import datetime, timedelta
from gym_mtsim import MtSimulator, OrderType, Timeframe, FOREX_DATA_PATH


sim = MtSimulator(
    unit='USD',
    balance=10000.,
    leverage=100.,
    stop_out_level=0.2,
    hedge=False,
)

if not sim.load_symbols(FOREX_DATA_PATH):
    sim.download_data(
        symbols=['EURUSD', 'GBPCAD', 'GBPUSD', 'USDCAD', 'USDCHF', 'GBPJPY', 'USDJPY'],
        time_range=(
            datetime(2021, 5, 5, tzinfo=pytz.UTC),
            datetime(2021, 9, 5, tzinfo=pytz.UTC)
        ),
        timeframe=Timeframe.D1
    )
    sim.save_symbols(FOREX_DATA_PATH)

Place some orders

sim.current_time = datetime(2021, 8, 30, 0, 17, 52, tzinfo=pytz.UTC)

order1 = sim.create_order(
    order_type=OrderType.Buy,
    symbol='GBPCAD',
    volume=1.,
    fee=0.0003,
)

sim.tick(timedelta(days=2))

order2 = sim.create_order(
    order_type=OrderType.Sell,
    symbol='USDJPY',
    volume=2.,
    fee=0.01,
)

sim.tick(timedelta(days=5))

state = sim.get_state()

print(
    f"balance: {state['balance']}, equity: {state['equity']}, margin: {state['margin']}\n"
    f"free_margin: {state['free_margin']}, margin_level: {state['margin_level']}\n"
)
state['orders']
balance: 10000.0, equity: 10717.58118589908, margin: 3375.480933228619
free_margin: 7342.1002526704615, margin_level: 3.1751271592500743
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
Id Symbol Type Volume Entry Time Entry Price Exit Time Exit Price Exit Balance Exit Equity Profit Margin Fee Closed
0 2 USDJPY Sell 2.0 2021-09-01 00:17:52+00:00 110.02500 2021-09-06 00:17:52+00:00 109.71200 NaN NaN 552.355257 2000.000000 0.0100 False
1 1 GBPCAD Buy 1.0 2021-08-30 00:17:52+00:00 1.73389 2021-09-06 00:17:52+00:00 1.73626 NaN NaN 165.225928 1375.480933 0.0003 False

Close all orders

order1_profit = sim.close_order(order1)
order2_profit = sim.close_order(order2)

# alternatively:
# for order in sim.orders:
#     sim.close_order(order)

state = sim.get_state()

print(
    f"balance: {state['balance']}, equity: {state['equity']}, margin: {state['margin']}\n"
    f"free_margin: {state['free_margin']}, margin_level: {state['margin_level']}\n"
)
state['orders']
balance: 10717.58118589908, equity: 10717.58118589908, margin: 0.0
free_margin: 10717.58118589908, margin_level: inf
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
Id Symbol Type Volume Entry Time Entry Price Exit Time Exit Price Exit Balance Exit Equity Profit Margin Fee Closed
0 2 USDJPY Sell 2.0 2021-09-01 00:17:52+00:00 110.02500 2021-09-06 00:17:52+00:00 109.71200 10717.581186 10717.581186 552.355257 2000.000000 0.0100 True
1 1 GBPCAD Buy 1.0 2021-08-30 00:17:52+00:00 1.73389 2021-09-06 00:17:52+00:00 1.73626 10165.225928 10717.581186 165.225928 1375.480933 0.0003 True

MtEnv

Create an environment

import gymnasium as gym
import gym_mtsim

env = gym.make('forex-hedge-v0')
# env = gym.make('stocks-hedge-v0')
# env = gym.make('crypto-hedge-v0')
# env = gym.make('mixed-hedge-v0')

# env = gym.make('forex-unhedge-v0')
# env = gym.make('stocks-unhedge-v0')
# env = gym.make('crypto-unhedge-v0')
# env = gym.make('mixed-unhedge-v0')
  • This will create a default environment. There are eight default environments, but it is also possible to create environments with custom parameters.

Create an environment with custom parameters

import pytz
from datetime import datetime, timedelta
import numpy as np
from gym_mtsim import MtEnv, MtSimulator, FOREX_DATA_PATH


sim = MtSimulator(
    unit='USD',
    balance=10000.,
    leverage=100.,
    stop_out_level=0.2,
    hedge=True,
    symbols_filename=FOREX_DATA_PATH
)

env = MtEnv(
    original_simulator=sim,
    trading_symbols=['GBPCAD', 'EURUSD', 'USDJPY'],
    window_size=10,
    # time_points=[desired time points ...],
    hold_threshold=0.5,
    close_threshold=0.5,
    fee=lambda symbol: {
        'GBPCAD': max(0., np.random.normal(0.0007, 0.00005)),
        'EURUSD': max(0., np.random.normal(0.0002, 0.00003)),
        'USDJPY': max(0., np.random.normal(0.02, 0.003)),
    }[symbol],
    symbol_max_orders=2,
    multiprocessing_processes=2
)

Print some information

print("env information:")

for symbol in env.prices:
    print(f"> prices[{symbol}].shape:", env.prices[symbol].shape)

print("> signal_features.shape:", env.signal_features.shape)
print("> features_shape:", env.features_shape)
env information:
> prices[GBPCAD].shape: (88, 2)
> prices[EURUSD].shape: (88, 2)
> prices[USDJPY].shape: (88, 2)
> signal_features.shape: (88, 6)
> features_shape: (10, 6)

Trade randomly

observation = env.reset()

while True:
    action = env.action_space.sample()
    observation, reward, terminated, truncated, info = env.step(action)
    done = terminated or truncated

    if done:
        # print(info)
        print(
            f"balance: {info['balance']}, equity: {info['equity']}, margin: {info['margin']}\n"
            f"free_margin: {info['free_margin']}, margin_level: {info['margin_level']}\n"
            f"step_reward: {info['step_reward']}"
        )
        break
balance: 18179.65219519348, equity: 18179.65219519348, margin: 0.0
free_margin: 18179.65219519348, margin_level: inf
step_reward: 0.0

Render in human mode

state = env.render()

print(
    f"balance: {state['balance']}, equity: {state['equity']}, margin: {state['margin']}\n"
    f"free_margin: {state['free_margin']}, margin_level: {state['margin_level']}\n"
)
state['orders']
balance: 18179.65219519348, equity: 18179.65219519348, margin: 0.0
free_margin: 18179.65219519348, margin_level: inf
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
Id Symbol Type Volume Entry Time Entry Price Exit Time Exit Price Exit Balance Exit Equity Profit Margin Fee Closed
0 14 EURUSD Buy 9.95 2021-08-27 00:00:00+00:00 1.17955 2021-08-31 00:00:00+00:00 1.18083 18179.652195 18179.652195 1052.554631 11736.522500 0.000222 True
1 13 EURUSD Buy 0.22 2021-08-26 00:00:00+00:00 1.17515 2021-08-31 00:00:00+00:00 1.18083 17127.097565 18179.652195 120.009649 258.533000 0.000225 True
2 12 GBPCAD Buy 7.10 2021-08-24 00:00:00+00:00 1.72784 2021-08-26 00:00:00+00:00 1.73770 17007.087916 17007.087916 5140.996853 9746.529273 0.000675 True
3 11 EURUSD Sell 3.33 2021-08-20 00:00:00+00:00 1.16996 2021-08-23 00:00:00+00:00 1.17457 11866.091062 11866.091062 -1610.650324 3895.966800 0.000227 True
4 10 GBPCAD Buy 6.65 2021-07-30 00:00:00+00:00 1.73335 2021-08-02 00:00:00+00:00 1.73577 13476.741387 13476.741387 868.941338 9248.130601 0.000786 True
5 9 EURUSD Sell 0.26 2021-07-21 00:00:00+00:00 1.17946 2021-07-22 00:00:00+00:00 1.17707 12607.800048 12607.800048 56.809064 306.659600 0.000205 True
6 8 USDJPY Buy 7.11 2021-07-12 00:00:00+00:00 110.34900 2021-07-16 00:00:00+00:00 110.08100 12550.990984 12550.990984 -1850.301309 7110.000000 0.018474 True
7 7 EURUSD Buy 4.23 2021-07-07 00:00:00+00:00 1.17903 2021-07-09 00:00:00+00:00 1.18774 14401.292293 14401.292293 3618.699910 4987.296900 0.000155 True
8 6 GBPCAD Sell 2.77 2021-07-02 00:00:00+00:00 1.70511 2021-07-05 00:00:00+00:00 1.70716 10782.592383 10782.592383 -612.337927 3831.428119 0.000678 True
9 5 EURUSD Sell 6.07 2021-06-21 00:00:00+00:00 1.19185 2021-06-22 00:00:00+00:00 1.19413 11394.930310 11394.930310 -1512.813611 7234.529500 0.000212 True
10 4 USDJPY Buy 4.18 2021-06-11 00:00:00+00:00 109.68200 2021-06-17 00:00:00+00:00 110.22100 12907.743921 12907.743921 1980.439673 4180.000000 0.016785 True
11 3 GBPCAD Buy 5.58 2021-06-01 00:00:00+00:00 1.70755 2021-06-02 00:00:00+00:00 1.70462 10927.304248 10927.304248 -1678.531017 7894.516666 0.000689 True
12 2 EURUSD Buy 2.65 2021-05-26 00:00:00+00:00 1.21922 2021-05-28 00:00:00+00:00 1.21896 12605.835265 12605.835265 -130.546444 3230.933000 0.000233 True
13 1 USDJPY Sell 6.73 2021-05-19 00:00:00+00:00 109.22700 2021-05-20 00:00:00+00:00 108.76700 12736.381709 12736.381709 2736.381709 6730.000000 0.017759 True

Render in simple_figure mode

  • Each symbol is illustrated with a separate color.
  • The green/red triangles show successful buy/sell actions.
  • The gray triangles indicate that the buy/sell action has encountered an error.
  • The black vertical bars specify close actions.
env.render('simple_figure')

png

Render in advanced_figure mode

  • Clicking on a symbol name will hide/show its plot.
  • Hovering over points and markers will display their detail.
  • The size of triangles indicates their relative volume.
env.render('advanced_figure', time_format="%Y-%m-%d")

png

A Complete Example using stable-baselines

import gymnasium as gym
from gym_mtsim import (
    Timeframe, SymbolInfo,
    MtSimulator, OrderType, Order, SymbolNotFound, OrderNotFound,
    MtEnv,
    FOREX_DATA_PATH, STOCKS_DATA_PATH, CRYPTO_DATA_PATH, MIXED_DATA_PATH,
)
from stable_baselines3 import A2C
from stable_baselines3.common.vec_env import DummyVecEnv
import random
import numpy as np
import torch

env_name = 'forex-hedge-v0'

# reproduce training and test
seed = 2024
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)

env = gym.make(env_name)
model = A2C('MultiInputPolicy', env, verbose=0)
model.learn(total_timesteps=1000)

observation, info = env.reset(seed=seed)

while True:
    action, _states = model.predict(observation)
    observation, reward, terminated, truncated, info = env.step(action)
    done = terminated or truncated

    if done:
        break

env.unwrapped.render('advanced_figure', time_format='%Y-%m-%d')

png

References

gym-mtsim's People

Contributors

alex2782 avatar aminhp avatar mostafahadian avatar nexon33 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gym-mtsim's Issues

Run on MT5

Is it possible to run a created model on an actual MetaTrader5 Instance?

Low bound of spaces.Box

Hello @AminHP ,
why is the low bound of balance, equity and features is -np.inf? see code:

        self.observation_space = spaces.Dict({
            'balance': spaces.Box(low=-np.inf, high=np.inf, shape=(1,)),
            'equity': spaces.Box(low=-np.inf, high=np.inf, shape=(1,)),
            'margin': spaces.Box(low=-np.inf, high=np.inf, shape=(1,)),
            'features': spaces.Box(low=-np.inf, high=np.inf, shape=self.features_shape),
            'orders': spaces.Box(
                low=-np.inf, high=np.inf,
                shape=(len(self.trading_symbols), self.symbol_max_orders, 3)
            )  # symbol, order_i -> [entry_price, volume, profit]
        })

shouldnt low bound start from zero? meaning its impossible for balance be below 0

The new models and the issues caused by signal_features.

Hi, thank you for sharing your code. Can you provide more examples of models? Because the performance of A2C is not very good, I am trying to use my own data (including newly added signal_features) and experimenting with different models, but they are not performing well, especially the DQN model. This may be a silly question, please forgive my unfamiliarity with reinforcement learning. Additionally, I encountered errors when using the DDPG model. I have included my modifications and the error below. I would be grateful for your help.
` def _process_data(self, keys: List[str]=['Stochastic_K_1', 'Stochastic_D_1', 'Stochastic_K_2', 'Stochastic_D_2', 'MACD_DIF',
'MACD_DEA', 'MACD_Histogram', 'Moving_Average']) -> Dict[str, np.ndarray]:
signal_features = {}

    for symbol in self.trading_symbols:
        get_signal_at = lambda time: \
            self.original_simulator.price_at(symbol, time)[keys]

        if self.multiprocessing_pool is None:
            p = list(map(get_signal_at, self.time_points))
        else:
            p = self.multiprocessing_pool.map(get_signal_at, self.time_points)

        signal_features[symbol] = np.array(p)
    # data = self.prices
    signal_features = np.column_stack(list(signal_features.values()))
    return signal_features`
Snipaste_2023-08-01_09-58-09

Supported MacOS

I've check that there is no Metatrader 5 on pip, because it's support Windows OS only.
As I understand, it's impossible to use your module outside of Windows, cause it's contains required for Windows only module.
Maybe you could provide limited using of your module for MacOS / Linux without MetaTrader?
Thank you for your great job, @AminHP!

can i ask attributes' meaning of SymbolInfo class?

class LocalMtSimulator(MtSimulator):
    def download_data(self, symbol: str, df, symbol_info: SymbolInfo) -> None:
        """load dataframe from disk"""
        # symbol_df = pd.read_excel(path)
        self.symbols_info[symbol] = symbol_info
        self.symbols_data[symbol] = df

dataset = sqlite2df("data\\my\\kb8w.db", table_name='temp')
dataset['datetime'] = dataset['datetime'].apply(lambda x: bt.num2date(x))
dataset = dataset.rename(
        columns={"datetime": "Time", "open": "Open", "close": "Close", "high": "High", "low": "Low",
                 "volume": "Volume"})

sim = LocalMtSimulator(
        unit='CNY',
        balance=10000.,
        leverage=5,
        # stop_out_level=0.2,
        # hedge=True,

    )
sim.download_data('rb', dataset, symbol_info=SymbolInfo(
    info={
        "name": "rb",
        "path": "CTP\\rb",

        "currency_margin": 0,
        "currency_profit": 0,
        "trade_contract_size": 0,
        "margin_rate": 0.2,
        "volume_min": 0,
        "volume_max": 0,
        "volume_step": 0
    }

the above is code structure of how i load my own df, and when i fill symbolinfo class, i found i dont understand meaning of following parameters :
"currency_margin": 0,
"currency_profit": 0,
"trade_contract_size": 0,
"margin_rate": 0.2,
"volume_min": 0,
"volume_max": 0,
"volume_step": 0
can u explain these? Thank you.

Help! Need balance history at each reward calculation

Unsuccessfully trying to get a list of all balance values within each timestep inside of the reward method. So, at each call to _calculate_rewards(), I am trying to extract a list of all historical balances (for that timestep) - i.e. each call should add 1 historical balance.

I've tried calculating it within MtSimulator but this gets too complicated.

Poking around self.history but unable to successfully unwrap it for this.

Thanks

sim.download_data report error

Code:

sim = MtSimulator(
    unit='USD',
    balance=10000.,
    leverage=100.,
    stop_out_level=0.2,
    hedge=False,
)


sim.download_data(
    symbols=['EURUSD', 'GBPCAD', 'GBPUSD', 'USDCAD', 'USDCHF', 'GBPJPY', 'XAUUSD'],
    time_range=(
        datetime(2021, 5, 5, tzinfo=pytz.UTC),
        datetime(2021, 12, 5, tzinfo=pytz.UTC)
    ),
    timeframe=Timeframe.M5
)
sim.save_symbols("C:\\symbol.pkl")

Error:

  File "test.py", line 15, in <module>
    sim.download_data(
  File "D:\Porjects\rlmt\gym-mtsim-main\gym_mtsim\simulator\mt_simulator.py", line 59, in download_data
    si, df = retrieve_data(symbol, from_dt, to_dt, timeframe)
  File "D:\Porjects\rlmt\gym-mtsim-main\gym_mtsim\metatrader\api.py", line 20, in retrieve_data
    symbol_info = _get_symbol_info(symbol)
  File "D:\Porjects\rlmt\gym-mtsim-main\gym_mtsim\metatrader\api.py", line 53, in _get_symbol_info
    symbol_info = SymbolInfo(info)
  File "D:\Porjects\rlmt\gym-mtsim-main\gym_mtsim\metatrader\symbol.py", line 9, in __init__
    self.name: str = info.name
AttributeError: 'NoneType' object has no attribute 'name'

Question about volume

Hello @AminHP ,

Do you know why is the volume value always small? i noticed its usually in small numbers such as in the rough range [-5,+5]
I changed the volume to "Amount" but i didnt touch the action space (which should be [-inf,+inf]), i am wondering if the action space is close to inf, why am i getting very small numbers when the action is generated by gym?
I know i can mutiply or square the value, but was just wondering if i am missing something ? albeit i didnt change anything in the code.

Overriding signal_features

Hi Amin,

On gym-anytradnig it's possible to override _process_data and add your own custom indicators (from Finta etc).

Is it possible to do the same with mtsim - or how should I be thinking about this? Currently I can't even see how to add and format my own training data.

Pickle files

Hello @AminHP ,
What data do you store inside the pickle files in the data folder?

Symbol Data Download

Hi
how can I download new tick data for Symbols from MT5?
is there any way to get live tick data and test the model on it?

Thanks

Check if market is closed

Hi!

Any way to check if market is closed at a given date and time? Like holydays and weekends.

Thanks!

Feature Request: update from gym to gymnasium

Hi, this repository is currently listed in the gymnasium third party environments but we are cleaning the list up to only include maintained gymnasium-compatible repositories.

Would it be possible for it to be upgraded from gym to gymnasium? Gymnasium is the maintained version of openai gym and is compatible with current RL training libraries (rllib and tianshou have already migrated, and stable-baselines3 will soon).

For information about upgrading and compatibility, see migration guide and gym compatibility. The main difference is the API has switched to returning truncated and terminated, rather than done, in order to give more information and mitigate edge case issues.

Composite Spaces

Thanks to the author for the excellent repository. Helped me open up a new direction to explore.
I am interested in the issue of creating an environment. In practice, many traders use multiple time frames to decide whether to open a position. For example, I want to analyze the last 200 bars of the H4 time frame and the last 100 bars from the M5 time frame, and I also want to separately transfer bid and ask data to the environment. I wrote several indicators in MQL5 that upload all the necessary data into a sqlite database and it is convenient to use this data to create sequences that form the environment. The stable-baselines3 documentation states that it is possible to create a composite environment. Something like this:
self.observation_space = spaces.Dict({ 'SlowTF': spaces.Box(low=0, high=1, shape=cfg.slowTF_shape, dtype=np.float32), 'FastTF': spaces.Box(low=0, high=1, shape=cfg.fastTF_shape, dtype=np.float32), 'Tick': spaces.Box(low=0, high=1, shape=cfg.tick_shape, dtype=np.float32) })
How can this approach be implemented in gym-mtsim or perhaps in gym-anytrading?
The number of parameters in gym-mtsim, in my opinion, looks unnecessary. unit, balance, equity, margin, leverage, etc. only complicate the learning process and can be implemented directly in the metatrader5 terminal itself using MQL5. If the model is trained only to search for entry and exit points of a position, this will already be enough for its use. MQL5 has built-in support for the ONNX format. The trained model can be exported to ONNX format and used directly in the code of an advisor or indicator as an imported function.

Question: about _get_unit_ratio

Hello,
Thank you so much for the simple and wonderful library.
can you please explain what is this fucntion doing?
why do we need to get the unit ratio to calculate order profit?

    def _get_unit_ratio(self, symbol: str, time: datetime) -> float:
        symbol_info = self.symbols_info[symbol]
        if self.unit == symbol_info.currency_profit:
            return 1.

        if self.unit == symbol_info.currency_margin:
            return 1 / self.price_at(symbol, time)['Close']

        currency = symbol_info.currency_profit
        unit_symbol_info = self._get_unit_symbol_info(currency)
        if unit_symbol_info is None:
            raise SymbolNotFound(f"unit symbol for '{currency}' not found")

        unit_price = self.price_at(unit_symbol_info.name, time)['Close']
        if unit_symbol_info.currency_margin == self.unit:
            unit_price = 1. / unit_price

        return unit_price

Training with CSV

Hello, thanks for this great repo, I wonder, why did you use pkl files and not csv's?
and how can I replace the csv with my own data set to be in the form of csv or even metatrader files?

thanks

A Complete Example using stable-baselines

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
[<ipython-input-6-4ca9ec63cf36>](https://localhost:8080/#) in <module>()
     11 # env = gym.make('crypto-hedge-v0')
     12 
---> 13 model = A2C('MultiInputPolicy', env, verbose=0)
     14 model.learn(total_timesteps=1000)
     15 

2 frames
[/usr/local/lib/python3.7/dist-packages/stable_baselines3/common/base_class.py](https://localhost:8080/#) in __init__(self, policy, env, learning_rate, policy_kwargs, tensorboard_log, verbose, device, support_multi_env, create_eval_env, monitor_wrapper, seed, use_sde, sde_sample_freq, supported_action_spaces)
    189                 assert np.all(
    190                     np.isfinite(np.array([self.action_space.low, self.action_space.high]))
--> 191                 ), "Continuous action space must have a finite lower and upper bound"
    192 
    193     @staticmethod

AssertionError: Continuous action space must have a finite lower and upper bound

Where can set the lower and upper bound? Thank you

Error, with only import

Hello,

I recently discovered this project and was eager to try it out. However, after installing the project using pip, attempting to import it immediately resulted in an error. The error message is as follows:

Traceback (most recent call last):
File "/mnt/c/Users/franc/Documents/GitHub/GCollab_Lstm_Renforcement/test.py", line 1, in
from gym_mtsim import OrderType
File "/home/osdann/.local/lib/python3.10/site-packages/gym_mtsim/init.py", line 13, in
'original_simulator': MtSimulator(symbols_filename=FOREX_DATA_PATH, hedge=True),
File "/home/osdann/.local/lib/python3.10/site-packages/gym_mtsim/simulator/mt_simulator.py", line 42, in init
if not self.load_symbols(symbols_filename):
File "/home/osdann/.local/lib/python3.10/site-packages/gym_mtsim/simulator/mt_simulator.py", line 73, in load_symbols
self.symbols_info, self.symbols_data = pickle.load(file)
File "/home/osdann/.local/lib/python3.10/site-packages/pandas/core/internals/blocks.py", line 2728, in new_block
return klass(values, ndim=ndim, placement=placement, refs=refs)
TypeError: Argument 'placement' has incorrect type (expected pandas._libs.internals.BlockPlacement, got slice)".

I am using WSL 2 and have set up a new Conda environment specifically for this.
So far, I have only installed the latest version of mtsim and bs3.

Could there be an issue on my end ? Any help would be greatly appreciated.

Thank you.

Stop Loss & Take Profit

Hi @AminHP I would appreciate if you could provide example on how to implement stop loss and take profit just like in metatrader5.

Thanks,

questions on _process_data(), _get_observation() and multi symbols

1 The _process_data() method takes the return value of _get_prices() as a feature, but the return value of _get_prices() is two columns of bar data which are close and open, and this are not features. Why?
2 _get_observation() returns a dict, but not a np.ndarray, for a typicle gym env, Shouldn't the observation returned by the step method be a np.ndarray?
3 I see many data structures in this framework use Dict, which should be to support multiple symbols? I only load data of a single symbol, so should I change the underlying data structure,? or just put a single key (the single symbol i focus on) in the dict.

BUG: sim._check_volume

hello @AminHP ,
There is a bug in the check volume function inside the simulator, you write :

 def _check_volume(self, symbol: str, volume: float) -> None:
        symbol_info = self.symbols_info[symbol]
        if not (symbol_info.volume_min <= volume <= symbol_info.volume_max):
            raise ValueError(
                f"'volume' must be in range [{symbol_info.volume_min}, {symbol_info.volume_max}]"
            )
        if not round(volume / symbol_info.volume_step, 6).is_integer():
            raise ValueError(f"'volume' must be a multiple of {symbol_info.volume_step}")

you are rounding the volume to 6 decimals and expecting an integer as a result, this can only be true if the passed volume was an integer, your error message says mutiple of 0.01 (volume step), this also contradicts with your voume check function inside env._get_modified_volume where you expect volume to be mutiple of 0.01 as well.

example: environment with custom parameters

this example does not open any trades

balance: 10000.0, equity: 10000.0, margin: 0.0
free_margin: 10000.0, margin_level: inf

just different from expected results you wrote

issue with multi processing

Hello @AminHP ,
when running one of your examples (Create an environment with custom parameters), i get the follwoing error:
File "", line 1, in
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\spawn.py", line 116, in spawn_main
exitcode = _main(fd, parent_sentinel)
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\spawn.py", line 125, in _main
prepare(preparation_data)
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\spawn.py", line 236, in prepare
_fixup_main_from_path(data['init_main_from_path'])
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\spawn.py", line 287, in _fixup_main_from_path
main_content = runpy.run_path(main_path,
File "C:\Program Files\Python39\lib\runpy.py", line 268, in run_path
return _run_module_code(code, init_globals, run_name,
File "C:\Program Files\Python39\lib\runpy.py", line 97, in _run_module_code
_run_code(code, mod_globals, init_globals,
File "C:\Program Files\Python39\lib\runpy.py", line 87, in _run_code
exec(code, run_globals)
File "c:\Users\Ali.Khankan\Desktop\gym-mtsim\ali_test.py", line 16, in
env = MtEnv(
File "c:\Users\Ali.Khankan\Desktop\gym-mtsim\gym_mtsim\envs\mt_env.py", line 63, in init
self.multiprocessing_pool = Pool(multiprocessing_processes) if multiprocessing_processes else None
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\pathos\multiprocessing.py", line 111, in init
self._serve()
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\pathos\multiprocessing.py", line 123, in _serve
_pool = Pool(nodes)
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\pool.py", line 212, in init
self._repopulate_pool()
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\pool.py", line 303, in _repopulate_pool
return self._repopulate_pool_static(self._ctx, self.Process,
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\pool.py", line 326, in _repopulate_pool_static
w.start()
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\process.py", line 121, in start
self._popen = self._Popen(self)
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\context.py", line 327, in _Popen
return Popen(process_obj)
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\popen_spawn_win32.py", line 45, in init
prep_data = spawn.get_preparation_data(process_obj._name)
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\spawn.py", line 154, in get_preparation_data
_check_not_importing_main()
File "C:\Users\Ali.Khankan\AppData\Roaming\Python\Python39\site-packages\multiprocess\spawn.py", line 134, in _check_not_importing_main
raise RuntimeError('''
RuntimeError:
An attempt has been made to start a new process before the
current process has finished its bootstrapping phase.

    This probably means that you are not using fork to start your
    child processes and you have forgotten to use the proper idiom
    in the main module:

        if __name__ == '__main__':
            freeze_support()
            ...

    The "freeze_support()" line can be omitted if the program
    is not going to be frozen to produce an executable.

setting multiprocessing_processes = 0 solved the problem

Should use open price instead of close price

Hi HP,

Thanks for ur great project, just a thought on line 85 of mt_simulator.py

    for order in self.orders:
        order.exit_time = self.current_time
        order.exit_price = self.price_at(order.symbol, order.exit_time)['Close'] # Should be Open price
        self._update_order_profit(order)
        self.equity += order.profit

I think you meant open price? because the candlestick is timed at the open time of the bar.

The example provided using the same dataset for learning and testing?

The example you provided uses the same dataset for both training and testing? In order to evaluate the performance of a machine learning model, we need to separate the dataset into training and testing sets? Can you show us how to evaluate the trained model in unseen dataset? thanks

Hi, can i define my own action space?

for example , my action space has 7 discrete actions, and they correspond to 7 different positions as follows:
heavy long postion (80% long position size)
mid long postion (50% long position size)
light long postion (30% long position size)
not holding any symbols, meaning position size is 0
light short postion (30% short position size)
mid short postion (50% short position size)
heavy short postion (80% short position size)

In order to achieve this, what should I do?

pyfolio

hi Mohamad,
I prepared an example of using pyfolio to analyze results, but it require small changes to env and sim.
if you want I can send you the changes to push.
best,
ofer

Error running using the environment

Hi
Could i please be helped with this issue:

Traceback (most recent call last):
File "c:\Users\seanm\OneDrive\Desktop\Test\test.py", line 2, in
import gym_mtsim
File "C:\Users\seanm\OneDrive\Desktop\Test\sean\lib\site-packages\gym_mtsim-1.3.0-py3.10.egg\gym_mtsim_init_.py", line 13, in
'original_simulator': MtSimulator(symbols_filename=FOREX_DATA_PATH, hedge=True),
File "C:\Users\seanm\OneDrive\Desktop\Test\sean\lib\site-packages\gym_mtsim-1.3.0-py3.10.egg\gym_mtsim\simulator\mt_simulator.py", line 37, in init
if not self.load_symbols(symbols_filename):
File "C:\Users\seanm\OneDrive\Desktop\Test\sean\lib\site-packages\gym_mtsim-1.3.0-py3.10.egg\gym_mtsim\simulator\mt_simulator.py", line 73, in load_symbols
self.symbols_info, self.symbols_data = pickle.load(file)
File "C:\Users\seanm\OneDrive\Desktop\Test\sean\lib\site-packages\pandas-2.1.1-py3.10-win-amd64.egg\pandas\core\internals\blocks.py", line 2400, in new_block
return klass(values, ndim=ndim, placement=placement, refs=refs)
TypeError: Argument 'placement' has incorrect type (expected pandas._libs.internals.BlockPlacement, got slice)

i suspected pandas , tried to uninstall the one i had and used the setup.py but still same problem

please help

Incompatibility with Linux systems

One of the core dependency, MetaTrader5 is only available to Windows but not Linux. This causes the gym-mtsim to be unable to run on Linux machines.

[Question] - Why different results each time loaded model is run?

Any ideas why the code below (it is basically the same as the code in the README.md file) would return different results each time it is run after the model has been trained? The trained model is no longer being updated and the input (trades) do not change between runs.

`
model = A2C.load(modelName, env)

observation = env.reset()
while True:
    action, _states = model.predict(observation)
    observation, reward, done, info = env.step(action)
    
    state = env.render()
    z = calcsomevalue(state)
    if done:
       x  = getsomevalue(z)
       y = calsomevalue(z)
        print(<results>)
        break

`

how to load dataframe in mtsim?

Just like the parameter 'df' of Env in anytrading, how can I load my own dataframe in MtSim . I see in the example that only the .pkl file is loaded

how can I pass through the observation_space as Dict into Keras DQN model

I have tried many time and method for this. I have writen the code below, could you please check for me whether I have done something wrong. In this case I left only this 2 informations for the experiment

model_balance = Sequential()
model_balance.add(Flatten(input_shape=(1,1,1), name='balance'))
model_balance_input = Input(shape=(1,1), name='balance')
model_balance_encoded = model_balance(model_balance_input)

model_equity = Sequential()
model_equity.add(Flatten(input_shape=(1,1,1), name='equity'))
model_equity_input = Input(shape=(1,1), name='equity')
model_equity_encoded = model_Equity(model_equity_input)

con = concatenate([model_Balance_encoded, model_Equity_encoded])

dense = Dense(1024, activation='relu')(con)
dense = Dense(1024, activation='relu')(dense)
output = Dense(actions, activation='softmax')(dense)

model = Model(inputs=[model_Balance_input, model_Equity_input], outputs=output)

memory = SequentialMemory(limit=2000000, window_length=1)
policy = LinearAnnealedPolicy(EpsGreedyQPolicy())
dqn = DQNAgent(model=model, policy=policy, nb_actions=actions, memory=memory)

dqn.processor = MultiInputProcessor(2)

dqn.compile( optimizer = Adam(learning_rate=1e-4), metrics = ['mse'] #'accuracy', 'mae')

dqn.fit(env, nb_steps=1000, verbose=1, visualize=False,)

====================================================================
Then, this problem occured, and I could not find the error.
9966

please tell me the solution.
thanks in advance

Leverage as part of action space

Hello @AminHP ,
I noticed that you are using a constant leverage of 100 during the construction of the MtSimulator object, as you know leverage could be variant on the order level, means every order could have a different leverage, did you think of making leverage as part of the action space?
I was thinking that allowing the agent to decide on the leverage (1x,5x,10x...etc) could allow it to make better decisions?

Fee is not the same as spread

It appears you have not considered the fee per lot charged on some asset classes by some brokers. Eg it is common in my experience for there to be zero commission on indices but a commission per lot per round turn of $6-7...

understanding action_space

Hello @AminHP ,
I am struggeling to understand your action space, you say quote: "
The action space is a 1D vector of size count(trading_symbols) * (symbol_max_orders + 2). For each symbol, two types of actions can be performed, closing previous orders and placing a new order. The latter is controlled by the first symbol_max_orders elements and the former is controlled by the last two elements."

you also write:

self.action_space = spaces.Box(
            low=-np.inf, high=np.inf,
            shape=(len(self.trading_symbols) * (self.symbol_max_orders + 2),)
        )  #

why do you +2 to the symbole_max_orders? what are the last 2 elements you are referring to?

A question about calculate balance

In my opinion, balance mean to cash. So when open a new order, the balance should minus cash used in orders. But in _create_hedged_order function, not update balance. It's a bug or I have misunderstand?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.