Блог им. tranquility

Как можно строить свечные графики в питоне.

Как и обещал ранее некоторым участникам, сейчас продемонстрирую код, с помощью которого можно визуализировать свечной график, данные для которого будет взят с сайта Финам. Самое прамолинейное решение — это найти какой-нибудь модуль для питона, которому скармливаются бары, а он тебе выдает, собственно, свечной график. Такие есть, но на тот момент, когда я интересовался темой, найденное меня не устроило. Например, свечной график мне нарисуют, а как на нем тот же индикатор отрисовать — уже проблема. А если надо задать какую-нибудь эдакую линию, маркер, цвет — с этим надо разбираться. Но зачем тратить на это время, если есть весьма добротный модуль для построения графиков Matplotlib, с помощью него можно сделать любой график полиграфического качества, который у тебя в любое издание примут без вопросов, если, конечно, там и смысловая составляющая на должном уровне, само собой. В общем, качаем скрипт отсюда:
yadi.sk/d/fiMn-YUtrB6aEw
если не установлено, устанавливаем python 3.5+, к нему matplotlib и numpy, запускаем скрипт и умиляемся результату))
Как можно строить свечные графики в питоне.
Окно с графиком, которое выдает скрипт позволяет выделять область, например:
Как можно строить свечные графики в питоне.
растягивать ее на всю область графика:
Как можно строить свечные графики в питоне.
и двигать график относительно просматриваемой прямоугольной области.
В принципе, этого функционала вполне достаточно для того, чтобы в одном лишь питоне сделать всю работу по созданию, тестированию и отлаживанию торговой стратегии. Очень удобно рисовать наклонные линии сделок на графике (например, зеленая линия для прибыльного открытия-закрытия позиции и красная для убыточного) и анализировать их входы на предмет как корректности технической составляющей алгоритма стратегии, так и самой торговой идеи. Например, в случае тестера метатрейдера на выходе получаешь много всякой статистики, но анализировать причину убыточности сделок там не так удобно (я таким занимался в начале 2016 года, может с тех пор тестер метатрейдера поменялся).

При желании, можно визуализировать любые другие исторические данные по свечам в формате, который выдает Финам (по тому же яндексу данные качаются здесь: www.finam.ru/profile/moex-akcii/pllc-yandex-n-v/export/).
У скрипта есть три входных параметра (строчки 9, 91 и 92):
dataFile = 'YNDX_190114_190115.csv'
timeZoneDiffSecs = 3 * 3600
pt = 0.01

которые задают файл с данными от Финама, разницу между поясом, для времени которого хотите представить график и UTC, а также минимальный шаг цены инструмента.

Технологически, каждая свеча прорисовывается как errorbar (кресты ошибок), дважды — один раз для тела свечи, другой раз для ее тени. В первом случае отрисовываются горизонтальные линии отмечающие границы тела свечи, во втором эти линии отсутствуют, как у привычного вида тени свечи:
plt.errorbar( xs[ blackBars ], totalCentres[ blackBars ], yerr = totalSpans[ blackBars ], ecolor = 'k', elinewidth = 0.5, capsize = 0, ls = 'none' )
plt.errorbar( xs[ blackBars ], bodyCentres[ blackBars ], yerr = np.abs( bodySpans[ blackBars ] ), ecolor = 'k', elinewidth = 0.5, capsize = 2, ls = 'none' )

Еще, чтобы разобраться в этом коде, надо знать такие аспекты numpy как slicing, например, вот здесь используется:
bodyCentres = 0.5 * ( bars[ :, 1 ] + bars[ :, 4 ] )
bodySpans = 0.5 * ( bars[ :, 4 ] — bars[ :, 1 ] )
totalCentres = 0.5 * ( bars[ :, 2 ] + bars[ :, 3 ] )
totalSpans = 0.5 * ( bars[ :, 2 ] — bars[ :, 3 ] )
и boolean indexing, вот пример использования:
blackBars = np.abs( bodySpans ) < 0.25 * pt
greenBars = np.logical_and( np.logical_not( blackBars ),
                            bodySpans >= 0.25 * pt )
redBars = np.logical_not( np.logical_or( blackBars, greenBars ) )

вот, собственно, сам скрипт:
import os.path, time
import numpy as np
import matplotlib.style
import matplotlib as mpl
mpl.style.use('classic')
mpl.rcParams['axes.formatter.useoffset'] = False
import matplotlib.pyplot as plt

dataFile = 'YNDX_190114_190115.csv'
#dataFile = 'YNDX_190114_190115-1.csv'
#dataFile = 'YNDX_190114_190115-2.csv'
#dataFile = 'YNDX_190114_190115-3.csv'
#dataFile = 'YNDX_181201_190115-4.csv'

def my_split( s, seps ): # this function splits line to parts separated with given separators
    res = [s]
    for sep in seps:
        s, res = res, []
        for seq in s:
            res += seq.split( sep )
    i = 0
    while i < len( res ):
        if res[i] == '':
            res.pop(i)
            continue
        i += 1
    return res

def loadFinamCsv( fname ):
    if not os.path.isfile( fname ):
        raise ValueError( 'wrong file name: %s' % fname )  

    counter = 0

    fi = open( fname, 'r' )

    tickerNameIsFound = False

    for line in fi: # this loop counts number of bars and reads ticker name from the first bar
        firstSymbol = line[ :1 ]
        if firstSymbol == '' or firstSymbol == '<':
            continue
        if not tickerNameIsFound:
            parsed = my_split( line, ',\n' )
            ticker = parsed[0]
            period = parsed[1]
            tickerNameIsFound = True
        counter += 1

    bars = np.zeros( ( counter, 6 ), dtype = np.float64 ) # create matrix for reading the whole file

    print( counter )

    fi.seek( 0, 0 ) # move file pointer to the beginning 

    counter = 0
                 
    for line in fi:
        firstSymbol = line[ :1 ]
        if firstSymbol == '' or firstSymbol == '<':
            continue
        
        parsed = my_split( line, ';,\n' )     

        timeStamp = parsed[2] + parsed[3]
        dtime = time.strptime( timeStamp + '+0300', '%Y%m%d%H%M%S%z' )
        timeEpoch = time.mktime( dtime )

        bars[ counter, : ] = np.array( ( timeEpoch, np.float64(parsed[4]), np.float64(parsed[5]),
                                         np.float64(parsed[6]), np.float64(parsed[7]), np.float64(parsed[8]) ) )

        counter += 1
        if counter % 1000 == 0:
            print( int( counter / 1000 ), end = ' ' )
        
    fi.close()
    print( '\n' )

    return { 'ticker': ticker, 'period': period, 'bars': bars }

def convertPeriodString( periodRaw ):
    try:
        numMins = int( periodRaw )
        if numMins % 60 != 0:
            return 'M%d' % numMins
        else:
            return 'H%d' % ( numMins // 60 )
    except ValueError:
        return periodRaw

timeZoneDiffSecs = 3 * 3600 # we need to know in advance the time zone difference between UTC
pt = 0.01 # we need to know in advance the min price step (point)

data = loadFinamCsv( dataFile )
ticker = data[ 'ticker' ]
period = data[ 'period' ]
bars = data[ 'bars' ]

periodFine = convertPeriodString( period )


xs = np.array( range( bars.shape[0] ) )

bodyCentres = 0.5 * ( bars[ :, 1 ] + bars[ :, 4 ] )
bodySpans = 0.5 * ( bars[ :, 4 ] - bars[ :, 1 ] )
totalCentres = 0.5 * ( bars[ :, 2 ] + bars[ :, 3 ] )
totalSpans = 0.5 * ( bars[ :, 2 ] - bars[ :, 3 ] )

blackBars = np.abs( bodySpans ) < 0.25 * pt
greenBars = np.logical_and( np.logical_not( blackBars ),
                            bodySpans >= 0.25 * pt )
redBars = np.logical_not( np.logical_or( blackBars, greenBars ) )

plt.clf()

plt.errorbar( xs[ blackBars ], totalCentres[ blackBars ], yerr = totalSpans[ blackBars ], ecolor = 'k', elinewidth = 0.5, capsize = 0, ls = 'none' )
plt.errorbar( xs[ blackBars ], bodyCentres[ blackBars ], yerr = np.abs( bodySpans[ blackBars ] ), ecolor = 'k', elinewidth = 0.5, capsize = 2, ls = 'none' )

plt.errorbar( xs[ greenBars ], totalCentres[ greenBars ], yerr = totalSpans[ greenBars ], ecolor = 'g', elinewidth = 0.5, capsize = 0, ls = 'none' )
plt.errorbar( xs[ greenBars ], bodyCentres[ greenBars ], yerr = np.abs( bodySpans[ greenBars ] ), ecolor = 'g', elinewidth = 0.5, capsize = 2, ls = 'none' )

plt.errorbar( xs[ redBars ], totalCentres[ redBars ], yerr = totalSpans[ redBars ], ecolor = 'r', elinewidth = 0.5, capsize = 0, ls = 'none' )
plt.errorbar( xs[ redBars ], bodyCentres[ redBars ], yerr = np.abs( bodySpans[ redBars ] ), ecolor = 'r', elinewidth = 0.5, capsize = 2, ls = 'none' )

plt.xlabel( 'Bar No., %s' % periodFine )
plt.ylabel( ticker ) # before was: fname[ : fname.find( ' ' ) ]

plt.xlim( xs[0] - 0.5, xs[-1] + 0.5 )

plt.annotate( 'start: %s' % ( time.strftime( '%Y-%m-%d %H:%M:%S', time.gmtime( bars[ 0, 0 ] + timeZoneDiffSecs ) ) ),
            xy=(0.1,0.95), xycoords='axes fraction',
            fontsize=11, horizontalalignment='left', verticalalignment='top' )

plt.annotate( 'end: %s' % ( time.strftime( '%Y-%m-%d %H:%M:%S', time.gmtime( bars[ -1, 0 ] + timeZoneDiffSecs ) ) ),
            xy=(0.1,0.90), xycoords='axes fraction',
            fontsize=11, horizontalalignment='left', verticalalignment='top' )

plt.savefig( dataFile[ : dataFile.rfind( '.' ) ] + '.png')

plt.show()


В скрипте присутствует всего несколько комментариев, но, думаю, кому это действительно покажется полезным, сам разберется. Если что — задавайте вопросы. Буду рад, если этот мой небольшой труд кому-то поможет вывести какую-нибудь гениальную торговую идею, чего всем и желаю))
  • Ключевые слова:
  • python
★34
9 комментариев
сейчас гусев обкончается
avatar
годный скрипт. Спасибо.
незнал про этот модуль.
Есть plotly тоже рисует графики, причем может сгенерить html с ява скриптом и масштабированием, а может вывести в jupiter.
avatar
A Figushkin, странно, Вы не слышали про matplotlib, а я — про plotly, как такое может быть?)) Про первый знаю точно — он очень хорошо документирован, по любому вопросу ответ в гугле на раз находится на stackoverflow и подобных.
avatar
Спасибо. Я как раз вывел уже хениальную торг идею. Тока рынок пока не дошел до точки входа, а я пока квартиру заложу, чтобы накупиться по самые помидоры в день Х. Помалу зарабатывать не превыкши!
avatar
Я бы сказал, что это больше бары, чем свечи).
avatar
Так вроде на самом сайте Финама и так бары рисуются, не?
Пафос Респектыч, рисуются. Но на сайте Финама свою стратегию не потестишь и линии сделок не нарисуешь. А еще если взять короткий таймфрейм и длинный промежуток времени, прокручивать назад замучаешься пока найдешь то, что тебя интересует, а тут — просто выделил нужный участок и смотри что там творилось, как в лупу)
avatar
tranquility, ну в лупу так в лупу )
Здравствуйте, в этой статье вы показываете как можно, используя Python, нарисовать график тиковых баров, но у вас уже есть данные с начальной ценой, конечной, низкой и высокой. А у меня есть только данные по цене, объёму и временным меткам, как в таком случае быть? Если я из таких сырых данных хочу построить график баров и свечей?
avatar

теги блога tranquility

....все тэги



UPDONW
Новый дизайн