Торговля с Python - страница 4

 
Malik Arykov #:

Я о том что интеграция питон с МТ5 не полная. Нельзя НАПРЯМУЮ получить значения индикаторов

Сохранение данных на 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);

Чтение данных на питон

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 #:

Цели топика просты как пять копеек: позволить всем желающим начать алгоритмическую торговлю на Python, просто скопировав кусочки кода типа инициализации связи с терминалом, запроса котировок, запроса на сервер об открытии или закрытии сделки, и т.д. и т.п. Сосредоточиться на логике только, притом на чрезвычайно удобном для этого языке.

ну да... логики как раз и не хватает для алгоритмической торговли - логики, что тестировать торговую стратегию нужно, прежде чем "всем желающим" вставлять кусочки кода и лезть в реальную торговлю


ну все равно удачи с Вашими "немифическими" целями

)))

 

Люди, которым не нужно, чтобы цены сохранялись в файлы, чуть перепишут код, и сделают не "запись в файлы - чтение из файлов", а просто сразу нужную им структуру данных. Здесь во многом просто показ примитивных вещей, для желающих сделать первые шаги.

Однако, уже познакомившихся с такими понятиями как списки, кортежи, словари, и т.д.

Предлагаю чтение из файлов цен осуществлять функцией read_all_files_with_prices , которая будет задействовать функцию read_file_with_prices для чтения файла с ценами каждого инструмента.

Пусть функция read_file_with_prices возвращает или массив numpy.ndarray со строками и столбцами, соответствующими исходному файлу цен (если цены успешно прочитались и прошли проверку на целостность данных, тип каждого элемента numpy.float64), или строку ‘no data’, если данные не прочитались, или неполны.

Функция read_all_files_with_prices пусть возвращает словарь с котировками по всем инструментам. Пусть в этом словаре ключи - наименования инструментов, значения - словари с котировками по одному соответствующему инструменту. В словарях с котировками по одному инструменту ключи - отсчёты даты-времени (класс date_time), значения - бары (класс bar), кроме того предлагаю предусмотреть ключи :

  • 'is_data': значение по ключу - True, если котировки в наличии, False - если функция чтения из файла вернула 'no data',

  • 'instrument': значение по ключу - инструмент (str);

  • 'prices': значение по ключу - массив цен по инструменту (numpy.ndarray)

Короче, такую структуру данных предлагаю:


 

код на Python лучше помещать в виде скриншотов из IDE/редактора/сайта.

местное отсутствие подсветки по крайней мере комментариев кода, делает его слабо-читаемым, он сливается с основным текстом.

 

Добавил функции для чтения файлов и создания указанной выше структуры данных:

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 лучше помещать в виде скриншотов из IDE/редактора/сайта.

местное отсутствие подсветки по крайней мере комментариев кода, делает его слабо-читаемым, он сливается с основным текстом.

Зато его можно скопировать, и открыть в IDE, а с картинки - перенабирать вручную разве что...


Работа показанной выше программы:



В принципе почти всё готово к тому, чтобы начать анализ данных, принятие и выполнение торговых решений.

 
Mikhael1983 #:

Зато его можно скопировать, и открыть в IDE, а с картинки - перенабирать вручную разве что...


вряд-ли кто будет заморачиваться с копи-пастой кода..он никому тут не нужен

прочесть и обсудить да. Подчерпнуть что-нить любопытное или чего подсказать. А таскать в IDE уже вряд-ли. На форуме публикуют ради чтения другими, а для использования код либо аттачат, либо ссылку в репозитарий (или совмещают)

но это отвлечённо.

 

Поместим в файл Functions.py следующий код, функцию, вычисляющую 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 

Параметры d и k мне нужны для осуществления сдвига по времени, не суть, будем задавать их равными нулям.

 

Придумаю какую-нибудь простую дебильную логику, типа:

для каждого инструмента вычисляем SMA, запаздывающую на z=100 интервалов между отсчётами, то есть SMA порядка s=1+2*z=201.

Если в рынке нет сделок, или количество их (по конкретному инструменту) меньше, чем допустимое количество (отдельно по Sell, и отдельно по Buy), то:

если цена в текущий момент выше SMA(201) на 30 или более пипс - открыть сделку на продажу, с SL=TP=50 пипс,

если цена в текущий момент ниже SMA(201) на 30 или более пипс - открыть сделку на покупку, с SL=TP=50 пипс.

Если в какой-то момент прибыль по сделке достигла 20 пипс - закрыть её, не дожидаясь закрытия по SL или TP.

Реализуем для примера такую торговлю. Просто, глупо, с точки зрения трейдинга, но суть дела в реализации.

 

Предлагаю такие функции для открытия и для закрытия сделок:

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