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

Читайте на SMART-LAB:
Фото
USD/CAD: военная разрядка подкосила нефть, но поддерживает канадца
Канадский доллар продолжил заметно укрепляться за прошедший период и достиг локальных максимумов, полностью нивелировав слабость в течение марта....
Фото
Итоги первичных размещений ВДО и некоторых розничных выпусков на 28 апреля 2026 г.
Следите за нашими новостями в удобном формате:  Telegram ,  Youtube ,  RuTube,   Smart-lab ,  ВКонтакте ,  Сайт
В Accent разработали сервис для оценки влияния недвижимости на портфель инвестора
Группа Accent запустила интерактивный инструмент для анализа инвестиционного портфеля. Сервис, доступный на сайте компании, позволяет оценить,...
Фото
Какой убыток мог быть у Магнита в 2025 году?
На этой неделе, вероятно, под занавес сезона годовых отчетов, свои результаты должен опубликовать Магнит. Что ждать и насколько все плохо?

теги блога tranquility

....все тэги



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