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

В одной из предыдущих статей мы разобрали использование библиотеки 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 = 30 # Период в минутах для построения свечей

PAIR = 'BTC_USD'

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)
    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

    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 # обнуляем пик спреда между линиями

    ax[2].fill_between([x for x in range(len(macdhist))], 0,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()

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

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

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


Это статья из цикла "Популярно о бирже"
Все статьи цикла:

Последнее изменение:


Крипто-кошельки для помощи и благодарности проекту:

Bitcoin адрес проекта: [[address]]

Перевод на сумму [[value]] BTC получен. Спасибо!.
[[error]]

Ethereum адрес проекта: [[address]]



Комментарии
03.11.2017 14:59:41
Доброго дня Андрей!

Спасибо вам за ваши статьи!

сейчас пытаюсь запустить новую версию бота.
столкнулся с такой ошибкой:



Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 17:26:49) [MSC v.1900 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
»>
================ RESTART: C:\Users\***\exmo_macd.py ================
Открытых ордеров нет

Warning (from warnings module):
File "C:\Users\***\exmo_macd.py", line 93
idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0
RuntimeWarning: invalid value encountered in sign
Выход, по MACD рынок падает
Открытых ордеров нет
Выход, по MACD рынок падает
Открытых ордеров нет
Выход, по MACD рынок падает


дальше по кругу пишет:

Открытых ордеров нет
Выход, по MACD рынок падает

и ничего не происходит.

Буду признателен за помощь.
ПроголосоватьПроголосовать
0 0
03.11.2017 15:20:05
Бот работает. Посмотрел график, тренд действительно был нисходящий.
Но ошибка при старте бота так и есть.
ПроголосоватьПроголосовать
0 0
03.11.2017 18:03:29
Да, по сути это не ошибка а предупреждение, на процесс не влияет. Просто когда ищем пересечения, не хватает данных из прошлого, и он не может найти пересечения в самом начале периода, когда боту они и не нужны. На скриншотах видно, свечи строятся с начала, а macd с отступом - именно там и ругается. Попозже я обновлю код, добавлю подавление вывода этого сообщения.
ПроголосоватьПроголосовать
0 0
03.11.2017 20:43:21
Спасибо за доработку бота.
Есть вопросы: ссылка на данные в коде на фолоникс, эта ссылка должны быть и для биржи эксмо?)
И правильно ли я понимаю, что достаточно установить базу талиб и скачать обновленную версию бота?
Так же в обновленной версии ORDER_LIFE_TIME = 0.5, тобишь ордер живет пол минуты?
ПроголосоватьПроголосовать
0 0
03.11.2017 20:53:44
Добрый день.
1. Первого вопроса не понял - в статье есть примеры для кода на полониксе, и для кода на эксмо, и есть ссылки на скачивание ботов для той биржи и для этой. Там разные способы получения данных, т.к. они по разному отдают, но и тот и другой случай разобраны в коде в соответствующих разделах статьи.
2. Да, установить талиб и всё остальное, как расписано тут: https://bablofil.ru/python-indicators. После этого запустить новую версию бота.
3. Да, полминуты, но это под каждую пару надо подбирать эмпирически. В принципе, на эксмо не так часто сделки происходят, так что наверное разумно поставить побольше.
ПроголосоватьПроголосовать
0 0
03.11.2017 22:25:58
Cкачал бота под заголовком Exmo, и там все же внутри кода ссылка: #start_time = time.time() - 15*60*60
#resource = requests.get("https://poloniex.com/public?command=returnChartData¤cyPair=%s&start=%s&end=9999999999&period=300" % (pair, start_time))
#data = json.loads(resource.text).
Мб я что - то путаю?
ПроголосоватьПроголосовать
0 0
03.11.2017 23:04:51
Ааа, точно, этот кусок кода закомментирован, и выполняться не будет, я его забыл удалить (.
Можете у себя удалить, ничего не изменится)
ПроголосоватьПроголосовать
0 0
03.11.2017 22:41:12
И да, ссылки в конце этой статьи Вы не указали, забыли, видимо.
ПроголосоватьПроголосовать
0 0
03.11.2017 23:16:00
Точно, спасибо! Добавил.
ПроголосоватьПроголосовать
0 0
04.11.2017 13:22:37
Здравствуйте, Андрей. Я по ошибке оставил комментарий под другой статьей, там у меня были проблемы с установкой NumPy. С этим я разобрался, но библиотека ta-lib ни в какую не хочет устанавливаться. Можете подсказать, в чем проблема?
C:\Users\Jura>pip install ta-lib
Collecting ta-lib
Using cached TA-Lib-0.4.10.tar.gz
Installing collected packages: ta-lib
Running setup.py install for ta-lib ... error
Complete output from command c:\users\jura\appdata\local\programs\python\pyt
hon35-32\python.exe -u -c "import setuptools, tokenize;__file__='C:\\Users\\Jura
\\AppData\\Local\\Temp\\pip-build-bqdme5_a\\ta-lib\\setup.py';exec(compile(getat
tr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'ex
ec'))" install —record C:\Users\Jura\AppData\Local\Temp\pip-s2sskwci-record\ins
tall-record.txt —single-version-externally-managed —compile:
C:\Users\Jura\AppData\Local\Temp\pip-build-bqdme5_a\ta-lib\setup.py:77: User
Warning: Cannot find ta-lib library, installation may fail.
warnings.warn('Cannot find ta-lib library, installation may fail.')
running install
running build
running build_py
creating build
creating build\lib.win32-3.5
creating build\lib.win32-3.5\talib
copying talib\deprecated.py -> build\lib.win32-3.5\talib
copying talib\test_abstract.py -> build\lib.win32-3.5\talib
copying talib\test_data.py -> build\lib.win32-3.5\talib
copying talib\test_func.py -> build\lib.win32-3.5\talib
copying talib\test_stream.py -> build\lib.win32-3.5\talib
copying talib\__init__.py -> build\lib.win32-3.5\talib
running build_ext
building 'talib.common' extension
error: Unable to find vcvarsall.bat

—--------------------------------------
Command "c:\users\jura\appdata\local\programs\python\python35-32\python.exe -u -
c "import setuptools, tokenize;__file__='C:\\Users\\Jura\\AppData\\Local\\Temp\\
pip-build-bqdme5_a\\ta-lib\\setup.py';exec(compile(getattr(tokenize, 'open', ope
n)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install —record
C:\Users\Jura\AppData\Local\Temp\pip-s2sskwci-record\install-record.txt —single
-version-externally-managed —compile" failed with error code 1 in C:\Users\Jura
\AppData\Local\Temp\pip-build-bqdme5_a\ta-lib\
ПроголосоватьПроголосовать
0 0
04.11.2017 15:04:10
Это он VisualStudio пытается найти на вашем компе.
Что бы не ставить студию, можно скачать тут http://www.lfd.uci.edu/%7Egohlke/pythonlibs/ уже собранный whl файл talib, потом установить его pip install путь_к_файлу.whl
ПроголосоватьПроголосовать
0 0
04.11.2017 15:28:50
Спасибо большое, заработало. А ещё можно узнать, что нужно заменить в скрипте графика, чтобы отображались кривые не с poloniex, а с EXMO?
ПроголосоватьПроголосовать
0 0
04.11.2017 17:32:28
Отличный вопрос!
.Дополнил статью, выложил пример.
ПроголосоватьПроголосовать
0 0
04.11.2017 20:42:41
Скрипт работает, но не без изъянов. На что-то ругается, не отображает нижний график (там серое поле) и не показывает точки
Warning (from warnings module):
File "C:\Users\Jura\AppData\Local\Programs\Python\Python35-32\lib\site-packages\matplotlib\cbook\deprecation.py", line 106
warnings.warn(message, mplDeprecation, stacklevel=1)
MatplotlibDeprecationWarning: The finance module has been deprecated in mpl 2.0 and will be removed in mpl 2.2. Please use the module mpl_finance instead.

Warning (from warnings module):
File "C:\Users\Jura\Desktop\Крипта\chart.py", line 78
idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0
RuntimeWarning: invalid value encountered in sign
ПроголосоватьПроголосовать
0 0
04.11.2017 20:47:27
Что самое интересное, это происходит только на паре BTC_USD. В ZEC_RUB, LTC_EUR всё замечательно
ПроголосоватьПроголосовать
0 0
04.11.2017 21:33:54
Сейчас по BTC_USD пересечений нет.. Можно считать это ограничением эксмо.
Мы можем получить только 10 000 последних торгов.
Если для ZEC_RUB 10 000 это грубо говоря три дня, то для ходовой пары BTC_USD час-два.. Боту для принятия решения такого набора данных хватит, а вот красивый график из этого не построишь.
Поставьте период в 5 минут - будут и точки, и графики
ПроголосоватьПроголосовать
0 0
04.11.2017 23:59:17
Да, так работает. Тем не менее хоть в основном действительно бот стал торговать выгоднее и научился выжидать, но за ним нужно следить, только что он, например, выставил лайт за 158 евро
ПроголосоватьПроголосовать
0 0
05.11.2017 14:26:32
И снова добрый день. Не могу сказать точно, это только у меня так или же у всех, но некоторые тенденции у бота замечены, поэтому хотел бы высказать некоторые пожелания:.
1. Бот очень хорошо работает на биткоинах (за ночь при игре на 8 долларах поднял 30 центов), но на альтах при игре на понижение он часто просчитывается и потом не может продать. Думаю, если прикрутить какой-нибудь скрипт, который при игре на альтах учитывает ещё и курс битка (от которого очень сильно альты зависят), он сможет так же хорошо торговать, как и на битках
2. Бот, как уже сказал, изредка выставляет слишком высокие цены за криптовалюту, когда в альтах
3. Было бы интересно посмотреть, как бот будет работать при учете нескольких индикаторов (например, облака Ишимоку Кинко Хё), и будет ли от этого увеличиваться точность
ПроголосоватьПроголосовать
0 0
13.11.2017 15:14:18
Добрый день! При запуске бота для exmo пишет
Traceback (most recent call last):
File "C:\Bots\exmo_macd.py", line 6, in
import numpy
ModuleNotFoundError: No module named 'numpy'
В чем может быть дело?
ПроголосоватьПроголосовать
0 0
13.11.2017 15:16:20
Разобрался)
ПроголосоватьПроголосовать
0 0
14.11.2017 21:48:03
poloniex_macd.py:62: RuntimeWarning: invalid value encountered in sign
idx = numpy.argwhere(numpy.diff(numpy.sign(macd - macdsignal)) != 0).reshape(-1) + 0

выдает и не торгует что делать подскажите?
ПроголосоватьПроголосовать
0 0
14.11.2017 22:37:37
Предупреждение это нормально, что бы не писал такого, в начале скрипта, после
import numpy

добавьте
numpy.seterr(all='ignore')

А вот почему не торгует, другой вопрос.. Вы правильно указали пару? Что пишет в логе?
ПроголосоватьПроголосовать
0 0
15.11.2017 08:40:24
Все торгует спасибо! Вот такой вопрос BEAR_PERC 70 и BULL_PERC 98 при таких настройка очень мало сделок совершает вы на таких тестировали?
ПроголосоватьПроголосовать
0 0
17.11.2017 13:13:46
Добрый день. На таких же.
Ну да, чем осторожнее бот, тем меньше сделок (удобных ситуаций на рынке по паре), нарастить можно, увеличив количество пар. Вот тут в конце статьи есть отчет по работе подобного бота на другой бирже https://bablofil.ru/bot-dlya-birgi-bittrex/.
ПроголосоватьПроголосовать
0 0
Пожалуйста, авторизуйтесь, что бы оставить свой комментарий