Улучшение алгоритмов

Maximum stock weight

Неразумно полагаться на одну-две компании при написании алгоритма. В таком случае, даже если стратегия верна, непредсказуемые мировые события/новости могут нанести непоправимый ущерб инвестиционному портфелю (Например, 1 и 2).

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

Хороший способ диверсифицировать риски - увеличить количество инструментов в инвестиционном портфеле. После этого мы можем maximum stock weight сделать равным 0.05. Это означает, что мы будем выделять не более 5% нашего капитала на каждую инструмент в портфеле.

def get_weights_bordering_max_capital(weights, border=0.1):
    weights_return = weights.fillna(0)
    under_border = weights_return <= border
    weights_return = weights_return.where(under_border)
    weights_return = weights_return.fillna(border)

    up_border = weights_return >= -1 * border
    weights_return = weights_return.where(up_border)
    weights_return = weights_return.fillna(-1 * border)

    return weights_return

Пример использования

import qnt.data as qndata
import qnt.stats   as qnstats
import qnt.stepper as qnstepper
import datetime as dt

data = qndata.load_data(
                       tail = dt.timedelta(days=4*365),
                       forward_order=True)

def get_weights_strategy(data):

    strategy = data.sel(field="open") * data.sel(field="is_liquid")

    weights = strategy / abs(strategy).sum('asset')
    return weights


def get_weights_bordering_max_capital(weights, border=0.1):
    weights_return = weights.fillna(0)
    under_border = weights_return <= border
    weights_return = weights_return.where(under_border)
    weights_return = weights_return.fillna(border)

    up_border = weights_return >= -1 * border
    weights_return = weights_return.where(up_border)
    weights_return = weights_return.fillna(-1 * border)

    return weights_return


weights_clean = get_weights_strategy(data)
weights = get_weights_bordering_max_capital(weights_clean,
                                   0.05)



stat = qnstats.calc_stat(data, weights)
display(stat.to_pandas().tail())

qnstats.print_correlation(weights, data)

qnstepper.write_output(weights)
field equity relative_return volatility underwater max_drawdown sharpe_ratio mean_return bias instruments avg_turnover avg_holding_time
time
2020-08-17 1.775526 0.007091 0.209698 0.000000 -0.309622 0.709352 0.148750 1.0 951.0 0.023546 109.270150
2020-08-18 1.782287 0.003808 0.209700 0.000000 -0.309622 0.710370 0.148965 1.0 951.0 0.023550 109.262512
2020-08-19 1.775737 -0.003675 0.209498 -0.003675 -0.309622 0.733613 0.153691 1.0 951.0 0.023553 109.253208
2020-08-20 1.780279 0.002558 0.209498 -0.001127 -0.309622 0.740572 0.155149 1.0 951.0 0.023550 109.264783
2020-08-21 1.785862 0.003136 0.209503 0.000000 -0.309622 0.746723 0.156440 1.0 951.0 0.023554 117.762112
WARNING! This strategy correlates with other strategies.
The number of systems with a larger Sharpe ratio and correlation larger than 0.8: 2
The max correlation value (with systems with a larger Sharpe ratio): 0.9009298804903136
Current sharpe ratio(3y): 0.7467229068047863

write output: /root/fractions.nc.gz

Neutralization

Давайте проанализируем показатели акций 500 самых крупных компаний, котирующихся на фондовых биржах США. Так называемый индекс S&P500 (рис. 1). Видно, что в среднем рынок растёт. Доход S&P500 сильно варьируется от нескольких процентов до более чем 20% для некоторых годов. Значит ли это, что просто держать длинные позиции хорошая идея?

sp500 Рис.1

Несмотря на рост S&P500, его Sharpe ratio менее 1. Одна из главных причин - периодические финансовые кризисы. Вот некоторые из них:

  • 1987 год. «Черный понедельник». Индекс Dow Jones упал на 22,6%. Причина - массовый отток инвесторов с региональных рынков.

  • 2000-2003. Крах доткомов. Кризис вызван массовым вложением денег в интернет-проекты.

  • 2007–2008. Мировой экономический кризис. Начался с ипотечного кризиса в США, банкротства банков и падения цен на акции, проложив путь мировому экономическому кризису (иногда называемому «великой рецессией»)

Последствия кризисов видны на графике S&P 500 и проявляются в падении рынка до 30%. Наивно думать, что кризис это страшные истории из прошлого. Начало 2020 года ознаменовалось падением экономики, вызванным коронавирусом.

Мы можем исключить влияние рынка, если уравновесим длинные и короткие позиции для нашего алгоритма. Таким образом, суммарные инвестиции в рынок составят $0. Нейтрализация может быть сделана для всего рынка или для каждого сектора экономики отдельно (в целом для любой группы). Математическая формулировка нейтрализации крайне проста.

Допустим алгоритм определил вектор весов weightsi для i-ого дня. Для того что бы сделать алгоритм нейтральным относительно всего рынка (market-neutral), достаточно изменить веса алгоритма следующим образом:

neutralized_weightsi = weightsi - mean(weightsi).

Теперь среднее вектора весов для каждого дня равно нулю. Это означает, что мы не вкладываем деньги и не выводим их с рынка.

from qnt.neutralization import neutralize

weights = neutralize(weights, weights.asset ,group = 'market')

Пример использования

import qnt.data as qndata
import qnt.stats   as qnstats
import qnt.stepper as qnstepper
import datetime as dt
from qnt.neutralization import neutralize

data = qndata.load_data(
                       tail = dt.timedelta(days=4*365),
                       forward_order=True)

def get_weights_strategy(data):

    strategy = data.sel(field="open") * data.sel(field="is_liquid")

    weights = strategy / abs(strategy).sum('asset')
    return weights


weights_clean = get_weights_strategy(data)
weights = neutralize(weights_clean, weights_clean.asset ,group = 'market')



stat = qnstats.calc_stat(data, weights)
display(stat.to_pandas().tail())

qnstats.print_correlation(weights, data)

qnstepper.write_output(weights)
field equity relative_return volatility underwater max_drawdown sharpe_ratio mean_return bias instruments avg_turnover avg_holding_time
time
2020-08-17 1.371047 0.003825 0.085536 0.000000 -0.103292 0.967307 0.082739 0.525606 951.0 0.018116 92.184119
2020-08-18 1.380413 0.006832 0.085615 0.000000 -0.103292 0.989071 0.084680 0.525149 951.0 0.018120 92.183188
2020-08-19 1.377511 -0.002102 0.085543 -0.002102 -0.103292 1.007448 0.086180 0.523804 951.0 0.018130 92.173027
2020-08-20 1.383244 0.004161 0.085570 0.000000 -0.103292 1.027185 0.087896 0.523686 951.0 0.018136 92.178857
2020-08-21 1.387808 0.003299 0.085586 0.000000 -0.103292 1.040333 0.089038 0.522531 951.0 0.018140 96.456512
WARNING! This strategy correlates with other strategies.
The number of systems with a larger Sharpe ratio and correlation larger than 0.8: 2
The max correlation value (with systems with a larger Sharpe ratio): 0.9132571807203661
Current sharpe ratio(3y): 1.0403329174432017

write output: /root/fractions.nc.gz