Le trading avec Python - page 6

 
Алексей КоКоКо #:
Je vais soutenir le sujet de départ, j'ai emprunté mql plusieurs fois comme 4 et 5, et je dirai que j'ai personnellement peu d'envie d'apprendre le langage que je ne peux utiliser qu'ici dans le trading ...

C'est soit pour rien, soit parce que vous ne comprenez pas le problème. Allez-vous vraiment réécrire les algorithmes de l'indicateur ? C'est comme aller de Volgograd à Moscou via Vladivostok. Une dernière chose, tous les indicateurs tiennent compte de l'historique. C'est-à-dire que si vous prenez les données (barres) d'un mois, que vous calculez l'indicateur selon votre algorithme et que vous le comparez à Terminal, vos calculs seront différents.

 
Vitaly Muzichenko #:

Je ne comprends pas non plus cette phrase :

Sur la base de ce que j'ai écrit, il s'avère qu'il y a une grande couche de connaissance de Python :) Vous ouvrez un éditeur et vous connaissez déjà python - c'est si facile, et vous ouvrez mql et vous ne connaissez rien.

En même temps, qualifier mql, quiest complètement orienté vers la plate-forme, d'outil "obsolète" ... python a été créé en 1991 et ceci est bien antérieur

Ce que j'ai vu dans ce fil, écrit en python, est très facile à mettre en œuvre dans mql.

---

Eh bien, c'est intéressant en tant qu'idée générale, mais pas plus.

De plus, il ne tire pas profit de Python et ce n'est pas un "style Python". C'est comme enfoncer des clous avec un tournevis, le marteau est démodé.

Igor Makanu #:

Je soupçonne que la seule vaisselle dans votre maison est une cuillère - vous pouvez faire une soupe ou un gâteau et il est plus sûr d'utiliser

)))

Si vous l'aimez, utilisez Python, mais pas en tant que sujet de départ - ne créez pas vos propres types de données personnalisés - barres et autres, n'écrivez pas vos propres calculs ... et utilisez les solutions existantes, sinon il n'y a aucun intérêt à utiliser ce langage, vous pouvez aussi bien écrire vos propres paquets de réseaux de neurones ;)

Oui) D'ailleurs, il n'y a pas de bibliothèque sur Python pour ces fonctions de trader, ou si vous pouvez écrire l'analyse statistique complète d'une série en quelques lignes, vous n'avez pas besoin de MA)....

 
Aleksey Mavrin #:

Ouais) Au fait, il n'y a pas de bibliothèque Python pour toutes sortes de fonctions proches du trading, ou si vous pouvez écrire une analyse statistique complète d'une série en quelques lignes, alors la MA n'est plus nécessaire)....

Les meilleures 101 bibliothèques de trading algorithmique en Python | PythonRepo

 

Impossible de revenir sur le forum hier. Je vais continuer. J'ai commencé un nouveau compte démo, et dessus se trouvent les noms des instruments sans '_i' à la fin, j'ai donc corrigé le code dans cette partie.

Je vais continuer le mouvement progressif et lent, une sorte de micro-tuto, pour ceux qui pourraient être utiles.

Introduisons une variable n (petite) - que ce soit, pour ainsi dire, la longueur de la fenêtre. Autrement dit, dans les fichiers avec des prix, N lectures, par exemple, 1000, et n, définissons, par exemple, 10, ou 100, ou 288 (un jour, si le délai est M5).

La variable d signifie pour moi le décalage de cette fenêtre dans le temps, dans le passé. Posons d = 0.


Par exemple, nous afficherons 10 relevés de cours de clôture et SMA de l'ordre de 101 (retardés de 50 intervalles entre relevés) :


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 = 10 
d = 0
N_sd_sell_max = 3 # максимальное количество сделок типа sell по одному инструменту 
N_sd_buy_max = 3 # максимальное количество сделок типа buy по одному инструменту 
volume = 0.01 # объём сделок  
account_demo = ['Alpari-MT5-Demo', 51056680 , 'password'] 
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 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) 
            
            # демонстрация актуальности котировок, 
            # поскольку сейчас выходной, то последний отсчёт времени в файлах данных не совпадает с текущим, не суть   
             for instrument in instruments: 
                 if dict___with_prices_for_all_instruments[instrument]['is_data']: 
                    str1 = 'Последний отсчёт в файле с ценами для {}: {}' 
                    print(str1.format(instrument, str( int (dict___with_prices_for_all_instruments[instrument]['prices'][N- 1 , 0 ]))))
            
            # отобразим значения последних n отсчётов цен закрытия баров, и SMA101, вычисленной по ценам закрытия баров  
             for instrument in instruments: 
                dict___with_prices_for_instrument = dict___with_prices_for_all_instruments[instrument] 
                 if dict___with_prices_for_instrument['is_data']: 
                    AData = dict___with_prices_for_instrument['prices'] 
                    Ax_DTID  = AData[:, 0 ] # столбец отсчётов даты-времени в виде M5_view в N строк, пока низачем не нужен 
                    Ax_close = AData[:, 4 ] # столбец отсчётов цен закрытия в N строк 
                    A_DTID  = Ax_DTID[Ax_close.size-n-d:Ax_close.size-d] # столбец отсчётов даты-времени в виде M5_view в n строк  
                    A_close = Ax_close[Ax_close.size-n-d:Ax_close.size-d] # столбец отсчётов цен закрытия в n строк 
                    print('Последние {} отсчётов цен закрытия для {}:'.format(n, instrument)) 
                    print(A_close) 
                    z = 50 # параметр, задающий запаздывание скользящей средней, для z= 50 скользящая средняя по 1 + 2 * 50 = 101 отсчёту  
                    A_close_s = Functions.SMA(Ax_close, 1 + 2 *z, 0 , n, d) 
                    print('Последние {} отсчётов SMA( 101 ) по ценам закрытия для {}:'.format(n, instrument)) 
                    print(A_close_s) 
                
            # завершение соединения с 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)


Résultat des travaux :


 

Apprenons à interroger le terminal sur les transactions en cours sur le marché, pour ainsi dire, pour les instruments.

Je vais introduire la fonction

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)

Il prend la liste des positions ouvertes du symbole et renvoie le tuple(le nombre de transactions à vendre, le nombre de transactions à acheter).

Dans la fonction principale, ajoutez ce fragment avant la fonction 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]))

Le résultat (n est réduit à 5 pour le bien de la sortie) :


Je propose de commencer l'ouverture effective des transactions. Par exemple : si le prix est supérieur à la SMA de quelque chose : ouvrez une transaction de vente, s'il est inférieur à la SMA de quelque chose : ouvrez une transaction d'achat.

Je suggère quelques fonctions supplémentaires : pour contrôler les trades ouverts en termes de SL et TP, les fermer s'ils sont absents, et une fonction qui renvoie le résultat actuel (profit ou perte) en pips.

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 
 

Nous devons nous rappeler de gérer d'une manière ou d'une autre la situation où l'ordre est déjà dans l'historique et qu'il n'est pas en position active, et de ne pas renvoyer l'ordre pour ouverture dans ce cas.

Cette situation se produit de temps en temps et, par exemple, travailler par le biais de Trade.mqh ne résout pas ce problème (du moins, ce n'était pas le cas auparavant - j'ai abandonné Trade.mqh il y a longtemps).

Parfois, c'est l'inverse : l'ordre de clôture figure déjà dans l'historique et la position est toujours visible.

 
JRandomTrader #:

Nous devons nous rappeler de gérer d'une manière ou d'une autre la situation où l'ordre est déjà dans l'historique et qu'il n'est pas en position active, et de ne pas renvoyer l'ordre pour ouverture dans ce cas.

Cette situation se produit de temps en temps et, par exemple, travailler par le biais de Trade.mqh ne résout pas ce problème (du moins, ce n'était pas le cas avant - j'ai abandonné Trade.mqh il y a longtemps).

Parfois, c'est l'inverse - l'ordre de clôture est déjà dans l'historique et la position est toujours visible.


J'ai montré comment interroger le terminal sur les positions ouvertes. Rien ne nous empêche d'écrire le code de telle sorte que si la position existe déjà, nous n'aurons pas besoin de renvoyer la demande, sinon - nous le ferons. En particulier, qu'après avoir envoyé une requête, l'objet est retourné, qui a tout dans ses propriétés, nous pouvons voir si c'est normal, ou quel type d'erreur est présent, et sur la base de cet acte. Il n'y a aucun problème ici.

 

Code complet du fichier TradeLogic.py jusqu'à présent (oui, c'est primitif, mais je suis sûr que cela pourrait ouvrir la porte au trading en utilisant Metatrader 5 et Python pour certains)

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)


Résultat du travail :



Z.I.Y. Les morceaux de code les plus primitifs, élémentaires, montrés, à mon avis, démontrent déjà très clairement qu'il n'est pas difficile d'implémenter absolument n'importe quelle logique, n'importe quel calcul, n'importe quelle comparaison, le contrôle des transactions ouvertes selon divers critères, leur fermeture automatique sous certaines conditions, la tenue de journaux à la fois en ligne de commande et en écrivant dans des fichiers, etc., etc.

Z.U.2 Till maintenant utilisé classe Sdelka, et en général, il est commode de passer les objets de cette classe dans les fonctions, les écrire à des fichiers comme .json, etc, - mais en quelque sorte a été un peu fatigué, je pense que ceux qui veulent comprendre, je peux aider ici avec quelque chose. Si vous le souhaitez, vous pouvez visser une interface graphique.


Je recommande vivement de trader avec Python + la bibliothèque metatrader5.

 
Mikhael1983 #:

J'ai montré comment interroger le terminal pour connaître les positions ouvertes. Rien ne vous empêche d'écrire le code de manière à ce que, s'il existe déjà une position, vous n'ayez pas besoin de renvoyer la demande, sinon, envoyez-la. Surtout, qu'après avoir envoyé une requête, l'objet est renvoyé, qui a tout dans ses propriétés, on peut voir s'il s'est ouvert normalement ou s'il y a une erreur, et sur la base de cet acte. Il n'y a aucun problème ici.

Encore une fois, l'ordre a déjà été exécuté et est passé à l'histoire. Il n'est plus en position active. La position a déjà été ouverte, mais elle n'est pas encore visible ; la demande ne la montre pas.

En général, cet intervalle de temps est très court et il est difficile de l'atteindre, d'autant que ce n'est pas toujours le cas.

Cependant, cette situation peut durer non pas quelques secondes mais quelques minutes lors d'une ouverture matinale avec un gap et un grand nombre d'ordres.

Pour être plus clair : TRADE_TRANSACTION_ORDER_DELETE est arrivé avant TRADE_TRANSACTION_HISTORY_ADD ou TRADE_TRANSACTION_DEAL_ADD.

Et il se peut qu'à un moment donné, nous ne voyions pas d'ordres dans l'actif ou l'historique, ou que nous ne voyions pas d'ordres ou de transactions.