Избранное трейдера Денис Сафонов
Привет, сегодня вместо традиционного бэктеста разберем площадки, где можно подсмотреть идеи для торговых стратегий. Навеяно постом Eugene Logunov о литературе для алго-трейдера https://smart-lab.ru/blog/627444.php Теперь у нас есть методики, но где взять идеи? :)
Наши предыдущие бэктесты хоть и адаптированы под Россию и имеют отличия в реализации – все равно основываются на ранее выявленных закономерностях в США/Европе. Сразу скажу, что речь пойдет об исследованиях в открытом доступе. Если на работе/в университете есть доступ к EBSCO или Science Direct, то вы и сами знаете, где все посмотреть.
Зачем вообще читать академические ресерчи, если фонд LTCM показал, что кол-во цитирований и премий спорно соотносится с успехом на рынке?
Хорошие ресерчи дают базовые идеи о том, что и почему работало в прошлом, на каких стадиях и почему перестало. Да, в них есть реализация или дизайн исполнения, но обычно он сырой и его всегда можно поменять, сохранив базовую идею. В отличие от дискуссий в рунете, очень сложно опубликовать что-то без пруфов, а проверка устойчивости не ограничивается t-статистикой > 3. Сам текст хорошо структурирован, методика либо объясняется полностью, либо ссылается на такой текст. Авторы в основном исследователи, которые выполняя свою работу попутно дают подсказки практикам. Но встречаются и практики, например, аналитики хедж фонда AQR сейчас главные поставщики контента по факторным стратегиям или ученые Dimson и Ibbotson, которые параллельно пишут исследования для инвестиционных банков. Если желание почитать что-то заумное осталось, то сформулируйте идею/биржевую аномалию, которую хотите проверить (например, покупка акций с наибольшими дивидендами) и возвращайтесь к этому тексту.

--
-- Выполнение действий с массивами.
--
local pairs = pairs
local type = type
module(...)
--- Создать копию массива (таблицы)
-- @return копию массива (таблицы)
function copy(array)
local copy_array = {}
if type(array) ~= "table" then
return array
end
for k, v in pairs(array) do
if type(v) == "table" then
copy_array[k] = copy(v)
else
copy_array[k] = v
end
end
return copy_array
end
--- Узнать, начинается ли индексация в массиве с нуля или с единицы.
-- @return 0 или 1
function base(array)
if array[0] ~= nil then
return 0
else
return 1
end
end
--- Вычислить число элементов в массиве.
-- @return число элементов в массиве
function size(array)
local n = 0
for _, _ in pairs(array) do
n = n + 1
end
return n
end
--- Проверить пустой или нет массив.
-- @return true/false
function isEmpty(array)
for _, _ in pairs(array) do
return false
end
return true
end
--- Получить первый индекс массива, где ничего не записано. Поиск начинается с 1.
-- @return первый индекс массива, где ничего не записано
function firstEmptyIndex(array)
local i = 1
while array[i] ~= nil do
i = i + 1
end
return i
endТем, кто не читал предыдущий топик этой темы, рекомендую для начала ознакомиться с ним [1].
В комментариях к предыдущему топику меня критиковали за неоптимальность кода Python. Однако, текст читают люди с совершенно разной подготовкой — от почти не знающих Python или знающих другие языки программирования, до продвинутых пользователей. Последние легко могут обнаружить неоптимальность кода и заменить его своим. Тем не менее, код должен быть доступен и новичкам, возможно не обладающим знанием пакетов и продвинутых методов. Поэтому, в коде я буду, по возможности, использовать только базовые конструкции Python, не требующие глубоких знаний, и которые могут легко читаться людьми, программирующими на других языках. Вместе с тем, по мере изложения, без фанатизма, буду вводить и новые элементы Python.
Если вы хотите как-то улучшить или оптимизировать код, приводите его в комментариях — это только расширит и улучшит изложенный материал.
Ну, а сейчас мы займемся разработкой и тестированием индикаторов. Для начала нам нужна простейшая стратегия с использованием МА — его и построим. Самой лучшей по характеристикам МА является ЕМА. Формула ЕМА:
HV, IV, RV, LV, SV – каких только волатильностей не напридумывали….
Куда опционщику смотреть? Что брать за основу? Это я еще про методы измерения не упомянул. Хотя с методами измерения HV – более-менее сошлись во мнении, что Yang-Zhang рулит. Вроде как адекватно описывает.
Не будем оспаривать, по крайней мере не в этой статье.
Я за другое – КАК ЭТО ВСЕ УВИДЕТЬ? В книжках учат наложить два графика друг на друга – HV на IV (ну или на оборот). Посмотреть кто выше – того продать, кто ниже – того купить:

Волатильность — это «медленная цена» или просто стоимость. Т.е. цена опциона зависит от базового актива, дней до экспиры и уровня страха трейдеров. Меняется она очень быстро. Чтобы оценивать именно стоимость опциона (страховки) – как раз и используется IV волатильность. Далее трейдерам нужно понять какая «медленная цена» у самого базового актива – HV волатильность. Вот для нее придумали формулы измерения исторической волатильности. Если погружаться в эти формулы, то начинают появляться новые параметры – приращение доходности, дисперсия и среднеквадратичное отклонение — сигма. Если первые два параметра это промежуточные вычисления, то сигма используется уже более активно. Господин Гаусс когда-то доказал, что в нормально распределенных случайных процессах в 68% случаев изменение величины (у нас это приращение доходности) от среднего не превысит одной сигмы. Те, кто давно в рынке скажут – рынок ни капли не нормально распределяет свои приращения и поправят Гаусса до величины 58%. Всё это интересно, занимательно, но заставляет нас ворошить знания по теорверу и статистике. А нам – трейдерам – дайте лучше кнопку «БАБЛО», а не вот это вот все…..
import requests
import datetime
import pathlib
SECIDs = ["GAZP", "BANEP", "LKOH"]
DISK = "E"
for SECID in SECIDs:
from_date = "2020-05-04"
to_date = "2005-01-03"
while str(to_date) != from_date:
to_date = str(to_date)
to_date = to_date.split('-')
a = datetime.date(int(to_date[0]), int(to_date[1]), int(to_date[2]))
b = datetime.timedelta(days=140)
to_date = a + b
pathlib.Path("{}:/{}/{}".format(DISK, "Database_MOEX", SECID)).mkdir(parents=True, exist_ok=True)
filename = SECID + "_" + str(to_date) + ".csv"
with requests.get("http://iss.moex.com/iss/history/engines/stock/markets/shares/boards/tqbr/securities/{}.csv?date={}".format(SECID, to_date)) as response:
with open("{}:/Database_MOEX/{}/{}".format(DISK, SECID, filename), 'wb') as f:
for chunk in response.iter_content():
f.write(chunk)Для начала пройдемся по его плюсам и минусам. Самый главный минус, что этот парсер качает только определенный период, который уникален для каждой акции, судя по всему для увеличения этого периода надо кинуть бирже на лапу:), и то что информация предоставляется за день, теперь перейдем к плюсам: можно выкачивать историю за определенный период для нескольких инструментов сразу (их количество ограничивается лишь количеством инструментов на мосбиржи), есть возможность назначать диск для сохранения информации, быстрота выгрузки данных.