Trading with Python - page 4

 
Malik Arykov #:

My point is that the integration of python with MT5 is not complete. You cannot get indicator values DIRECTLY

Saving data to mql
 int i = iBarShift(_Symbol,PERIOD_CURRENT,startTime);
 string nm = _Symbol+"_Inputs.csv";
 int h = FileOpen(nm,FILE_CSV|FILE_WRITE|FILE_COMMON,";");
 double d[2];
 while (i > 0) {
  d[0]=iСustom(_Symbol,PERIOD_CURRENT,"Ind_1",0,i);
  d[1]=iСustom(_Symbol,PERIOD_CURRENT,"Ind_2",0,i);
  FileWrite(h,d[0],d[1]);
  i--;
 }
 FileClose(h);

Reading data to python

import pandas as pd

_Symbol = "EURUSD"
_Base = "C:\\Users\\serg\\AppData\\Roaming\\MetaQuotes\\Terminal\\Common\\Files\\"

url = _Base+_Symbol+"_Inputs.csv"
X=pd.read_csv(url,delimiter=';',header=None)
 
Mikhael1983 #:

The aims of the topic are as simple as five pennies: to allow everyone to start algorithmic trading in Python, just by copying bits of code like initializing communication with the terminal, requesting quotes, asking the server to open or close a trade, etc., etc. Concentrate on logic only, and in an extremely easy to use language.

Right... logic is exactly what's missing for algorithmic trading - the logic that you need to test a trading strategy before "all comers" insert pieces of code and get into real trading


good luck with your "non-mythical" goals anyway

)))

 

People who don't need prices saved to files will rewrite the code a bit, and do not "write to files - read from files", but just the data structure they want. It's largely just a showcase of primitive things, for those willing to take the first steps.

However, those already familiar with concepts such as lists, tuples, dictionaries, etc.

I propose to read from price files with the function read_all_files_with_prices, which will trigger the function read_file_with_prices to read the price file of each instrument.

Let read_file_with_prices function return either array numpy.ndarray with rows and columns corresponding to the original price file (if prices were successfully read and checked for data integrity, each element type is numpy.float64), or 'no data' string if data was not read or is incomplete .

Let the read_all_files_with_prices function return a dictionary with quotes for all instruments. Suppose that the keys in this dictionary are the instruments names, and the values are the dictionaries with quotes of one corresponding instrument. In the dictionaries with quotes for one symbol the keys are date_time, the values are bars (class bar); besides, I suggest to provide keys :

  • 'is_data': key value - True if there is a quote, False - if reading from file returned 'no data',

  • 'instrument': key value - instrument (str);

  • 'prices': key-value - array of prices by instrument (numpy.ndarray)

In short, this is the data structure I suggest:


 

Python code is better placed as screenshots from the IDE/editor/site.

The local lack of highlighting, at least of code comments, makes it poorly readable, it blends in with the main text.

 

Added functions to read files and create the above data structure:

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_sd_sell_max = 3 # максимальное количество сделок типа sell по одному инструменту
N_sd_buy_max = 3 # максимальное количество сделок типа buy по одному инструменту
volume = 0.01 # объём сделок 
account_demo = ['Alpari-MT5-Demo', 12345678, 'password']
work_account = account_demo 
work_catalog = 'Z:\\fx_for_forum\\fx\\'
instruments = ['EURUSD_i', 'GBPUSD_i', 'EURGBP_i', 'USDCHF_i', 'USDCAD_i', 'USDJPY_i']



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_i', 'GBPUSD_i', 'EURGBP_i', 'USDCHF_i', 'USDCAD_i']:
        return 0.0001
    elif instrument in ['USDJPY_i']:
        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 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]))))
           
           
           
            # завершение соединения с 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)
 
Maxim Kuznetsov #:

Python code is better placed as screenshots from the IDE/editor/site.

The local lack of highlighting, at least for code comments, makes it poorly readable, it blends in with the main text.

But it can be copied and opened in IDE, but from the image it can be retyped manually except...


The work of the program shown above:



Basically, almost everything is ready to start analysing data and making and executing trading decisions.

 
Mikhael1983 #:

But it can be copied and opened in the IDE, while the image can only be retyped by hand...


I don't think anyone's going to bother copying and pasting the code... no one needs it here

to read and discuss, yes. Pick up something curious or give some hints. And I don't think I'll lug it around in IDE. People publish on forum for reading by others and for using the code either attach it or link to repository (or combine them).

But this is distracting.

 

Put the following code into the Functions.py file, a function that calculates SMA

import numpy as np 


def SMA(B, s, k, n, d): 
    A = B[B.size-n-s-d-k:B.size-d-k]
    Sigma = np.sum(A[n:n+s]) 
    C = np.zeros(n)
    C[n-1] = Sigma   
    for q in range(1, n): 
        Sigma = Sigma - A[n-q+s] + A[n-q]
        C[n-1-q] = Sigma  
    return C/s 

I need parameters d and k to perform a time shift, not the point, we will set them to zeros.

 

I'll come up with some simple dumb logic, like:

for each instrument we calculate SMA lagging by z=100 intervals between samples, i.e. SMA of the order s=1+2*z=201.

If there are no trades in the market or their quantity (for a certain symbol) is lower than the admissible quantity (separately for Sell and Buy)

if the price is currently 30 pips or more above SMA(201) - open Sell deal with SL=TP=50 pips,

if the price is currently 30 pips or more below the SMA(201) - open a Buy trade with SL=TP=50 pips.

If at any time profit on the trade reaches 20 pips - close it without waiting for closing by SL or TP.

Let's implement such a trade as an example. Simple, silly, from the trading point of view, but the essence of the matter is implementation.

 

I suggest such functions for opening and closing trades:

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