Negociando com Python - página 6

 
Алексей КоКоКо #:
Vou apoiar o início do tópico, pedi emprestado mql muitas vezes como 4 e 5, e vou dizer que pessoalmente tenho pouco desejo de aprender a língua que só posso usar aqui no comércio ...

Bem, ou isso é em vão ou porque você não entende o problema. Você vai realmente escrever os algoritmos indicadores novamente? É como chegar de Volgograd a Moscou via Vladivostok. Mais uma coisa, todos os indicadores levam em conta o histórico. Ou seja, se você pegar os dados (barras) durante um mês, calcular o indicador de acordo com seu algoritmo e compará-lo com o Terminal, seus cálculos serão diferentes.

 
Vitaly Muzichenko #:

Eu também não entendo a frase:

Com base no que escrevi, acontece que existe uma grande camada de conhecimento de Python :) Você abre um editor e já conhece python - é tão fácil, e abre mql e você não sabe nada.

Ao mesmo tempo, para chamar o mql, queé completamente orientado para plataformas, uma ferramenta "obsoleta" ... python foi criada em 1991 e isto é muito anterior

O que eu vi neste fio, escrito em píton, é muito fácil de implementar em mql

---

Bem, é interessante como uma idéia geral, mas não mais.

Além disso, ele não tira vantagens da Python e não é "estilo Python". É como martelar pregos com uma chave de fenda, o martelo é antiquado.

Igor Makanu #:

Eu suspeito que a única louça em sua casa é uma colher - você pode fazer uma sopa ou um bolo e é mais seguro de usar

)))

Se você gosta, use Python, mas não como o iniciador do tópico - não crie seus próprios tipos de dados personalizados - barras e assim por diante, não escreva seus próprios cálculos ... e use as soluções existentes, caso contrário não adianta usar esta linguagem, você também pode escrever seus próprios pacotes de rede neural ;)

Sim) A propósito, não há bíblia em Python para tais funções quase comerciais, ou se você puder escrever a análise estatística completa de uma série em algumas linhas, você não precisa de MA)...

 
Aleksey Mavrin #:

Sim) A propósito, não há nenhuma biblioteca Python para todos os tipos de funções quase comerciais, ou se você puder escrever uma análise estatística completa de uma série em algumas linhas, então o MA não é mais necessário)...

As Melhores 101 bibliotecas Python algorítmicas de comercialização | PythonRepo

 

Não foi possível voltar ao fórum ontem. Eu vou continuar. Iniciei uma nova conta demo, e nela estão os nomes dos instrumentos sem '_i' no final, então corrigi o código nesta parte.

Vou continuar o movimento gradual e lento, uma espécie de micro-tutorial, para quem possa vir a calhar.

Vamos introduzir uma variável n (pequena) - seja, por assim dizer, o comprimento da janela. Ou seja, em arquivos com preços, N leituras, por exemplo, 1000, en, vamos definir, por exemplo, 10, ou 100, ou 288 (um dia, se o prazo for M5).

A variável d para mim significa a mudança desta janela no tempo, para o passado. Vamos definir d = 0.


Por exemplo, exibiremos 10 leituras de preços de fechamento e SMA da ordem de 101 (atrasado em 50 intervalos entre as leituras):


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)


Resultado do trabalho:


 

Vamos aprender a interrogar o terminal sobre os negócios atuais no mercado, por assim dizer, para os instrumentos.

Vou apresentar a função

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)

Deixe ele pegar a lista de posições abertas do símbolo e devolver o tuple(o número de negócios a vender, o número de negócios a comprar).

Na função principal adicione este fragmento antes da função 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]))

O resultado (n é reduzido a 5 por causa da produção):


Proponho iniciar a abertura real dos negócios. Por exemplo: se o preço for superior ao SMA por alguns: abrir um comércio de venda, se for inferior ao SMA por alguns: abrir um comércio de compra.

Sugiro mais algumas funções: controlar os negócios abertos em termos de SL e TP, fechá-los se estiverem ausentes, e uma função que retorna o resultado atual (lucro ou perda) em 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 
 

Temos que nos lembrar de lidar de alguma forma com a situação quando a ordem já está na história e não está na posição ativa, e não reenviar a ordem de abertura neste caso.

Esta situação acontece de tempos em tempos e, por exemplo, trabalhar através do Trade.mqh não resolve este problema (pelo menos, não resolvia antes - eu desisti do Trade.mqh há muito tempo).

Às vezes é o contrário - a ordem de fechamento já está na história e a posição ainda é visível.

 
JRandomTrader #:

Temos que nos lembrar de lidar de alguma forma com a situação quando a ordem já está na história e não está na posição ativa, e não reenviar a ordem de abertura neste caso.

Esta situação acontece de tempos em tempos e, por exemplo, trabalhar através do Trade.mqh não resolve este problema (pelo menos, não resolvia antes - eu desisti do Trade.mqh há muito tempo).

Às vezes é vice-versa - a ordem de fechamento já está na história e a posição ainda é visível.


Mostrei como interrogar o terminal em relação às posições em aberto. Nada nos impede de escrever o código de tal forma que, se a posição já existe, não precisaríamos reenviar o pedido, se não - nós o faremos. Especialmente, que após enviar um pedido, o objeto é devolvido, que tem tudo em suas propriedades, podemos ver se ele é normal, ou que tipo de erro está presente, e com base neste ato. Aqui não há nenhum problema.

 
 

Código completo do arquivo TradeLogic.py até agora (sim, é primitivo, mas tenho certeza de que pode abrir a porta para o comércio usando Metatrader 5 e Python para alguns)

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)


Resultado do trabalho:



Z.I.Y. As peças de código mais primitivas, elementares, mostradas, em minha opinião, já demonstram muito claramente que não é difícil implementar absolutamente nenhuma lógica, quaisquer cálculos, comparações, controle de negócios abertos por vários critérios, fechando-os automaticamente sob algumas condições, mantendo registros tanto na linha de comando como por escrito em arquivos, etc., etc. - qualquer coisa sem qualquer restrição.

Z.U.2 Até agora, a classe Sdelka era usada, e em geral é conveniente passar os objetos desta classe em funções, escrevê-los em arquivos como .json, etc., - mas de alguma forma ficou um pouco cansado, acho que aqueles que querem entender, eu posso ajudar aqui com algo. Se você quiser, você pode parafusar uma interface gráfica.


Recomendo vivamente o comércio com a biblioteca Python + metatrader5.

 
Mikhael1983 #:

Eu mostrei como sondar o terminal para posições abertas. Não há nada que o impeça de escrever o código para que, se já houver uma posição, não seja necessário reenviar o pedido, se não - enviá-lo. Especialmente, que após o envio de um pedido, o objeto é devolvido, que tem tudo em suas propriedades, podemos ver se ele abriu normalmente ou se há um erro, e com base neste ato. Aqui não há problemas.

Mais uma vez, a ordem já foi executada e passada para a história. Ela não está mais na posição ativa. A posição já foi aberta, mas ainda não é visível; o pedido não a mostra.

Normalmente, este é um intervalo de tempo muito curto e é difícil alcançá-lo, especialmente porque nem sempre é o caso.

Entretanto, esta situação pode não durar nem mesmo segundos, mas minutos em uma abertura matinal com um intervalo e uma grande quantidade de pedidos.

Para tornar mais claro: TRADE_TRANSACTION_ORDER_DELETE veio mais cedo que TRADE_TRANSACTION_HISTORY_ADD ou TRADE_TRANSACTION_DEAL_ADD

E podemos não ver ordens em atividade ou histórico em algum momento, ou podemos não ver nenhuma ordem ou negociação.