Прикручиваем индикатор к боту

В одной из предыдущих статей мы разобрали использование библиотеки ta-lib и построение индикаторов. Вот ссылка для освежения памяти. Теперь давайте попробуем получить из этого практическую пользу – научимся анализировать MACD программно, и напишем функцию для принятия решения – стоит ли торговать в данный момент времени или не стоит. Ну и воткнем индикатор в бота.

Если вы помните, в прошлой статье мы получали данные с биржи, и строили три графика – сами свечи, линии MACD и гистограмму. Возьмем этот пример, и на его основе разберемся дальше, что же с этим делать.

Строим MACD и ставим задачу

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

import numpy
import talib
import requests
import json
import time

from matplotlib.finance import candlestick2_ohlc
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import datetime as datetime

PAIR = 'BTC_ETH' # Пара, по которой работаем

start_time = time.time() - 15*60*60 # за какой период брать данные - 15 часов в данном случае

resource = requests.get("https://poloniex.com/public?command=returnChartData&currencyPair=%s&start=%s&end=9999999999&period=300" % (PAIR, start_time))
data = json.loads(resource.text)

quotes = {}
quotes['open']=numpy.asarray([item['open'] for item in data])
quotes['close']=numpy.asarray([item['close'] for item in data])
quotes['high']=numpy.asarray([item['high'] for item in data])
quotes['low']=numpy.asarray([item['low'] for item in data])

xdate=[datetime.datetime.fromtimestamp(item['date']) for item in data]

fig, ax = plt.subplots(3, sharex=True)

candlestick2_ohlc(ax[0], quotes['open'],quotes['high'],quotes['low'],quotes['close'],width=0.6)

ax[0].xaxis.set_major_locator(ticker.MaxNLocator(6))

def chart_date(x,pos):
    try:
        return xdate[int(x)]
    except IndexError:
        return ''

Реклама:


ax[0].xaxis.set_major_formatter(ticker.FuncFormatter(chart_date)) fig.autofmt_xdate() fig.tight_layout() macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9) ax[1].plot(macd, color="y") ax[1].plot(macdsignal) plt.show()

После запуска получим примерно такую картину:

Но, раз уж смотреть на графики скучно, давайте попробуем их проанализировать? Методик много, но мы воспользуемся подсказкой от википедии:

Обычно сигналом «Покупать» считают, когда скользящая с меньшим периодом (на рисунке синяя линия) в нижней зоне пересекает снизу вверх скользящую с бо́льшим периодом (красная линия). Сигналом «Продавать» считают, когда скользящая с меньшим периодом в верхней зоне пересекает сверху вниз скользящую с бо́льшим периодом.

Конечно, в жизни не все так просто – и сам по себе индикатор предназначен для недельных и месячных периодов, и объем влияет на принятие решений, и, по хорошему, надо заодно проверить еще пяток индикаторов… Но не будем заморачиваться! Мы хотим научиться понимать тенденцию рынка – давайте научимся.

Значит, первое, что нам нужно сделать, это найти пересечение линий на графике. Можно вспомнить курс математики, а можно воспользоваться возможностями библиотеки numpy – там уже кто-то всё вспомнил, запрограммировал и оптимизировал. Нам, по сути, нужно добавить в код пару строчек:

import numpy
import talib
import requests
import json
import time

from matplotlib.finance import candlestick2_ohlc
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import datetime as datetime

PAIR = 'BTC_ETH' # Пара, по которой работаем

start_time = time.time() - 15*60*60 # за какой период брать данные - 15 часов в данном случае

resource = requests.get("https://poloniex.com/public?command=returnChartData&currencyPair=%s&start=%s&end=9999999999&period=300" % (PAIR, start_time))
data = json.loads(resource.text)

quotes = {}
quotes['open']=numpy.asarray([item['open'] for item in data])
quotes['close']=numpy.asarray([item['close'] for item in data])
quotes['high']=numpy.asarray([item['high'] for item in data])
quotes['low']=numpy.asarray([item['low'] for item in data])

xdate=[datetime.datetime.fromtimestamp(item['date']) for item in data]

fig, ax = plt.subplots(3, sharex=True)

candlestick2_ohlc(ax[0], quotes['open'],quotes['high'],quotes['low'],quotes['close'],width=0.6)

ax[0].xaxis.set_major_locator(ticker.MaxNLocator(6))

def chart_date(x,pos):
    try:
        return xdate[int(x)]
    except IndexError:
        return ''

ax[0].xaxis.set_major_formatter(ticker.FuncFormatter(chart_date))

fig.autofmt_xdate()
fig.tight_layout()

macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)
ax[1].plot(macd, color="y")
ax[1].plot(macdsignal)

idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0
print(idx)

plt.show()

Запускаем. Кое-где ругнулось, это связано с отсутствием данных в начале, построился тот же график, что и выше, и в консоль вывелись точки пересечения – в начале много, потому что, опять же, мы берем мало данных с биржи.

Эти цифры обозначают положение по оси X, в которых произошли пересечения. Давайте наложим их на график?

Еще немного меняем код:

import numpy
import talib
import requests
import json
import time

from matplotlib.finance import candlestick2_ohlc
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import datetime as datetime

PAIR = 'BTC_ETH' # Пара, по которой работаем

start_time = time.time() - 15*60*60 # за какой период брать данные - 15 часов в данном случае

resource = requests.get("https://poloniex.com/public?command=returnChartData&currencyPair=%s&start=%s&end=9999999999&period=300" % (PAIR, start_time))
data = json.loads(resource.text)

quotes = {}
quotes['open']=numpy.asarray([item['open'] for item in data])
quotes['close']=numpy.asarray([item['close'] for item in data])
quotes['high']=numpy.asarray([item['high'] for item in data])
quotes['low']=numpy.asarray([item['low'] for item in data])

xdate=[datetime.datetime.fromtimestamp(item['date']) for item in data]

fig, ax = plt.subplots(3, sharex=True)

candlestick2_ohlc(ax[0], quotes['open'],quotes['high'],quotes['low'],quotes['close'],width=0.6)

ax[0].xaxis.set_major_locator(ticker.MaxNLocator(6))

def chart_date(x,pos):
    try:
        return xdate[int(x)]
    except IndexError:
        return ''

ax[0].xaxis.set_major_formatter(ticker.FuncFormatter(chart_date))

fig.autofmt_xdate()
fig.tight_layout()

macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)
ax[1].plot(macd, color="y")
ax[1].plot(macdsignal)

idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0
print(idx)

inters = []

for offset, elem in enumerate(macd):
    if offset in idx:
        inters.append(elem)
    else:
        inters.append(numpy.nan)
ax[1].plot(inters, 'ro')

plt.show()

Запускаем, и видим, как на графике отображаются точки пересечения:

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

Итак, задача: давайте теперь научимся понимать, когда график растет, а когда падает. Но сначала нужно разобраться с данными, которые мы получаем.

Как это работает?

Уберем (временно) все, что связано с кодом, и пристально взглянем на то, с чем работаем.

import numpy
import talib
import requests
import json
import time

from matplotlib.finance import candlestick2_ohlc
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import datetime as datetime

PAIR = 'BTC_ETH' # Пара, по которой работаем

start_time = time.time() - 15*60*60 # за какой период брать данные - 15 часов в данном случае

resource = requests.get("https://poloniex.com/public?command=returnChartData&currencyPair=%s&start=%s&end=9999999999&period=300" % (PAIR, start_time))
data = json.loads(resource.text)

quotes = {}
quotes['open']=numpy.asarray([item['open'] for item in data])
quotes['close']=numpy.asarray([item['close'] for item in data])
quotes['high']=numpy.asarray([item['high'] for item in data])
quotes['low']=numpy.asarray([item['low'] for item in data])

macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)

print('=====MACD=======', macd)
print('*'*80)

print('=====MACDSIGNAL=======', macdsignal)
print('*'*80)

idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0
print('======IDX=======',idx)
print('*'*80)

inters = []

for offset, elem in enumerate(macd):
    if offset in idx:
        inters.append(elem)
    else:
        inters.append(numpy.nan)

print('======INTERS=======',inters)
print('*'*80)

Вот что, примерно, будет выведено у нас в консоль:

Для построения графика мы используем несколько наборов данных. На шкале X мы используем время – для всех графиков в данной статье используется время, которое мы получили с биржи.

В первом графике мы рисуем свечи, используя готовые данные.

На втором мы получаем данные сами, с помощью вызова вот этой функции:

macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)

Как я писал в прошлой статье, мы в результате получаем три набора данных – macd, macdsignal, macdhist – это три массива значений, каждое из которых является значением для оси Y. Т.е. первый элемент будет отрисован в X(0), второй в X(1), на высоте Y, указанной в самом элементе. На скриншоте выше вы видите, я вывел в консоль полученные значения

=====MACDSIGNAL======= [             nan              nan              nan              nan

              nan              nan              nan              nan

              nan              nan              nan              nan

              nan              nan              nan              nan

              nan              nan              nan              nan

              nan              nan              nan              nan

              nan              nan              nan              nan

              nan              nan              nan              nan

              nan   7.91964156e-06   4.14538137e-06   2.60110672e-08

  -4.07798778e-06  -7.95972541e-06  -1.00486163e-05  -1.19658012e-05

  -1.41967407e-05  -1.91172395e-05  -2.54980919e-05  -3.30112736e-05

  -4.08438995e-05  -4.82626516e-05  -5.56410407e-05  -6.42993299e-05

  -7.47543657e-05  -8.34476354e-05  -9.08057290e-05  -9.68925398e-05

  -1.01468395e-04  -1.04801729e-04  -1.06813122e-04  -1.05755025e-04

  -1.00109101e-04  -9.20068930e-05  -8.33780421e-05  -7.33571885e-05

Nan – это значит Not a number, и это то, чего не видно на графике (в начале, линии рисуются с отступом). Дальше идут значения – они крутятся вокруг нуля, именно эти значения мы и отрисовываем на графике и используем для анализа. В данном случае первые 33 элемента отрисованы не будут, потом (X=34, Y=7.91964156e-06), (X=35, Y=4.14538137e-06) и т.п.

То же самое справедливо и для macd.

В этой строке:

idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0

я получаю пересечение значений (или графиков, как удобнее), и получаю на выходе массив индексов.

======IDX======= [  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17

  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  54  75  86

 110 111 114 116 125 154 156 157 161 162]

Что это значит – это значит, если я возьму массив значений macd, то пересечение будет в точках 0, 1, 2, …. 75, 86… 161, 162. То же самое справедливо и для массива macdsignal. Можно сказать и так, что IDX указывает на точки X на графике.

Для отображения пересечений (красных точек на графике) я создаю новый набор данных – он по длине такой же, как macd и macdsignal, но там, где есть пересечение macd и macdsignal, я вставляю значение macd, там где пересечения нет, вставляю nan. Таким образом, я отрисовываю все точки X, но красная точка появится только там, где есть пересечение.

Давайте ловить тренд

Теперь, зная суть вещей, давайте научимся понимать, в какую сторону движется рынок. Вернем отрисовку графиков, и на третьем будем отображать результаты нашего анализа.

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

import numpy
import talib
import requests
import json
import time

from matplotlib.finance import candlestick2_ohlc
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import datetime as datetime

PAIR = 'BTC_ETH' # Пара, по которой работаем

start_time = time.time() - 15*60*60 # за какой период брать данные - 15 часов в данном случае

resource = requests.get("https://poloniex.com/public?command=returnChartData&currencyPair=%s&start=%s&end=9999999999&period=300" % (PAIR, start_time))
data = json.loads(resource.text)

quotes = {}
quotes['open']=numpy.asarray([item['open'] for item in data])
quotes['close']=numpy.asarray([item['close'] for item in data])
quotes['high']=numpy.asarray([item['high'] for item in data])
quotes['low']=numpy.asarray([item['low'] for item in data])

xdate=[datetime.datetime.fromtimestamp(item['date']) for item in data]

fig, ax = plt.subplots(3, sharex=True)

candlestick2_ohlc(ax[0], quotes['open'],quotes['high'],quotes['low'],quotes['close'],width=0.6)

ax[0].xaxis.set_major_locator(ticker.MaxNLocator(6))

def chart_date(x,pos):
    try:
        return xdate[int(x)]
    except IndexError:
        return ''

ax[0].xaxis.set_major_formatter(ticker.FuncFormatter(chart_date))

fig.autofmt_xdate()
fig.tight_layout()

macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)
ax[1].plot(macd, color="y")
ax[1].plot(macdsignal)

idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0
print(idx)

inters = []

for offset, elem in enumerate(macd):
    if offset in idx:
        inters.append(elem)
    else:
        inters.append(numpy.nan)
ax[1].plot(inters, 'ro')

hist_data = []
max_v = 0

for offset, elem in enumerate(macdhist):

    
    if macd[offset] > macdsignal[offset]: # восходящий тренд
        v = 1 #perc
    else:
        v = -1
    hist_data.append(v*1000)

ax[2].fill_between([x for x in range(len(macdhist))], 0, hist_data, facecolor='green', interpolate=True)

plt.show()

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

Мне кажется, боту будет выгоднее торговать в такие моменты:

  1. Когда график падал, но начал расти (в самом конце падения)
  2. Когда график начал расти, растет, и не наметил тенденцию к падению (торговать во время роста, прекратить ближе к концу)

Вот как то так (нарисовал в пейнте. Обвел не всё, но самое показательное):

Понятно, что текущая логика с этим не справляется. Значит, надо её поменять!

Вот какой алгоритм пришел мне в голову: Мы берем разницу между двумя линиями MACD, и сравниваем с максимальной разницей линий MACD за период от одного изменения тренда до другого. Каждый раз, когда тренд разворачивается (пересечение линий) мы обнуляем этот максимум в ноль.

Таким образом, после разворота тренда, первая разница между двумя линиями будет максимальной (100% разницей). После этого если линии еще сильнее разойдутся, то уже следующая разница между линиями станет максимальной. В тот момент, когда линии начнут сходиться, разница уже не будет максимальной, а будет убывать (90%, 80%) и т.п.

Поэтому для торгов мы будем использовать две настроечные переменные:

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

Если рынок медвежий (падает) то торгуй тогда, когда разница между линиями меньше Y%. В таком случае он включится в торговлю только после того как тренд развернется, линии разойдутся на максимум, потом сойдутся почти полностью.

Вот так определим эти переменные:

BEAR_PERC = 70 # При падении рынка

BULL_PERC = 30 # При росте рынка

Каждое из этих значений может колебаться от 0 до 100, не обязательно, что бы они в сумме что-то давали, они независимые.

Давайте внесем изменения в код и посмотрим, что получится:

import numpy
import talib
import requests
import json
import time

from matplotlib.finance import candlestick2_ohlc
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import datetime as datetime

PAIR = 'BTC_ETH' # Пара, по которой работаем

BEAR_PERC = 70
BULL_PERC = 10

start_time = time.time() - 15*60*60 # за какой период брать данные - 15 часов в данном случае

resource = requests.get("https://poloniex.com/public?command=returnChartData&currencyPair=%s&start=%s&end=9999999999&period=300" % (PAIR, start_time))
data = json.loads(resource.text)

quotes = {}
quotes['open']=numpy.asarray([item['open'] for item in data])
quotes['close']=numpy.asarray([item['close'] for item in data])
quotes['high']=numpy.asarray([item['high'] for item in data])
quotes['low']=numpy.asarray([item['low'] for item in data])

xdate=[datetime.datetime.fromtimestamp(item['date']) for item in data]

fig, ax = plt.subplots(3, sharex=True)

candlestick2_ohlc(ax[0], quotes['open'],quotes['high'],quotes['low'],quotes['close'],width=0.6)

ax[0].xaxis.set_major_locator(ticker.MaxNLocator(6))

def chart_date(x,pos):
    try:
        return xdate[int(x)]
    except IndexError:
        return ''

ax[0].xaxis.set_major_formatter(ticker.FuncFormatter(chart_date))

fig.autofmt_xdate()
fig.tight_layout()

macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)
ax[1].plot(macd, color="y")
ax[1].plot(macdsignal)

idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0
print(idx)

inters = []

for offset, elem in enumerate(macd):
    if offset in idx:
        inters.append(elem)
    else:
        inters.append(numpy.nan)
ax[1].plot(inters, 'ro')

hist_data = []
max_v = 0
for offset, elem in enumerate(macdhist):
    curr_v = macd[offset] - macdsignal[offset]
    if abs(curr_v) > abs(max_v):
        max_v = curr_v
    perc = curr_v/max_v
        
    if       (   (macd[offset] > macdsignal[offset] and perc*100 > BULL_PERC) # восходящий тренд
                 or      (
                                 macd[offset] < macdsignal[offset] and perc*100 < (100-BEAR_PERC)
                            )

            ):
        v = 1
    else:
        v = 0
            
    if offset in idx and not numpy.isnan(elem):
        # тренд изменился
        max_v = curr_v = 0 # обнуляем пик спреда между линиями
    hist_data.append(v*1000)
        

ax[2].fill_between([x for x in range(len(macdhist))], 0, hist_data, facecolor='green', interpolate=True)

plt.show()

Работает. Правда, сработало в середине, там где тренд почти развернулся, но передумал, но это от настроек зависит, если выставить BEAR_PERC = 99, например, то такого не будет, но и на падении он не срубит… А если выставить 100 то будет всегда играть только на росте.. В общем это сами решайте. Но по мне, на графике наглядно видно, что в самые печальные моменты бот торговать не будет)

Окей, окей, но это же прошлые периоды, тут и самому можно смотреть, как сделать так, что бы бот в реальном времени смотрел и делал прогнозы?????

Смотрим в будущее

Давайте, как обычно, изменим код, и сделаем (для демонстрации), советчика – он будет говорить нам, торговать или нет, сейчас, в данную минуту. Вынесем часть кода в функцию, и добавим анимацию.

import numpy
import talib
import requests
import json
import time

from matplotlib.finance import candlestick2_ohlc
import matplotlib.animation as animation

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from datetime import datetime

BEAR_PERC = 70
BULL_PERC = 30

PAIR = 'BTC_ETH'

fig, ax = plt.subplots(3, sharex=True)
fig.comment = plt.figtext(.7,.05, '')

def update_graph(interval):
    
    start_time = time.time() - 15*60*60
    resource = requests.get("https://poloniex.com/public?command=returnChartData&currencyPair=%s&start=%s&end=9999999999&period=300" % (PAIR, start_time))
    data = json.loads(resource.text)

    quotes = {}
    quotes['open']=numpy.asarray([item['open'] for item in data])
    quotes['close']=numpy.asarray([item['close'] for item in data])
    quotes['high']=numpy.asarray([item['high'] for item in data])
    quotes['low']=numpy.asarray([item['low'] for item in data])

    xdate=[datetime.fromtimestamp(item['date']) for item in data]

    ax[0].xaxis.set_major_locator(ticker.MaxNLocator(6))

    def chart_date(x,pos):
        try:
            return xdate[int(x)]
        except IndexError:
            return ''
        
    ax[0].clear()
    ax[0].xaxis.set_major_formatter(ticker.FuncFormatter(chart_date))
    
    candlestick2_ohlc(ax[0], quotes['open'],quotes['high'],quotes['low'],quotes['close'],width=0.6)
    
    # print(ax[0].get_xdata())
    fig.autofmt_xdate()
    fig.tight_layout()

    macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)
    ax[1].clear()
    ax[1].plot(macd, color="y")
    ax[1].plot(macdsignal)

    idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0

    inters = []

    for offset, elem in enumerate(macd):
        if offset in idx:
            inters.append(elem)
        else:
            inters.append(numpy.nan)
    ax[1].plot(inters, 'ro')

    #ax[1].scatter(x=ax[0].get_xdata(), y=inters, c='b')

    hist_data = []
    max_v = 0

    
    for offset, elem in enumerate(macdhist):
        activity_time = False
        curr_v = macd[offset] - macdsignal[offset]
        if abs(curr_v) > abs(max_v):
            max_v = curr_v
        perc = curr_v/max_v
        
        if       (   (macd[offset] > macdsignal[offset] and perc*100 > BULL_PERC) # восходящий тренд
                     or      (
                                 macd[offset] < macdsignal[offset] and perc*100 < (100-BEAR_PERC)
                            )

                ):
            v = 1
            activity_time = True
        else:
            v = 0
            
        if offset in idx and not numpy.isnan(elem):
            # тренд изменился
            max_v = curr_v = 0 # обнуляем пик спреда между линиями
        hist_data.append(v*1000)
 
    ax[2].clear()
    ax[2].fill_between([x for x in range(len(macdhist))], 0, hist_data, facecolor='green', interpolate=True)
    plt.gcf().texts.remove(fig.comment)
    fig.comment = plt.figtext(.7,.05, '%s %s%s' % (PAIR, time.ctime(), ' ТОРГУЕМ!!!! ' if activity_time else ''), style='italic', bbox={'facecolor':'red' if activity_time else 'green', 'alpha':0.5, 'pad':10})
    

ani = animation.FuncAnimation(fig, update_graph, interval=1000)
plt.show()

 Теперь наш скрипт каждую секунду (по возможности) получает данные с биржи, строит график и дает нам совет – стоит сейчас торговать или нет. Соответственно, он сам перерисовывает график, и ничего перезапускать не надо.

Прикручиваем к боту

Всё это весело, но смотреть в экран и что-то делать – это не автоматизация. Давайте теперь, используя новые знания, применим их в существующем коде. Начнем с полоникса (раз уж он тут в статье с самого начала).

Poloniex

Уберем графики и всякое такое – нам же важнее ехать, а не шашечки. Сделаем функцию, которая просто принимает данные с биржи, анализирует их, и возвращает решение – торговать или нет.

Убираем все лишнее, и получаем небольшую, скромную функцию, которая возвращает нам все, что надо:

import numpy
import talib
import requests
import json
import time

from datetime import datetime

BEAR_PERC = 70
BULL_PERC = 30

PAIR = 'BTC_ETH'

def should_buy(pair):
    
    start_time = time.time() - 15*60*60
    resource = requests.get("https://poloniex.com/public?command=returnChartData&currencyPair=%s&start=%s&end=9999999999&period=300" % (pair, start_time))
    data = json.loads(resource.text)

    quotes = {}
    quotes['close']=numpy.asarray([item['close'] for item in data])
    macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)

    idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0

    inters = []

    for offset, elem in enumerate(macd):
        if offset in idx:
            inters.append(elem)
        else:
            inters.append(numpy.nan)

    hist_data = []
    max_v = 0

    for offset, elem in enumerate(macdhist):
        activity_time = False
        curr_v = macd[offset] - macdsignal[offset]
        if abs(curr_v) > abs(max_v):
            max_v = curr_v
        perc = curr_v/max_v
        
        if       (   (macd[offset] > macdsignal[offset] and perc*100 > BULL_PERC) # восходящий тренд
                     or      (
                                 macd[offset] < macdsignal[offset] and perc*100 < (100-BEAR_PERC)
                            )

                ):
            v = 1
            activity_time = True
        else:
            v = 0
            
        if offset in idx and not numpy.isnan(elem):
            # тренд изменился
            max_v = curr_v = 0 # обнуляем пик спреда между линиями
        hist_data.append(v*1000)
 
    return activity_time

while True:
    print("Покупать?", should_buy(PAIR))
    time.sleep(1)

Как добавить в код бота для полоникс? В бота на сайте я добавил, вот ссылка на обновленную версию. В какого-то другого – скопируйте функцию, подключите numpy, talib и requests, выставьте параметры  BEAR_PERC и BULL_PERC, ну и вызывайте, когда надо.

Exmo

Тут немного сложнее, т.к. Exmo не возвращает архивные данные по торгам… Плохо, придется собирать инфу с биржи, пока не накопится достаточно данных для анализа. Воспользуемся методом API trades. Он возвращает 100 последних торгов, и, судя по документации к API, ничего больше.. Но если не следовать документации, а добавить на свой страх и риск не указанный параметр limit, то можно получать до 10 000 записей :) Пишем функцию:

import numpy
import talib
import requests
import json
import time

from datetime import datetime

BEAR_PERC = 70
BULL_PERC = 30

PAIR = 'BTC_USD'

def should_buy(pair):
    
    resource = requests.get('https://api.exmo.com/v1/trades/?pair=%s&limit=10000' % pair)
    data = json.loads(resource.text)

    close_prices = {} # сформируем словарь с ценой закрытия по 5 минут
    for item in reversed(data[pair]):
        d = int(float(item['date'])/300)*300 # Округляем время сделки до 5 минут
        close_prices[d] = float(item['price'])
    
    macd, macdsignal, macdhist = talib.MACD(numpy.asarray([close_prices[item] for item in sorted(close_prices)]), fastperiod=12, slowperiod=26, signalperiod=9)

    idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0

    inters = []

    for offset, elem in enumerate(macd):
        if offset in idx:
            inters.append(elem)
        else:
            inters.append(numpy.nan)

    hist_data = []
    max_v = 0

    activity_time = False
    for offset, elem in enumerate(macdhist):
        activity_time = False
        curr_v = macd[offset] - macdsignal[offset]
        if abs(curr_v) > abs(max_v):
            max_v = curr_v
        perc = curr_v/max_v
        
        if       (   (macd[offset] > macdsignal[offset] and perc*100 > BULL_PERC) # восходящий тренд
                     or      (
                                 macd[offset] < macdsignal[offset] and perc*100 < (100-BEAR_PERC)
                            )

                ):
            v = 1
            activity_time = True
        else:
            v = 0
            
        if offset in idx and not numpy.isnan(elem):
            # тренд изменился
            max_v = curr_v = 0 # обнуляем пик спреда между линиями
        hist_data.append(v*1000)
 
    return activity_time

while True:
    print("Покупать?", should_buy(PAIR))
    time.sleep(1)

Её можно прикрутить, опять же, к любому боту, но я прикрутил к нашему, можете скачать обновленную версию. Здесь бот будет покупать реже, но уже с некоторым проблеском интеллекта.

Теперь о том, как строить MACD для эксмо

Давайте наш скрипт модифицируем так, что бы видеть MACD линии и гистограмму для торгов на эксмо, оставив логику советчика - покупать или нет. График советчика я заменил на гистограмму, для полноты картины. Так же добавил в скрипт переменную PERIOD, это время в минутах, за которое строим свечи. Для демострации, я выставил 30 минут - именно так строит эксмо, и так вы можете сравнить точность работы скрипта. Но я советую поменять вам это число на 5 - тогда вы сможете видеть пятиминутные свечи, более оперативно оценивать обстановку, и, возможно, получите преимущество перед теми, кто торгует на эксмо через родной интерфейс биржи.

Вот код:

import numpy
import talib
import requests
import json
import time

from matplotlib.finance import candlestick2_ohlc
import matplotlib.animation as animation

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from datetime import datetime

BEAR_PERC = 70
BULL_PERC = 30

PERIOD = 5 # Период в минутах для построения свечей

PAIR = 'BTC_USD'

SHOW_BOT_CHART = False

fig, ax = plt.subplots(3, sharex=True)
fig.comment = plt.figtext(.7,.05, '')

def update_graph(interval):

    resource = requests.get('https://api.exmo.com/v1/trades/?pair=%s&limit=10000' % PAIR)
    print(resource.text[:100])
    #return
    data = json.loads(resource.text)

    chart_data = {} # сформируем словарь с ценой закрытия по PERIOD минут
    
    for item in reversed(data[PAIR]):
        d = int(float(item['date'])/(PERIOD*60))*(PERIOD*60) # Округляем время сделки до PERIOD минут
        if not d in chart_data:
            chart_data[d] = {'open':0, 'close':0, 'high':0, 'low':0}

        chart_data[d]['close'] = float(item['price'])

        if not chart_data[d]['open']:
            chart_data[d]['open'] = float(item['price'])

        if not chart_data[d]['high'] or chart_data[d]['high'] < float(item['price']):
            chart_data[d]['high'] = float(item['price'])

        if not chart_data[d]['low'] or chart_data[d]['low'] > float(item['price']):
            chart_data[d]['low'] = float(item['price'])
        

    quotes = {}
    quotes['open']=numpy.asarray([chart_data[item]['open'] for item in sorted(chart_data)])
    quotes['close']=numpy.asarray([chart_data[item]['close'] for item in sorted(chart_data)])
    quotes['high']=numpy.asarray([chart_data[item]['high'] for item in sorted(chart_data)])
    quotes['low']=numpy.asarray([chart_data[item]['low'] for item in sorted(chart_data)])

    xdate=[datetime.fromtimestamp(item) for item in sorted(chart_data)]

    ax[0].xaxis.set_major_locator(ticker.MaxNLocator(6))

    def chart_date(x,pos):
        try:
            return xdate[int(x)]
        except IndexError:
            return ''
        
    ax[0].clear()
    ax[0].xaxis.set_major_formatter(ticker.FuncFormatter(chart_date))
    
    candlestick2_ohlc(ax[0], quotes['open'],quotes['high'],quotes['low'],quotes['close'],width=0.6)
    
    fig.autofmt_xdate()
    fig.tight_layout()

    macd, macdsignal, macdhist = talib.MACD(quotes['close'], fastperiod=12, slowperiod=26, signalperiod=9)
    ax[1].clear()
    ax[1].plot(macd, color="y")
    ax[1].plot(macdsignal)

    idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0

    inters = []

    for offset, elem in enumerate(macd):
        if offset in idx:
            inters.append(elem)
        else:
            inters.append(numpy.nan)
    ax[1].plot(inters, 'ro')

    max_v = 0
    hist_data = []
     
    for offset, elem in enumerate(macdhist):
        activity_time = False
        curr_v = macd[offset] - macdsignal[offset]
        if abs(curr_v) > abs(max_v):
            max_v = curr_v
        perc = curr_v/max_v
        
        if       (   (macd[offset] > macdsignal[offset] and perc*100 > BULL_PERC) # восходящий тренд
                     or      (
                                 macd[offset] < macdsignal[offset] and perc*100 < (100-BEAR_PERC)
                            )

                ):
            v = 1
            activity_time = True
        else:
            v = 0
            
        if offset in idx and not numpy.isnan(elem):
            # тренд изменился
            max_v = curr_v = 0 # обнуляем пик спреда между линиями

        hist_data.append(v*1000)

    ax[2].fill_between([x for x in range(len(macdhist))], 0, hist_data if SHOW_BOT_CHART else macdhist,  facecolor='gray', interpolate=True)
    plt.gcf().texts.remove(fig.comment)
    fig.comment = plt.figtext(.6,.05, '%s %s%s' % (PAIR, time.ctime(), ' ТОРГУЕМ!!!! ' if activity_time else ''), style='italic', bbox={'facecolor':'red' if activity_time else 'green', 'alpha':0.5, 'pad':10})
    

ani = animation.FuncAnimation(fig, update_graph, interval=1000)
plt.show()

Вот результат:

О том, как установить библиотеки для запуска, расписано в этой и этой статье, о том, как работать с ботом и всё такое в этой. Ниже под этой статьёй есть ссылки на все статьи цикла, советую прочитать все :)

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

Тэги: