Избранное трейдера Kotcher
Если вы задумывались о системной торговле, то, скорее всего, уже слышали о Python библиотеке Backtrader. Это гибкий фреймворк для тестирования торговых стратегий на исторических данных, который к тому же может быть подключён к автоторговле через API российского брокера. В нём можно реализовать практически любую логику, от простого пересечения скользящих средних до сложных многофакторных моделей.
➡️ Робот, который живёт в стене: мой опыт автоматизации торговли на Python
Однако даже самая изощрённая стратегия ничего не стоит, если протестирована на неликвидных бумагах — там, где в реальной торговле вы бы просто не смогли купить или продать по нужной цене. Именно поэтому работа с ликвидными акциями — ключ к достоверному тесту.
Ликвидность — это не про «красиво на графике», а про то, как на самом деле исполняются сделки, насколько проскальзывает цена и как часто ваши заявки останутся без исполнения. Здесь нам поможет Игорь Чечет — автор библиотек AlorPy, TinkoffPy и FinamPy, размещенных на GitHub, которые дают удобный способ подключиться к API этих трёх брокеров из Python. Эти инструменты и библиотека-обертка — фактически мост между Backtrader и живым рынком.
В предыдущих статьях я рассказывал, как пришёл к идее создания собственного торгового робота. Мотивация проста:
Автоматизация — алгоритм не спит, не нервничает и не занят своими делами.
Дисциплина — робот исключает эмоции, следуя правилам.
Тестирование — любую идею можно проверить на исторических данных, прежде чем рисковать деньгами.
Я всегда разделял два этапа: разработку торговых идей (логика стратегии) и реализацию механизма исполнения (отправка заявок, автотрейдинг). Сначала — бэктестинг и базовая оптимизация, и только потом — реальная торговля.
Поскольку я нахожусь в активном поиске подходящего решения для автотрейдинга и уже опробовал несколько рабочих вариантов, то эта статья представляет мои размышления об этом механизме исполнения заявок. Ваша критика или поддержка идей приветствуется.
Почему я не хочу использовать QUIК и Windows?
По моему мнению QUIK архаичен, нестабилен для автоматизации и требует оконной среды. Он не предназначен для headless-серверов (это компьютер без монитора, клавиатуры, мыши). QUIK + LUA или внешнее ПО — это сложная, криво документированная и уязвимая связка.

С любой прибыли, будь то дивиденды, купоны или изменение цены в плюс, вы должны уплатить налог. Это происходит автоматически, благодаря вашему брокеру. Он же передает информацию в налоговую службу. Возиться с декларацией, что-то там подсчитывать не нужно.
Однако есть такие бумаги, называемые депозитарными расписками. Это на самом деле не акции. Хотя не многие об этом знают. В этом и состоит подвох. Инвестор приобретает на Московской бирже, как он полагает, акцию нашей, российской компании. Но по сути он покупает бумагу иностранного эмитента. Как так получается?
Мне всегда было интересно узнать, что чувствуют люди во время финансовых кризисов, какие действия принимают, как влияют на их решения эмоции и насколько сложно не поддаваться панике. И я не имею в виду трейдеров, которые прогорели на своих спекуляциях, изначально взяв на себя слишком большой риск, рассчитывая на высокую доходность. Эти истории ничем не примечательны, разве что своими кричащими заголовками в СМИ: “Трейдер вышел в окно, когда узнал, что все потерял, да ещё остался должен”.

Мне интересно, как справляются с кризисами консервативные долгосрочные инвесторы, которые, имея на счету приличный капитал, буквально за несколько дней лишаются сумм, равных нескольким годовым бюджетам семьи. И если для молодых людей на стадии накопления любой кризис — это окно возможностей, шанс купить акции с хорошей скидкой, то вот для тех людей, которые живут на доход с капитала и регулярно делают изъятия — это катастрофа. В нашей стране подавляющее большинство пенсионеров выживает на государственную пенсию. Можно сказать, что они постоянно находятся в состоянии финансовой катастрофы, если не позаботились о своей пенсии заранее и дети им не помогают.
----------
Протестил с 2010 по 2021 год включительно несколько фьючей. Потери заложил -5 рублей на вход и -5 рублей на выход. Тестил по годам.
Вердикт:
Рыба есть. Местами даже жирная. Но алгоритм дает внутригодовые просадки таких адских амплитуд, что среднестатистический мужчина будет срать силикатными кирпичами и кричать от боли.
Например, внутригодовое отклонение от идеальной (прямой) эквити может превышать 100% от финального результата. А брент в 2014 году нарисовал акуенный минус. Сишка нарисовала не менее акуенный минус в 2021 году.
В 2019 году в TSLab сделал тесты стратегии «Hi_Lo», которая установлена в базовой версии этой программы. Смысл стратегии заключается в том. что вход в лонг осуществляется при пробитии хая предыдущей свечи, вход/переворот в шорт осуществляется при пробитии лоя предыдущей свечи. В TSLab мною был создан скрипт для тестирования одновременной торговли несколькими инструментами с целью диверсификации:

В результате тестирования и опыта торговли остановился на следующем варианте: торгуются фьючерсы RTS, Si, BR в соотношении 1:6:4, дневной таймфрейм. Результаты тестов за период с 01.01.2003 г. по настоящее время без капитализации, без учета комиссии и проскальзывания представлены ниже:
В 2020 году у меня возникла идея создать торговую стратегию, использующую только фактор времени, т.е. открытие и закрытие позиции в определенное время без дополнительных сигналов. В результате в TSLab был создан скрипт:

Тестировались фьючерсы RTS, Si, BR. Наиболее устойчивая закономерность найдена во фьючерсном контракте на индекс RTS при следующих параметрах: вход в шорт в 10.45, переворот в лонг в 17.15 и закрытие позиции в конце торговой сессии. Результаты тестов за период с 01.01.2017 г. по настоящее время без учета комиссии представлены ниже:

Settings=
{
Name = "AT-obl_can", -- название индикатора
delta=2.0, -- параметр индикатора
rep=5,
shif=0,
wt=1,
line=
{
{
Name = "ln1",
Type =TYPE_LINE,
Width = 2,
Color = RGB(255, 0, 0)
},
{
Name = "ln2",
Type =TYPE_LINE,
Width = 2,
Color = RGB(255, 0, 0)
}
}
}
function Init()
vMin = 0
vMax = 0
vMinindex = 0
vMaxindex = 0
voldMinindex = 0
voldMaxindex = 0
return 2
end
function OnCalculate(index)
rep = Settings.rep
shif = Settings.shif
wt = Settings.wt
sz = Size()-shif
if index <= sz then
if index <= 1 then
vMin = C(index)
vMax = C(index)
vMinindex = index
vMaxindex = index
voldMinindex = index
voldMaxindex = index
v = C(index)
else
if voldMaxindex >= voldMinindex then
--if vMin~=nil then
if C(index) > (1 + Settings.delta/100)*vMin then
vMin = C(index)
vMax = C(index)
vMaxindex = index
voldMinindex = vMinindex
vFrom = vMinindex
else
if vMin > C(index) then
vMin = C(index)
vMinindex = index
vFrom = voldMaxindex
else
vFrom = vMinindex
end
end
--end
else
if voldMaxindex <= voldMinindex then
--if vMax~=nil then
if C(index) < (1 - Settings.delta/100)*vMax then
vMax = C(index)
vMin = C(index)
vMinindex = index
voldMaxindex = vMaxindex
vFrom = vMaxindex
else
if vMax < C(index) then
vMax = C(index)
vMaxindex = index
vFrom = voldMinindex
else
vFrom = vMaxindex
end
end
--end
end
end
--if vFrom~=nil then
--[[
for i = vFrom, index do
k = (C(index)- C(vFrom))/(index- vFrom);
v = i*k + C(index) - index*k
SetValue(i, 1, v)
end --]]
--end
if index == sz then
for k = 1, 2 do
vf = 1
vs = 0
if k == 1 then
if vMinindex < vMaxindex then
vf = vMinindex
vs = vMaxindex
up = 0
elseif vMinindex > vMaxindex then
vs = vMinindex
vf = vMaxindex
up = 1
end
elseif k == 2 then
if voldMinindex < voldMaxindex then
vf = voldMinindex
vs = voldMaxindex
up = 0
elseif voldMinindex > voldMaxindex then
vs = voldMinindex
vf = voldMaxindex
up = 1
end
end
n = 0
xy = 0
x = 0
y = 0
xx = 0
m = 0
for i = vf, vs do
m = m +1
n = n + 1*(1+wt*m)
xy = xy + i*C(i)*(1+wt*m)
x = x + i*(1+wt*m)
y = y + C(i)*(1+wt*m)
xx = xx + i*i*(1+wt*m)
end
if (n*xx - x*x) ~= 0 and n ~= 0 then
a = (n*xy - x*y)/(n*xx - x*x)
b = (y - a*x)/n
for j = 1, rep do
n = 0
xy = 0
x = 0
y = 0
xx = 0
m = 0
for i = vf, vs do
v = a*i + b
clc = 0
if up == 1 and C(i) > v then
clc = 1
end
if up == 0 and C(i) < v then
clc = 1
end
if clc == 1 then
m = m + 1
n = n + 1*(1+wt*m)
xy = xy + i*C(i)*(1+wt*m)
x = x + i*(1+wt*m)
y = y + C(i)*(1+wt*m)
xx = xx + i*i*(1+wt*m)
end
end --[[--]]
if (n*xx - x*x) ~= 0 and n ~= 0 and n > 2 then
a = (n*xy - x*y)/(n*xx - x*x)
b = (y - a*x)/n --[[ --]]
end
end
for i = vf, sz do
v = a*i + b
if up == 1 and v >= C(vs) or up == 0 and v <= C(vs) then
SetValue(i, k, v)
end
end
end
end
end
end
end
end