Вы упускаете торговые возможности:
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Регистрация
Вход
Вы принимаете политику сайта и условия использования
Если у вас нет учетной записи, зарегистрируйтесь
Я поддержу топик стартера, я много раз брался за mql как 4 так и 5, и скажу что лично у меня мало желания изучать язык который мне пригодится только тут в торговле, ...
Ну это вы зря или из-за непонимания проблемы. Неужели алгоритмы индикаторов будете писать заново. Это все равно что добираться из Волгограда до Москвы через Владивосток. Еще адын нюанс, все индикаторы учитывают историю. Т.е. если вы возьмете данные (бары) за месяц, рассчитаете индикатор по своему алгоритму и сравните с Терминалом, то ваши расчеты будут отличаться.
Мне тоже не понятна фраза:
Исходя с написанного, получается что есть большая прослойка знающих Python :) Ну прям открыл редактор и уже знаешь питон - это-же так просто, а открыл mql - нифига не знаешь.
При этом, назвать mql который полностью ориентирован на платформу "устаревшим" инструментом ... питон создан в 1991, а это намного раньше
То, что Я увидел в этой ветке написанное на питоне, очень просто реализуется на mql
---
Не, ну как-бы для общего развития тема интересна, но не более.
Более того - то что тс понаписал - вообще никак не использует преимущества Питона и по сути совсем не "Питон-стайл". Это как гвозди забивать шуруповертом, молоток то устарел
подозреваю, что из столовых приборов в Вашем доме - только ложки, хочешь щи хлебай, а хочешь кашу жуй, да и довольно безопасно в обращении
)))
нравится, используйте Python , но только не как топикстартер - не создавайте свои новые пользовательские типы данных - бары и т.п., не пишите самостоятельно расчет МАшки...а используйте уже готовые решения, иначе смысла в использовании этого языка нет, ведь с таким же успехом можно и свои пакеты для работы с нейросетями можно взять и написать ;)
Да уж) а кстати на Питоне неужели нет ещё библы по всяким вот таким около-трейдерским функциям, или если в пару строк можешь написать полный стат.анализ ряда, то МА-шка уже не нужна..)
Да уж) а кстати на Питоне неужели нет ещё библы по всяким вот таким около-трейдерским функциям, или если в пару строк можешь написать полный стат.анализ ряда, то МА-шка уже не нужна..)
The Best 101 Python algorithmic-trading Libraries | PythonRepo
Не смог вчера вернуться к форуму. Продолжу. Завёл новый демосчёт, и на нём наименования инструментов без '_i' в конце, так что поправил код в этой части.
Продолжу постепенное и медленное движение, своего рода микроучебник, для тех, кому, может, пригодится.
Введу переменную n (маленькую) - пусть это будет, так сказать, длина окна. То есть в файлах с ценами N отсчётов, например 1000, а n зададим, например, 10, или 100, или 288 (сутки, если таймфрейм М5).
Переменная d у меня означает сдвиг этого окна по времени, в прошлое. Зададим d = 0.
Будем для примера выводить по 10 отсчётов цен закрытия и SMA порядка 101 (запаздывающей на 50 интервалов между отсчётами):
Результат работы:
Научимся опрашивать терминал о текущих, висящих в рынке, так сказать, сделках по инструментам.
Введу функцию
def buy_or_sell_opredelitel(position): try: tp = position.tp sl = position.sl if tp > sl: return 'buy' elif sl > tp: return 'sell' else: return None except: return None def Nsell_Nbuy_calculator(current_positions): ''' Функция принимает перечень открытых позиций. Функция возвращает кортеж (Nsell, Nbuy) с количествами позиций sell и buy, соответственно ''' Nsell = 0 Nbuy = 0 if len(current_positions) == 0: return (Nsell, Nbuy) else: for position in current_positions: if buy_or_sell_opredelitel(position) == 'sell': Nsell += 1 elif buy_or_sell_opredelitel(position) == 'buy': Nbuy += 1 return (Nsell, Nbuy)Пусть они принимает перечень открытых позиций по инструменту, и возвращает кортеж, вижа (количество сделок на продажу, количество сделок на покупку).
В функцию main добавим такой фрагмент перед функцией terminal_done()
# запрос открытых сделок по инструменту for instrument in instruments: current_positions_for_instrument = mt5.positions_get(symbol = instrument) Nsell_Nbuy = Nsell_Nbuy_calculator(current_positions_for_instrument) print(date_time.now().nice_view, ' - по {} открытых сделок sell {}, открытых сделок buy {}'.format(instrument, Nsell_Nbuy[0], Nsell_Nbuy[1]))Результат работы (n уменьшил до 5 для красоты вывода):
Предлагаю начинать собственно открытие сделок. Типа: если цена больше SMA на сколько-то: открыться на продажу, если ниже SMA на сколько-то - открыться на покупку.
Предлагаю ещё пару функций: чтобы открытые сделки контролировать на предмет наличия у них SL и TP, если их нет - закрывать, и функцию, возвращающую текущий результат (прибыль или убыток), в пипсах.
Не забыть как-то обрабатывать ситуацию, когда ордер уже в истории и его нет в активных, а позиции ещё нет, и не отправлять в этом случае ордер на открытие повторно.
Такая стиуация время от времени возникает и, например, работа через Trade.mqh эту проблему не решает (как минимум, не решала раньше - я от Trade.mqh давно отказался).
Бывает и наоборот - ордер на закрытие в уже истории, а позиция ещё видна.
Не забыть как-то обрабатывать ситуацию, когда ордер уже в истории и его нет в активных, а позиции ещё нет, и не отправлять в этом случае ордер на открытие повторно.
Такая стиуация время от времени возникает и, например, работа через Trade.mqh эту проблему не решает (как минимум, не решала раньше - я от Trade.mqh давно отказался).
Бывает и наоборот - ордер на закрытие в уже истории, а позиция ещё видна.
Я показал как опрашивать терминал на предмет открытых позиций. Ничто не мешает написать код так, что если позиция уже есть, то не отправлять запрос повторно, если нет - отправить. Тем более что по итогам отправки запроса возвращается объект, у которого всё есть в свойствах, можно посмотреть, штатно ли он открылся, или там какая ошибка прописана, и исходя из этого действовать. Никаких проблем здесь нет.
The Best 101 Python algorithmic-trading Libraries | PythonRepo
Уже 104) Здесь пишется 105-я)
Полный код файла TradeLogic.py к настоящему моменту (да, он примитивный, но уверен, для некоторых может открыть дверь в торговлю с использованием Метатрейдер 5 и Python)
import os, time import datetime as dt from json import loads, dump from random import randint import numpy as np from Classes import date_time, Bar import Functions import MetaTrader5 as mt5 N = 1000 # количество отсчётов, сохраняемых в файлах котировок n = 5 d = 0 price_SMA_razn_Level = 10 # модуль разности между ценой и SMA, при превышении этого порога открываем сделку N_sd_sell_max = 2 # максимальное количество сделок типа sell по одному инструменту N_sd_buy_max = 2 # максимальное количество сделок типа buy по одному инструменту SL = 50; TP = 50 # уровни SL и TP, в пипсах volume = 0.01 # объём сделок account_demo = ['Alpari-MT5-Demo', 51056680, 'pxav2sxs'] work_account = account_demo work_catalog = 'Z:\\fx_for_forum\\fx\\' instruments = ['EURUSD', 'GBPUSD', 'EURGBP', 'USDCHF', 'USDCAD', 'USDJPY'] def terminal_init(path_to_terminal, account): ''' Функция осуществляет соединение с терминалом MT5 ''' if mt5.initialize(path_to_terminal, server=account[0], login=account[1], password=account[2]): str1 = ' - соединение с терминалом {} билд {} установлено' print(date_time.now().nice_view, str1.format(mt5.terminal_info().name, mt5.version()[1])) return True else: print(date_time.now().nice_view, ' - соединение с терминалом установить не удалось') return False def terminal_done(): ''' Функция завершает соединение с терминалом MT5 ''' try: mt5.shutdown() print('\n' + date_time.now().nice_view, ' - соединение с терминалом успешно завершено') except: print('\n' + date_time.now().nice_view, ' - соединение с терминалом завершить не удалось') def dt_stamp_from_M5_view(M5_view): return date_time(int(M5_view[0:4]), int(M5_view[4:6]), int(M5_view[6:8]), int(M5_view[8:10]), int(M5_view[10:12])) def pips_definer(instrument): ''' Функция возвращает величину пипса для инструмента, например для EURUSD - 0.0001, для USDJPY - 0.01 ''' if instrument in ['EURUSD', 'GBPUSD', 'EURGBP', 'USDCHF', 'USDCAD']: return 0.0001 elif instrument in ['USDJPY']: return 0.01 else: return None def save_prices_to_file(instrument, n=100): ''' Функция принимает инструмент, количество отсчётов цены n в таймфрейме М5, и сохраняет в файл (только сформировавшиеся полностью бары, время соответствует времени закрытия (!) бара) ''' w = pips_definer(instrument) tochn = int(abs(np.log10(w))+1) path_file = os.path.join(work_catalog, instrument+'.txt') bars = mt5.copy_rates_from_pos(instrument, mt5.TIMEFRAME_M5, 0, n+1) # в случае ошибки mt5.copy_rates_from_pos возвращает None try: f = open(path_file, 'w') for i in range(0, n+1): bar = bars[i] dt_stamp = date_time.fromtimestamp(bar[0], dt.timezone.utc) + dt.timedelta(hours=1, minutes=5) open_ = str(round(bar[1], tochn)) high_ = str(round(bar[2], tochn)) low_ = str(round(bar[3], tochn)) close_ = str(round(bar[4], tochn)) if i != n: f.write(dt_stamp.M5_view + ' ' + open_ + ' ' + high_ + ' ' + low_ + ' ' + close_ + '\n') f.close() print(date_time.now().nice_view, ' - котировки {} успешно записаны в файл'.format(instrument)) except: print(date_time.now().nice_view, ' - котировки {} записать в файл не удалось'.format(instrument)) def read_file_with_prices(catalog, instrument): ''' Функция принимает каталог, и наименование инструмента, читает в указанном каталоге файл с ценами для указанного инструмента (формат данных: отсчёт времени в виде строки формата M5_view, open, high, low, close), проверяет цены на целостность данных. Функция возвращает или массив ndarray (если всё успешно), или строку 'no data' (если данные не прочитались, или неполны) ''' path_file = os.path.join(catalog, instrument+'.txt') try: f = open(path_file, 'r') ndarray___with_prices_for_instrument = np.loadtxt(f, delimiter=' ', dtype='float64') f.close() except: print(date_time.now().nice_view + ' - Файл с котировками для {} прочитать не удалось'.format(instrument)) return 'no data' # проверка данных на целостность timedelta_5 = dt.timedelta(minutes=5) nx = ndarray___with_prices_for_instrument.shape[0] for i in range(1, nx): dt_stamp_i = dt_stamp_from_M5_view(str(int(ndarray___with_prices_for_instrument[i, 0]))) dt_stamp_im1 = dt_stamp_from_M5_view(str(int(ndarray___with_prices_for_instrument[i-1, 0]))) t_delta = dt_stamp_i - dt_stamp_im1 if t_delta != timedelta_5: if t_delta >= dt.timedelta(hours=47) and t_delta <= dt.timedelta(hours=50): pass # разрыв данных с вечера пятницы до понедельника else: t1 = ' - Котировки для {} неполны, и не будут переданы на выполнение расчётов\nИменно - ошибка при {}' print((date_time.now().nice_view + t1).format(instrument, dt_stamp_im1.M5_view)) return 'no data' return ndarray___with_prices_for_instrument def read_all_files_with_prices(catalog, instruments): ''' Функция принимает каталог, и список инструментов, читает в указанном каталоге файлы с ценами для указанных инструментов с использованием функции read_file_with_prices. Функция возвращает словарь prices_for_all_instruments с котировками по всем инструментам. В этом словаре ключи - наименования инструментов, значения - словари с котировками по одному соответствующему инструменту. В словарях с котировками по одному инструменту ключи - отсчёты даты-времени (класс date_time), значения - бары (класс bar), кроме того предусмотрены ключи: - 'is_data' - значение по ключу - True, если котировки в наличии, False - если функция чтения из файла вернула 'no data', - 'instrument' - значение по ключу - инструмент (str); - 'prices' - значение по ключу - массив котировок по инструменту (numpy.ndarray) ''' dict___with_prices_for_all_instruments = {} for instrument in instruments: w = pips_definer(instrument) ndarray___with_prices_for_instrument = read_file_with_prices(catalog, instrument) dict___with_prices_for_instrument = {} if type(ndarray___with_prices_for_instrument) == type('no data'): dict___with_prices_for_instrument['is_data'] = False else: dict___with_prices_for_instrument['is_data'] = True dict___with_prices_for_instrument['instrument'] = instrument dict___with_prices_for_instrument['prices'] = ndarray___with_prices_for_instrument for i in range(0, ndarray___with_prices_for_instrument.shape[0]): dt_stamp = dt_stamp_from_M5_view(str(int(ndarray___with_prices_for_instrument[i, 0]))) dict___with_prices_for_instrument[dt_stamp] = Bar(instrument, 5, dt_stamp, ndarray___with_prices_for_instrument[i, 1], ndarray___with_prices_for_instrument[i, 3], ndarray___with_prices_for_instrument[i, 2], ndarray___with_prices_for_instrument[i, 4], w) dict___with_prices_for_all_instruments[instrument] = dict___with_prices_for_instrument return dict___with_prices_for_all_instruments def open_trade(buy_or_sell, instr, volume, price_in, price_sl, price_tp, w, deviation_in_pips): ''' Функция осуществляет проверку: не ушёл ли текущий уровень цены от ожидаемого уровня price_in, и если всё хорошо, совершает открытие сделки ''' dopustimaya_delta_ot_price_in_in_pips = 5 if buy_or_sell == 'buy': trade_type = mt5.ORDER_TYPE_BUY price = mt5.symbol_info_tick(instr).ask comment = 'buy via Python' elif buy_or_sell == 'sell': trade_type = mt5.ORDER_TYPE_SELL price = mt5.symbol_info_tick(instr).bid comment = 'sell via Python' if abs((price - price_in)/w) < dopustimaya_delta_ot_price_in_in_pips: trade_request = {'action': mt5.TRADE_ACTION_DEAL, 'symbol': instr, 'volume': volume, 'type': trade_type, 'price': price, 'sl': price_sl, 'tp': price_tp, 'deviation': deviation_in_pips*10, 'magic': 12345, 'comment': comment, 'type_time': mt5.ORDER_TIME_GTC, 'type_filling': mt5.ORDER_FILLING_FOK} # отправление торгового запроса на сервер result = mt5.order_send(trade_request) # print(result) if result.comment == 'Request executed': if buy_or_sell == 'buy': print(date_time.now().nice_view, ' - сделка на покупку {} открыта'.format(instr)) elif buy_or_sell == 'sell': print(date_time.now().nice_view, ' - сделка на продажу {} открыта'.format(instr)) else: if buy_or_sell == 'buy': print(date_time.now().nice_view, ' - сделку на покупку {} открыть не удалось'.format(instr)) elif buy_or_sell == 'sell': print(date_time.now().nice_view, ' - сделку на продажу {} открыть не удалось'.format(instr)) return result else: print(date_time.now().nice_view, ' - цена ушла от ожидаемой >5 пипс, сделку по {} не открываю'.format(instr)) return None def close_trade(position): ''' Функция осуществляет закрытие сделки ''' instr = position.symbol if position.tp > position.sl: # buy trade_type = mt5.ORDER_TYPE_SELL # если позиция buy, то для её закрытия нужно sell price = mt5.symbol_info_tick(instr).bid elif position.sl > position.tp: # sell trade_type = mt5.ORDER_TYPE_BUY # если позиция sell, то для её закрытия нужно buy price = mt5.symbol_info_tick(instr).ask position_id = position.ticket volume = position.volume trade_request = {'action': mt5.TRADE_ACTION_DEAL, 'symbol': instr, 'volume': volume, 'type': trade_type, 'position': position_id, 'price': price, #'deviation': deviation_in_pips*10, 'magic': position.magic, 'comment': 'close via python', 'type_time': mt5.ORDER_TIME_GTC, 'type_filling': mt5.ORDER_FILLING_FOK} # отправление торгового запроса на сервер result = mt5.order_send(trade_request) if result.comment == 'Request executed': print(date_time.now().nice_view, ' - сделка {} по {} закрыта'.format(position_id, instr)) return result def buy_or_sell_opredelitel(position): try: tp = position.tp sl = position.sl if tp > sl: return 'buy' elif sl > tp: return 'sell' else: return None except: return None def Nsell_Nbuy_calculator(current_positions): ''' Функция принимает перечень открытых позиций. Функция возвращает кортеж (Nsell, Nbuy) с количествами позиций sell и buy, соответственно ''' Nsell = 0 Nbuy = 0 if len(current_positions) == 0: return (Nsell, Nbuy) else: for position in current_positions: if buy_or_sell_opredelitel(position) == 'sell': Nsell += 1 elif buy_or_sell_opredelitel(position) == 'buy': Nbuy += 1 return (Nsell, Nbuy) def SL_TP_control(instrument, current_positions): ''' Функция принимает перечень открытых позиций. Функция осуществляет контроль выставленности SL и TP у открытых позиций, в случае обнаружения сделки без SL или TP - закрывает (пытается закрыть) все сделки из полученного перечня. ''' Nsell_Nbuy = Nsell_Nbuy_calculator(current_positions) if Nsell_Nbuy[0] > 0 or Nsell_Nbuy[1] > 0: print(date_time.now().nice_view, ' - осуществляю проверку выставленности TP и SL у открытых сделок по {}'.format(instrument)) print(date_time.now().nice_view, ' - открытых сделок sell {}, открытых сделок buy {}'.format(Nsell_Nbuy[0], Nsell_Nbuy[1])) s = 0 for i in range(len(current_positions)): try: if current_positions[i].tp > current_positions[i].sl or current_positions[i].sl > current_positions[i].tp: s += 1 except: print(date_time.now().nice_view, ' - обнаружена сделка по {} с отсутствующим SL или TP - закрываю!'.format(instrument)) close_trade(current_positions[i]) time.sleep(0.5) if s == len(current_positions): print(date_time.now().nice_view, ' - выставленность TP и SL у открытых сделок по {} - OK'.format(instrument)) def profit_calculator(instrument, position): ''' Функция принимает позицию, и возвращает текущий результат, в пипсах. ''' w = pips_definer(instrument) buy_sell = buy_or_sell_opredelitel(position) if buy_sell == 'sell': bs = -1 elif buy_sell == 'buy': bs = 1 if buy_sell == 'buy' or buy_sell == 'sell': profit_in_pips = round((position.price_current - position.price_open)/w, 1)*bs return profit_in_pips return None def main(N): ''' Главная функция, обеспечивающая в бесконечном цикле связь с терминалом, сохранение котировок, и запуск функции, осуществляющей торговлю ''' dt_start = date_time.now() dt_write = dt_stamp_from_M5_view(dt_start.M5_view) + dt.timedelta(minutes=5, seconds=10) print('\n' + dt_start.nice_view, ' - начал работу, бездействую до ' + dt_write.nice_view + '\n') timedelta_sleep = dt_write - date_time.now() time.sleep(timedelta_sleep.seconds) while True: # установка соединения с MetaTrader5 if terminal_init(os.path.join('C:\\', 'Program Files', 'Alpari MT5', 'terminal64.exe'), work_account): # запись цен в файлы: для всех инструментов, N отсчётов if (dt.datetime.now() - dt_write) < dt.timedelta(seconds=10): print('\n' + date_time.now().nice_view + ' - начинаю запись цен в файлы для всех инструментов') for instrument in instruments: save_prices_to_file(instrument, N) # пауза 5 секунд после записи файлов с ценами, возможно необходимых для открытия в Mathcad, # или ещё зачем, если в них нет нужды, желающие могут переписать код, исключив бессмысленную # последовательность записи файлов, и затем чтения файлов, сразу сформировав нужную им структуру данных time.sleep(5) # определяем значение последнего отсчёта времени, который должен быть в файлах котировок, если они актуальны # (в выходные дни это будет не так, а в будни, пока рынок открыт - так) fx_dt_stamp_now = dt_stamp_from_M5_view(date_time.now().M5_view) print('\n\n'+date_time.now().nice_view, ' - начинаю вычисления для отсчёта времени {}'.format(fx_dt_stamp_now.M5_view)) # получаем словарь с ценами для всех инструментов dict___with_prices_for_all_instruments = read_all_files_with_prices(work_catalog, instruments) # вычислим значения последних n отсчётов цен закрытия баров, и SMA101, вычисленной по ценам закрытия баров, # если цена выше SMA на price_SMA_razn_Level, в пипсах, и открытых сделок на продажу по этому инструменту меньше, # чем N_sd_sell_max, то открываем сделку на продажу, # если цена ниже SMA на price_SMA_razn_Level, в пипсах, и открытых сделок на покупку по этому инструменту меньше, # чем N_sd_buy_max, то открываем сделку на покупку. for instrument in instruments: dict___with_prices_for_instrument = dict___with_prices_for_all_instruments[instrument] current_positions_for_instrument = mt5.positions_get(symbol = instrument) Nsell_Nbuy = Nsell_Nbuy_calculator(current_positions_for_instrument) if dict___with_prices_for_instrument['is_data']: w = pips_definer(instrument) # величина пипса для инструмента tochn = int(abs(np.log10(w))+1) AData = dict___with_prices_for_instrument['prices'] Ax_close = AData[:, 4] # столбец отсчётов цен закрытия в N строк A_close = Ax_close[Ax_close.size-n-d:Ax_close.size-d] # столбец отсчётов цен закрытия в n строк z = 50 # параметр, задающий запаздывание скользящей средней, для z=50 скользящая средняя по 1+2*50=101 отсчёту A_close_s = Functions.SMA(Ax_close, 1+2*z, 0, n, d) price_SMA_razn_for_instrument = round((A_close[n-1] - A_close_s[n-1])/w, 1) print('\n') str1 = ' - для {} крайние справа отсчёты цены {}, SMA {}, разность {} пипс' print(date_time.now().nice_view, str1.format(instrument, A_close[n-1], round(A_close_s[n-1], tochn), price_SMA_razn_for_instrument)) if abs(price_SMA_razn_for_instrument) < price_SMA_razn_Level: print(date_time.now().nice_view, ' - разность с SMA не превышает порога {} пипс, ничего не делаю'.format(price_SMA_razn_Level)) elif abs(price_SMA_razn_for_instrument) > price_SMA_razn_Level: if price_SMA_razn_for_instrument > price_SMA_razn_Level and Nsell_Nbuy[0] < N_sd_sell_max: print(date_time.now().nice_view, ' - разность с SMA превышает порог {} пипс, продаю {}'.format(price_SMA_razn_Level, instrument)) open_trade('sell', instrument, volume, A_close[n-1], A_close[n-1]+SL*w, A_close[n-1]-TP*w, w, 2) time.sleep(1) if price_SMA_razn_for_instrument < -price_SMA_razn_Level and Nsell_Nbuy[1] < N_sd_buy_max: print(date_time.now().nice_view, ' - разность с SMA превышает порог {} пипс, покупаю {}'.format(price_SMA_razn_Level, instrument)) open_trade('buy', instrument, volume, A_close[n-1], A_close[n-1]-SL*w, A_close[n-1]+TP*w, w, 2) time.sleep(1) # запрос открытых сделок по инструменту print('\n') for instrument in instruments: current_positions_for_instrument = mt5.positions_get(symbol = instrument) Nsell_Nbuy = Nsell_Nbuy_calculator(current_positions_for_instrument) print(date_time.now().nice_view, ' - по {} открытых сделок sell {}, открытых сделок buy {}'.format(instrument, Nsell_Nbuy[0], Nsell_Nbuy[1])) # завершение соединения с MetaTrader5 terminal_done() # определение параметров ожидания до следующего выполнения тела цикла dt_start = date_time.now() dt_write = dt_stamp_from_M5_view(dt_start.M5_view) + dt.timedelta(minutes=5, seconds=10) timedelta_sleep = dt_write - dt_start print(date_time.now().nice_view + ' - засыпаю до {}\n\n\n\n\n'.format(dt_write.nice_view)) time.sleep(timedelta_sleep.seconds) if __name__ == '__main__': main(N)Результат работы:
З.Ы. Показанные самые примитивные, элементарные, кусочки кода, на мой взгляд, уже достаточно ясно демонстрируют, что нетрудно реализовать абсолютно любую логику, любые вычисления, сравнения, контроль открытых сделок по различным критериям, автозакрытие их при некоторых условиях, ведение логов, как в командной строке, так и записывая в файлы, и т.д. и т.п.- всё что угодно без каких бы то ни было ограничений.
З.Ы.2 До сих пор не использовал класс Sdelka, а вообще-то удобно передавать в функции объекты этого класса, записывать их в файлы как .json, и т.д., - но как-то малость устал, думаю желающие разберутся, могу помочь тут с чем-то. При желании можно прикрутить графический интерфейс.
Всем настоятельно рекомендую торговлю с Python + библиотека metatrader5.
Я показал как опрашивать терминал на предмет открытых позиций. Ничто не мешает написать код так, что если позиция уже есть, то не отправлять запрос повторно, если нет - отправить. Тем более что по итогам отправки запроса возвращается объект, у которого всё есть в свойствах, можно посмотреть, штатно ли он открылся, или там какая ошибка прописана, и исходя из этого действовать. Никаких проблем здесь нет.
Ещё раз: ордер уже выполнился и ушёл в историю. Его уже нет в активных. Позиция уже открыта, но её ещё не видно, запрос её не показывает.
Обычно это очень краткий интервал времени и на него трудно попасть, тем более, что он далеко не всегда бывает.
Но, например, при утреннем открытии с гэпом и большом потоке ордеров эта ситуация может длиться даже не секунды, а минуты.
Чтобы было понятнее: TRADE_TRANSACTION_ORDER_DELETE пришло раньше, чем TRADE_TRANSACTION_HISTORY_ADD или TRADE_TRANSACTION_DEAL_ADD
И мы в какой-то момент можем не видеть ордера ни в активных, ни в истории, или не видеть ни ордера ни сделки.