Trading with Python - page 6

 
Алексей КоКоКо #:
I'll support the topic starter, I've borrowed mql for many times as 4 and 5, and i'll say that i personally have little desire to learn the language that i can use only here in trading ...

Well, that's either for nothing or because you don't understand the problem. Are you really going to write the indicator algorithms all over again? It is like getting from Volgograd to Moscow via Vladivostok. One more thing, all indicators take into account the history. That is, if you take the data (bars) for a month, calculate the indicator according to your algorithm and compare it to Terminal, your calculations will be different.

 
Vitaly Muzichenko #:

I don't understand the phrase either:

Based on what I wrote, it turns out that there is a large layer of Python knowledge :) You open an editor and you already know python - it's so easy, and open mql and you don't know a thing.

At the same time, to call mql, whichis completely platform-oriented, an "obsolete" tool ... python was created in 1991 and this is much earlier

What I saw in this thread, written in python, is very easy to implement in mql

---

Well, it's interesting as a general idea, but not more.

Moreover he doesn't take advantages of Python and it's not "Python-style". It's like hammering nails with a screwdriver, the hammer is old-fashioned.

Igor Makanu #:

I suspect that the only crockery in your house is a spoon - you can make a soup or a cake and it's safer to use

)))

If you like it, use Python, but not as the topicstarter - don't create your own custom data types - bars and so on, don't write your own calculation ... and use existing solutions, otherwise there's no point in using this language, you may as well write your own neural network packages ;)

Yes) By the way, there's no library on Python for those trader's functions, or if you can write the full statistical analysis of a series in a couple of lines, you don't need MA)...

 
Aleksey Mavrin #:

Yeah) By the way, there's no Python library for all sorts of near-trading functions, or if you can write a complete statistical analysis of a series in a couple of lines, then the MA is no longer needed)...

The Best 101 Python algorithmic-trading Libraries | PythonRepo

 

Couldn't get back to the forum yesterday. I will continue. I started a new demo account, and on it are the names of instruments without '_i' at the end, so I corrected the code in this part.

I will continue the gradual and slow movement, a kind of micro-tutorial, for those who may come in handy.

Let's introduce a variable n (small) - let it be, so to speak, the length of the window. That is, in files with prices, N readings, for example, 1000, and n, let's set, for example, 10, or 100, or 288 (a day, if the timeframe is M5).

The variable d for me means the shift of this window in time, into the past. Let's set d = 0.


For example, we will display 10 readings of closing prices and SMA of the order of 101 (delayed by 50 intervals between readings):


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)


Result of work:


 

Let's learn how to interrogate the terminal about the current trades in the market, so to speak, for the instruments.

I will introduce the function

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)

Let it take the list of open positions of the symbol and return the tuple(the number of deals to sell, the number of deals to buy).

In the main function add this fragment before the function 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]))

The result (n is reduced to 5 for the sake of the output):


I propose to start actual opening of trades. For example: if the price is higher than the SMA by some: open a sell trade, if it is lower than the SMA by some: open a buy trade.

I suggest a couple more functions: to control open trades in terms of SL and TP, close them if they are absent, and a function that returns the current result (profit or loss) in 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 
 

We have to remember to somehow handle the situation when the order is already in the history and it is not in the active position, and not to re-send the order for opening in this case.

This situation happens from time to time and, for example, working through Trade.mqh doesn't solve this problem (at least, it didn't before - I gave up Trade.mqh a long time ago).

Sometimes it is the other way around - the order to close is already in the history and the position is still visible.

 
JRandomTrader #:

We have to remember to somehow handle the situation when the order is already in the history and it is not in the active position, and not to re-send the order for opening in this case.

This situation happens from time to time and, for example, working through Trade.mqh doesn't solve this problem (at least, it didn't before - I gave up Trade.mqh a long time ago).

Sometimes it is vice versa - the order to close is already in the history and the position is still visible.


I showed how to interrogate the terminal regarding the open positions. Nothing prevents us from writing the code in such a way that if the position already exists, we would not need to resend the request, if not - we will. Especially, that after sending a request, the object is returned, which has everything in its properties, we can see whether it is normal, or what kind of error is present, and based on this act. There is no problem here.

 

It's already 104) It's 105)

 

Full code of the TradeLogic.py file so far (yes, it's primitive, but I'm sure it might open the door to trading using Metatrader 5 and Python for some)

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)


Work result:



Z.I.Y. The most primitive, elementary, pieces of code shown, in my opinion, already demonstrate quite clearly that it is not difficult to implement absolutely any logic, any calculations, comparisons, control of open trades by various criteria, closing them automatically under some conditions, keeping logs both on the command line and by writing to files, etc., etc. - anything without any restrictions whatsoever.

Z.U.2 Till now used class Sdelka, and in general it is convenient to pass the objects of this class into functions, write them to files as .json, etc., - but somehow got a little tired, I think those who want to understand, I can help here with something. If you want, you can screw in a graphical interface.


I strongly recommend trading with Python + metatrader5 library.

 
Mikhael1983 #:

I have shown how to poll the terminal for open positions. There is nothing that prevents you from writing the code so that if there is already a position, then you don't need to re-send the request, if not - send it. Especially, that after sending a request, the object is returned, which has everything in its properties, we can see whether it opened normally or there is an error, and based on this act. There are no problems here.

Once again, the order has already been executed and passed into history. It is no longer in the active position. The position has already been opened, but it is not visible yet; the request does not show it.

Usually, this is a very short time interval and it is difficult to reach it, especially since it is not always the case.

However, this situation may last not even seconds but minutes at a morning opening with a gap and a great amount of orders.

To make it clearer: TRADE_TRANSACTION_ORDER_DELETE has come earlier than TRADE_TRANSACTION_HISTORY_ADD or TRADE_TRANSACTION_DEAL_ADD

And we may not see orders in active or history at some point, or we may not see any orders or trades.

Reason: