Блог им. jatotrade_com



########################################################################################################################
# Jatotrader 3.0 # JatoLab for Python. Version 1.0. Apr 27 2020, ©2020 by Evgeny Shibaev #
########################################################################################################################
import numpy as np
import pandas as pd
#from datetime import datetime
import matplotlib.pyplot as plt
from glob import glob
#Исходными данными явлются файлы в формате csv.
#Файлы находятся в папке Jatotrader\DATASET\имятикера\. Каждый файл содержит в себе информацию по одной торговой сессии
#одного тикера с заданным методом формирования баров. Дата сессии и метод формирования баров указаны в имени файла.
#Например, в файле 2020-02-03_RIH0_TICKS_500.FRQ содержится информация за 3 февраля 2020 года по фьючерсному контракту RIH0,
#сформированная из расчета 500 тиков на бар. Превая строка файла - это имена столбцов DATETIME,H,L,O,C,DH,DL,DO,DC,OTO,
#BI,SI,BV,SV,BC,SC. Последующие строки - это бары, идущие в хронологическом порядке. DATETIME-строка в формате
#"ГГГГ-ММ-ДД ЧЧ:ММ:СС", H,L,O,C - максимальная, минимальная цена и цена открытия и закрытия бара. DH и DL - максимальное
# и минимальное значение накопленной маркет-дельты бара, DO и DC - значение маркет-дельты при открытии и закрытии бара.
#OTO - значение объемно-тикового осциллятора на закрытии бара, BI-интенсивность покупок (тиков в секунду),
#SI- интенсивность продаж, BV-объем покупок, SV-объем продаж, BC-количество покупок,SC-количество продаж
#Путь к набору данных - укажите ваш путь к папке DATASET
path = 'C:\\Jatotrader\\DATASET\\'
#Изменяемые параметры частотных графиков
tiker = 'RIU9' #Имя тикера
method = 'TICKS' #Метод формирования бара
frame = 1500 #Размер бара
#Функция сигнала на продажу. Аргументы: датафрейм candles - это свечи с данными за конкретную торговую сессию,
# i - индекс свечи внутри дня. Возвращает значение True, если условие истинно, иначе False.
#Условием сигнала может быть любая комбинация параметров свечей из датафрейма candles. В данном случае, сигналом на продажу
#на i-й (текущей) свече будет являться пересечение индикатором ОТО уровня в 20% снизу вверх (i-1 - индекс предыдущей свечи)
def SignalSell (candles, i):
return candles['OTO'][i] > 20 and candles['OTO'][i-1] < 20
#По аналогии функции сигнала на продажу.
def SignalBuy (candles, i):
return candles['OTO'][i] < -20 and candles['OTO'][i-1] > -20
#Функция проверки сигналов на покупку-продажу по всем свечам торговой сессии. Возвращает списки сигналов на покупку и
#продажу.Каждый список состоит из индексов свечей. Например tobuy=[17, 78] означает сигналы на покупку на 17-й и 78-й свече.
def simulate (candles):
tobuy, tosell = [],[]
for i in range(len(candles)): #Основной цикл по свечам
#ВАЖНО!!! ИНДЕКС СВЕЧИ ДОЛЖЕН БЫТЬ НЕ МЕНЕЕ ИНДЕКСА, ИСПОЛЬЗУЕМОГО В SignalBuy И SignalBuy
#Например, если в функции SignalSell вы используете параметры i-4 свечи, то строка ниже будет такой if i > 3:
if i > 1:
if SignalSell(candles, i):
tosell.append(i)
if SignalBuy(candles, i):
tobuy.append(i)
return tobuy, tosell
#Расчет прибыли и риска (максимального количества контрактов текущей позиции) по барам внутри торговой сессии
#Аргументы: списоки сигналов на покупку и продажу (индексы баров, соответствующих сигналам)
#Возвращает список, соответствующий значениям прибыли (убытка) на каждом баре торговой сессии
def daylyequity (buys, sells):
equity, risk =[], []
daymax=-1000000000
daymin=1000000000
maxrisk=0
outind = len(candles)
sumbuys, qbuys, sumsells, qsells, avgbuys, avgsells = 0, 0, 0, 0, 0, 0
for i in range(outind): #Цикл по всем свечам торговой сессии
if i in buys:
quantb = buys.count(i)
sumbuys += candles['C'][i] * quantb
qbuys += quantb
avgbuys = sumbuys/qbuys
if i in sells:
quants = sells.count(i)
sumsells += candles['C'][i] * quants
qsells += quants
avgsells = sumsells/qsells
avgremain = avgbuys if avgbuys > avgsells else avgsells
closedprof = (avgsells - avgbuys) * min (qsells, qbuys)
profremain = (candles['C'][i] - avgbuys) if (qbuys > qsells) else (avgsells - candles['C'][i])
profit= closedprof + abs(qsells - qbuys)*profremain
daymax = max (daymax, profit)
daymin = min (daymin, profit)
maxrisk = max (maxrisk, abs(qbuys - qsells))
equity.append(profit)
risk.append(qbuys - qsells)
dayproflist.append(equity[-1]) #Закрытие позиции по закрытию торговой сессии
daymaxlist.append(daymax)
dayminlist.append(daymin)
dayrisk.append(maxrisk)
candles['RISK']= risk
return equity
#Инициализация переменных
#dayproflist - итоговая прибыль по дням, Инициализация переменных
dayproflist, daymaxlist, dayminlist, dayrisk = [], [], [], []
trades=0
shift=0
############# Основной цикл по торговым сессиям набора данных #######################################
#Каждый файл это данные за конкретную торговую сессию одного частотного графика, например RIH0 500 тиков на бар.
files = glob(f'{path}{tiker}\\*_{tiker}_{method}_{frame}.frq')
#Читаем в датафрейм candles из файлов с частотными данными за соответствующую дату с соотв. частотными настройками
for file in files: #Цикл по торговым сессиям
candles=pd.read_csv(file, header=0, sep=',', skiprows = [1,2])
clen=len(candles) #Количество частотных свечей
istart=0 #Индекс первой свечи на графике
xwidth= min(clen, clen) #Количество свечей, отображаемое на графике
fig, [axPrice, axOTO, axEq] = plt.subplots(3, 1, figsize=(16, 8)) #Три секции графика с соответствующими осями
axPrice.set_title(file)
axPrice.set_ylabel('Price')
axOTO.set_ylabel('OTO')
axEq.set_ylabel('Equity')
axRisk = axEq.twinx() #Дополнительная шкала уровня риска на графике эквити
axRisk.set_ylabel('Position')
axDPrice = axPrice.twinx() #Дополнительная шкала изменения цены
axDOTO = axOTO.twinx()
for ax in [axPrice, axOTO, axEq, axDOTO]: #цикл по секциям axPrice
ax.grid(True) #Добавляем сетку в каждую секцию
ax.set_xlim(xmin=istart, xmax=xwidth) #Задаем границы отображения по шкале X
t = np.arange(istart, xwidth) #Шкала Х по количеству свечей
axOTO.plot(t, candles['OTO'][istart:xwidth], linewidth=1) #Рисуем график ОТО
axPrice.plot(t, candles['C'][istart:xwidth], color = 'black', linewidth=0.7)
print(clen, "свечей прочитано")
#Это просто пример как можно получить столбец изменений параметра от предыдущих значений (изменеия ОТО)
candles['DOTO']=[0]+[candles['OTO'][i+1]-candles['OTO'][i] for i in range (clen-1)]
colorsOTO = ['indianred' if x < 0 else 'seagreen' for x in candles['DOTO']]
axDOTO.bar(t, candles['DOTO'][istart:xwidth], color = colorsOTO, alpha = 0.5)
#Формируем сигналы на покупку и продажу
signals = simulate(candles)
tb=signals[0] #Список индексов свечей с сигналами на покупку
#Сигналы на покупку на графике
axPrice.scatter([x+shift for x in tb], [candles['C'][x+shift] for x in tb], marker = 'o', color='g')
ts=signals[1] #Список индексов свечей с сигналами на продажу
#Сигналы на продажу на графике
axPrice.scatter([x+shift for x in ts], [candles['C'][x+shift] for x in ts], marker = 'o', color='r')
trades += len(tb)+len(ts)+abs(len(tb)-len(ts)) #Количество сделок с учетом закрытия позиции полностью в конце дня
#Вертикальные линии сигналов по всем секциям графика для визуального контроля
for s in tb:
axPrice.axvline(x=s, color='g', alpha=0.35)
axOTO.axvline(x=s, color='g', alpha=0.35)
axEq.axvline(x=s, color='g', alpha=0.35)
for s in ts:
axPrice.axvline(x=s, color='r', alpha=0.35)
axOTO.axvline(x=s, color='r', alpha=0.35)
axEq.axvline(x=s, color='r', alpha=0.35)
#График эквити внутри торговой сессии
de=np.array(daylyequity(tb, ts))
axEq.plot(t, de, color = 'black', linewidth=0.5)
axEq.text(t[-1]+1, de[-1], "%.2f" % (de[-1]), bbox={'facecolor':'darkred' if de[-1] < 0 else 'darkgreen', 'pad':3}, color = 'white')
#График изменения позиции (риска) внутри торговой сессии
axRisk.plot(t, candles['RISK'][istart:xwidth], color = 'black', linewidth=0.5)
axEq.fill_between(t, de, where= (de > 0), facecolor='g', alpha=0.35, interpolate=True)
axEq.fill_between(t, de, where= (de < 0), facecolor='r', alpha=0.35, interpolate=True)
#Итоговая "эквити"
#Накопленная прибыль по дням
res = 0
eq = []
for d in dayproflist:
res +=d
eq.append(res)
fig, ax = plt.subplots(figsize=(16, 8))
axRes = ax.twinx() #Дополнительная шкала накопленной прибыли
axRisk = ax.twinx() #Дополнительная шкала риска по дням
ax.grid(True)
#Прибыль по итогу торговой сессии
x = [x for x in range (len (dayproflist))]
y = [y for y in dayproflist]
color = ['red' if y < 0 else 'green' for y in dayproflist]
ax.bar(x, y, color = color, alpha=0.75, width = 0.65)
axRes.plot(x, eq, color = 'black', linewidth=1)
#Максимальная прибыль в течение торговой сессии
y = [y for y in daymaxlist]
color = ['r' if y < 0 else 'g' for y in daymaxlist]
ax.bar(x, y, color = color, alpha=0.35)
#Минимальная прибыль (максимальный убыток) в течение торговой сессии
y = [y for y in dayminlist]
color = ['r' if y < 0 else 'g' for y in dayminlist]
ax.bar(x, y, color = color, alpha=0.35)
#Максимальный риск (объем открытой позиции) в течение торговой сессии
y = [y for y in dayrisk]
axRisk.bar(x, y, color = 'b', width = 0.3, alpha = 0.35)
#Итоговые показатели
sumeq=np.array(eq)
sumprof=sumeq[-1]
axRes.fill_between(x, sumeq, where= (sumeq > 0), facecolor='g', alpha=0.35, interpolate=True)
axRes.fill_between(x, sumeq, where= (sumeq < 0), facecolor='r', alpha=0.35, interpolate=True)
axRes.text(x[-1]+1, sumprof, "%.2f" % (sumprof), bbox={'facecolor':'g', 'alpha':0.5, 'pad':3})
wins=sum(i > 0 for i in dayproflist)
loss=sum(i <= 0 for i in dayproflist)
days=len(dayproflist)
axRes.set_title(f'Ticker: {tiker} {frame} {method} per bar')
ax.text(0, max(daymaxlist), (f'Win days: {wins} ({round (wins/days*100, 2)}%) Loss days: {loss} ({round (loss/days*100, 2)}%) in {days} days. Max risk: {max(dayrisk)} contracts. Trades: {trades}'))
axRes.set_title(f'Ticker: {tiker} {frame} {method} per bar')
Скачать Jatotrader можно здесь. Как получить ключ в этом видео. Как подключиться к КВИКУ смотри здесь. С 8-м Квиком пока не работает, доделываю, по плану после 25 мая — биржа вводит 19-ти значные заявки.
Подписаться на мой канал можно здесь в ютьюбе.
Можно например extractSignals. Ну или checkForSignals согласно комментарию над функцией. Короче вы поняли.
Ну и будем честными — считать закрытие свечи как цену сделки (ну я так код понял, давно питон не трогал) для относительно частых сигналов, вносит такую погрешность, что финальный симулированный результат может быть больше похож на шум.
Я наверное не правильно объяснил, ф-я simulate только распределяет сигналы по барам. Сами сигналы формируются в функциях SignalSell и SignalBuy.А здесь вы правы, закрытие-открытие свечи нельзя считать исполнением сделки. Это упрощение сделано лишь для проверки гипотез и не является зеркалом реальной торговой системы. Правда, учитывая среднее количество сделок менее 9 на одну торговую сессию и то, что реальная заявка может быть исполнена как хуже, так и лучше цены закрытия бара, такое допущение является вполне адекватным. В следующих версиях симулятора я могу добавить механизм выставления и исполнения ордеров даже с учетом «латенси», как это сделано в Jatotrader.