Выполняю свое обещание разобрать структуру Робота StepByStep по заменяемым блокам.
Робот на стратегии StepByStep, выбран конечно не случайно. Стратегия очень простая, но при этом довольно интересная, а главное таит в себе неограниченные возможности модернизации и наращивания потенциала. При этом стратегия остается очень надежной и не совершает убыточных сделок по самой сути стратегии. А это означает, что даже не опытный инвестор, не потеряет вложенные средства по вине Робота, если будет использовать Робота по назначению. Стратегия StepByStep и Робот на основе этой стратегии относятся к инвестиционным дивидендным, хотя Робот и совершает много сделок и работает на малых таймфреймах. предпочтительными инструментами для работы Робота на стратегии StepByStep являются дивидендные акции. Я лично запускаю Роботов на таймфрейме 1 минута, хотя я сам использую не только дивидендные акции, новичкам все же лучше начать именно с дивидендных акций. По мере освоения работы Робота, можно попробовать использовать и другие инструменты.
Еще важные замечания. Инвестиционные Роботы это не Грааль, он не сможет вас полностью защитить от ваших ошибочных действий, поэтому внимательно читайте описания, что не понятно лучше несколько раз переспросите, пока не получите ясной картины. Хотя Роботы обладают собственным "интеллектом", интеллект робота знает и умеет только то, чему его научил, тот, кто написал программу Робота. Робот никогда не будет умнее вас.
Долго думал, как удобнее для читателей, сначала выложить полный текст Робота, а потом разложить его на блоки, или опубликовать Блоки Робота из которых можно собрать полноценного Робота. А в случае затруднения уже выложить полный код в статье. так, что жду ваших комментариев.
И так приступим.
Блок №1. Инициализация.
Это блок инициализации, тут происходит инициализация кода Робота, указывается откуда берутся данные о цене, под каким именем будет работать Робот и на какой акции .
function Initialize()
{
StrategyName = "StepByStep_SBS_v62";
AddInput("Input1", Inputs.Candle, 5, true, "CHMF=МБ ЦК");
Блок №2. Параметры.
Этот блок параметров робота, которые вы должны установить при запуске Робота. Данный параметры работают с момента запуска Робота, до его остановки. Если Робота остановить, то можно поменять параметры и они вступят в силу при следующем запуске Робота. тут можно изменить количество покупаемых Роботом акций, изменить проценты на которые должна измениться цена, что бы Робот совершил покупку или продажу и так далее.
Я в Робота добавил параметр DohodZakr. Дело в том, что при изменении кода Робота, и в некоторых других случаях, например при сплите акций, приходится создавать нового робота, и Доход заработанный Роботом может теряться для нового Робота. А в инвестициях интересно и важно знать, какой доход получил Робот именно на данной акции. Кроме того, сюда вписываю полученные по акции дивиденды. Далее поясню как я эти данные использую.
Добавил два параметра-коэффициента kBuy и kSell, это множители для изменения процентов на покупку и продажу уровней, Введены для регулирования расходования КЭШа. Конечно, и на покупку и на продажу должны стоять одинаковые проценты, иначе, если процент на покупку больше, чем процент на продажу мы будем терять пропорционально в доходности.
AddParameter("StartQ", 0, "Стартовое кол-во", 0);
AddParameter("Q", 10, "Кол-во докупки", 0);
AddParameter("DeltaPercentBuy", 0, "% изменения цены для докупки верхний", 0);
AddParameter("DeltaPercentBuy1", 5, "% изменения цены для докупки нижний", 0);
AddParameter("DeltaPercentSell", 15, "% изменения цены для продажи нижний", 0);
AddParameter("DeltaPercentSell1", 5, "% изменения цены для продажи верхний", 0);
AddParameter("DohodZakr", 705.11, "% доход по закрытым роботам", 0);
AddParameter("kBuy", 1, "Коэффициент увеличения Delta", 0);
AddParameter("kSell", 1, "Коэффициент увеличения Delta", 0);
Блок№3. Глобальные переменные.
AddGlobalVariable("inited", Types.Boolean, false);
AddGlobalVariable("lastSignalId", Types.Int, 0);
AddGlobalVariable("lastSignalProcessed", Types.Int, 0);
AddGlobalVariable("xPosition", Types.Double, 0.0);
AddGlobalVariable("LastPrice", Types.DoubleDictionary);
AddGlobalVariable("LastPriceCount", Types.Int, 0);
AddGlobalVariable("LongLimitStart", Types.Double, 0);
AddGlobalVariable("StartPrice", Types.Double, 0);
AddGlobalVariable("Delta", Types.Double, 0);
AddGlobalVariable("DeltaPercentBuyNew", Types.Double, 0);
AddGlobalVariable("DeltaPercentSellNew", Types.Double, 0);
AddGlobalVariable("LastMinPrice", Types.Double, 0);
AddGlobalVariable("MinPrice", Types.Double, 0);
AddGlobalVariable("Type", Types.Double, 1);
AddGlobalVariable("Dohod", Types.Double, 0);
AddGlobalVariable("DohodAkc", Types.Double, 0);
AddGlobalVariable("DohodLot", Types.Double, 0);
Глобальные переменные сохраняются неизменными при остановке робота и последующем запуске, даже в случае перезагрузки терминала. В эти переменные записывают данные которые Робот рассчитывает в процессе своей работы и которые ему (и Вам) нужны для правильной реакции на события происходящие с акцией. В ручную менять глобальные переменные не рекомендуется, и в терминале эта функция не предусмотрена. Конечно глобальные переменный можно корректировать через специальные ухищрения, но это может нарушить логику работы Робота, и поэтому нужно досконально понимать, что вы меняете и как это отразится на функционале Робота.
Некоторые глобальные переменные для нас носят справочный характер, нужны для понимания текущего состояния работы Робота. Например в глобальной переменной xPosition мы видим набранную Роботом позицию, а в глобальной переменной LastPrice можно посмотреть цены уровней и количество акций купленных на каждом уровне.
В глобальной переменной Dohod в данной версии Робота, у нас будет отражаться, сколько Роботы всего заработали на данной акции (включая закрытые Роботы), + полученные дивиденды.
В глобальной переменной DohodAkc я рассчитываю, сколько акций на данный момент можно купить на доход заработанный Роботом. Что то типа окупаемости. В глобальной переменной DohodLot считается сколько лотов акций можно купить на доход заработанный Роботом. На основании значения этой переменной я принимаю решение об реинвестировании дохода в данную акцию. Я писал об этом принципе целую статью, правда давно. Если сказать коротко, так как я на пенсии, то реинвестируется не весь доход, а определенный процент, пока получается реинвестировать в диапазоне 20-40% от заработанного дохода Робота, в зависимости от текущего состояния рынка. Этот принцип позволяет автоматически больше вкладывать в акции приносящие больше дохода и меньше в приносящие мало. Так как рынок изменчив и аутсайдеры иногда начинают приносить хороший доход, а лидеры типа Газпрома, становятся аутсайдерами, я не продаю активы, и не останавливаю Роботов, а просто получаю дивиденды, если их "неожиданно" не отменяют))).
Блок№4. Индикаторы.
AddChartIndicator("MY.str_Invest_Grid_5", new Dictionary <string, string>{{"Price", "MinPrice"},{"PL", "DeltaPercentBuyNew"},{"PL1", "DeltaPercentBuy1"},{"Delta", "DeltaPercentSell"},{"Delta1", "DeltaPercentSell1"}, {"Type", "Type"}});
В данном Роботе, приведенный индикатор не участвует в функционале работы. Индикатор тут предназначен исключительно для визуализации состояния рынка и работы и состояния Робота, поэтому игнорировать индикатор не целесообразно. Тем более на этапе освоения Робота, понимания последовательности его действий, а так же индикатор очень поможет для разработки модернизаций и дополнений.
В этот блок можно добавить любые нужные вам индикаторы для анализа и необходимые вам для использования в модернизированных Роботах. Но об этом поговорим позднее.
Блок №5. Первый запуск Робота и покупка стартовой позиции.
function OnUpdate()
{
if(StartPrice == 0)
StartPrice = Input1.Close[0];
double pos = CurrentPosition();
var signal = GetSignalInfo(lastSignalId);
var activeOrder = GetActiveOrders().FirstOrDefault(ao => ao.SignalId == lastSignalId);
// StartQ - позиция робота при старте
// Правило 1. если первый запуск и робот не проинициализирован, покупаем StartQ
if (pos < StartQ && !inited)
{
if ( (Q%LotSize() != 0) || (StartQ%LotSize() != 0) )
{
ShowMessage("StartQ или Q не кратно лоту.Робот остановлен!");
Stop();
}
if(LongLimitStart == 0)
{
LongLimitStart = LongLimit;
SetLongLimit(StartQ);
}
// Покупка StartQ
if (StartQ > 0)
{
EnterLongLimit(Input1.Close[0]*(1.0 + 0.01 * OrderSlippage));
var lastSignal = GetLastSignalInfo();
if(lastSignal != null)
{
lastSignalId = lastSignal.SignalID;
lastSignalProcessed = 0;
}
return;
}
}
В данном блоке мы сначала указываем Роботу текущую на данный момент стоимость инструмента, устанавливаем значение основных глобальных переменных на момент первого запуска.
Затем в правиле №1 проверяем, что это именно первый запуск Робота и заданная нами стартовое количество для первой покупки (StartQ) больше позиции Робота (при запуске Робота, мы можем указать ему начальную позицию, если у нас имеются уже эти акции и мы хотим передать их Роботу в управление). Далее идет проверка, что мы правильно задали параметры Робота, в частности проверяется, кратность StartQ количеству акций в Лоте. Ведь мы не можем купить только часть Лота акций. Если не кратно, происходит принудительная остановка Робота с выдачей сообщения об ошибке параметра.
Затем, мы сохраняем в переменной LongLimitStart значение LongLimit нашего Робота и устанавливаем максимальное разрешенное количество акций для покупки равное нашему StartQ. Ну а затем выставляется лимитная заявка непосредственно на покупку акций в количестве соответствующему StartQ.
Блок №6. Расчет Глобальных переменных.
var lastPriceSorted = new SortedList<double, double>(LastPrice);
Dohod = DohodZakr + RealizedPLAfterCommission ();
DohodAkc = Dohod/Input1.Close[0];
DohodLot = DohodAkc/LotSize();
//
Action SaveGlobalVars = () => {
LastPrice = new Dictionary<double, double>(lastPriceSorted);
LastPriceCount = LastPrice.Count;
xPosition = pos;
Dohod = Dohod;
DohodAkc = DohodAkc;
DohodLot = DohodLot;
};
//
Delta = (LastPriceCount - (StartQ/Q));
DeltaPercentBuyNew = DeltaPercentBuy + kBuy*Delta;
DeltaPercentSellNew = DeltaPercentSell + kSell*Delta;
В этом блоке рассчитываются вспомогательные глобальные переменные и происходит их обновление. Обратите внимание на Delta, она рассчитывается как количество уровней в настоящий момент минус количество уровней созданной при покупке StartQ. Логика следующая, StartQ это количество акций которое мы изначально подразумеваем как инвестиционную часть для получения дивидендов, а вот все что будем покупать и продавать выше этого количества у нас используется для повышения доходности наших инвестиций. Но это конечно не означает, что акции купленные как StartQ мы не будем продавать никогда. Просто по мере покупки дополнительных уровней, мы будем увеличивать расстояние между уровнями, тем самым экономнее расходовать КЭШ. Ну а что бы не терять в доходности увеличивая процент для покупки уровня, мы должны одновременно, на тот же процент увеличивать и процент для продажи уровня.
Блок №7. обработка уровней StartQ.
if(pos >= StartQ && !inited)
{
inited = true;
if(LongLimitStart != 0)
SetLongLimit(LongLimitStart);
var signals = GetSignalInfo(SignalType.Open, (int)pos);
var initSum = signals.Where(s => s.ActionSuperType == AvailableActions.Initialization).Sum(s => s.OperationExecuted * Math.Max(AverPrice(), Input.Close[0]));
var boughtSum = signals.Where(s => s.ActionSuperType != AvailableActions.Initialization).Sum(s => s.OperationExecuted * s.PriceOfTrade);
var avpStart = (initSum + boughtSum) / (pos);
var avp = avpStart;
double b = xPosition;
while (pos - b > 0)
{
lastPriceSorted.Add(avp, Math.Min(Q, pos - b));
avp += avpStart * 0.001* DeltaPercentBuy;
b = b + Q;
}
xPosition = pos;
}
if(!inited)
{
CancelActiveOrders(true);
SaveGlobalVars();
return;
}
if(activeOrder != null && pos == xPosition && activeOrder.Quantity - activeOrder.QuantityRest != lastSignalProcessed)
{
return;//ждём сделки
}
После покупки StartQ, Робот создаст количество уровней вверх от цены покупки с шагом DeltaPercentBuy, и будет создано количество уровней равное StartQ/Q.
Продолжение в следующей статье.
Приложение.
Сразу опубликую текст используемого в Роботе индикатора. Напоминаю, индикатор не относится непосредственно к Роботу, и устанавливается отдельно, как установить индикатор, я подробно описывал ранее.
function Initialize()
{
IndicatorName = "str_Invest_Grid_5";
PriceStudy = true;
AddInput("Input", Inputs.Candle);
AddSeries("str_Invest_Grid_5", DrawAs.Line, Color.Red);
// Дополнительные параметры:
AddParameter("Price", 20);
AddParameter("PL", 1);
AddParameter("Delta", 1);
AddParameter("PL1", 1);
AddParameter("Delta1", 1);
AddParameter("Type", 0);
// Серии для отображения цен
AddSeries("L1", DrawAs.Custom, Color.LightGreen, true);
AddSeries("L2", DrawAs.Custom, Color.Red, true);
AddSeries("L3", DrawAs.Custom, Color.Red, true);
AddSeries("L4", DrawAs.Custom, Color.Green, true);
AddSeries("L5", DrawAs.Custom, Color.Green, true);
// Уровни
AddLevel(0, Color.LightGreen, LineStyles.DashBig, 2, "str_Invest_Grid_5");
AddLevel(0, Color.Red, "str_Invest_Grid_5");
AddLevel(0, Color.Red, LineStyles.Dot, 2, "str_Invest_Grid_5");
AddLevel(0, Color.Green, LineStyles.Dot, 2, "str_Invest_Grid_5");
AddLevel(0, Color.Green, "str_Invest_Grid_5");
}
function Evaluate()
{
if (CurrentIndex == MaxIndex)
{
// Alfadirect.
// Индикатор для отображения в стартегии Invest_Grid
// Если Type == 0, рисуем отклонение в абсолюте
if (Type == 0)
{
Levels[0].Level = Price;
Levels[1].Level = Price - PL;
Levels[2].Level = Price - PL1;
Levels[3].Level = Price + Delta1;
Levels[4].Level = Price + Delta;
}
// Если Type == 1 рисуем отклонение в %
if (Type == 1)
{
Levels[0].Level = Price;
Levels[1].Level = Price*(1-0.001*PL);
Levels[2].Level = Price*(1-0.001*PL1);
Levels[3].Level = Price*(1+0.001*Delta1);
Levels[4].Level = Price*(1+0.001*Delta);
L1 = Levels[0].Level;
L2 = Levels[1].Level;
L3 = Levels[2].Level;
L4 = Levels[3].Level;
L5 = Levels[4].Level;
Levels[0].Color = L1.Color;
Levels[1].Color = L2.Color;
Levels[2].Color = L3.Color;
Levels[3].Color = L4.Color;
Levels[4].Color = L5.Color;
}
}
}
Поясню, почему я пришел к использованию именно этого алгоритма выставления заявок, и использования данного варианта индикатора для визуализации.
В Роботе который выставлен в терминале Альфа-Инвестиции Заявка на покупку или продажу выставляется когда Свеча закрывается выше уровня выставления заявки которую рассчитывают как цена покупки умноженную на заданный нами процент роста или падения при которых мы хотим покупать или продать уровень.
На скрин представлены возможные варианты положения свечи относительно уровней продажи-покупки. Если это уровень на котором мы должны совершить продажу, то на свечах 1 и 2 заявки не будут выставлены, хотя цена поднималась выше нужного нам уровня, заявка выставиться только на свечах 3 и 4.
А если это уровень покупки, то на свечах 1 и 2 заявка будет выставлена, а на свечах 3 и 4 не будет, хотя цена опускалась ниже и уровня на котором мы рассчитывали докупить актив.
После введения на бирже системы маркет-мейкеров, заявка часто выставлялась на уровне "плиты" установленной маркет- мейкерами. Заявка попадала в конец большого списка и часто не исполнялась, так как до нашей заявки очередь не доходила и цена изменялась не в нужную нам сторону.
Первым вариантом модернизации который я применил, это замена выставления заявок с закрытия свечи, на значение High (максимум свечи) для продажи, и Low (минимум свечи) для покупки. Ситуация улучшилась, заявки стали выставляться раньше и реже попадать на "плиты" маркет-мейкеров. Но число не сработавших заявок, и в последствие снятых, оставалось довольно высоким.
Я так же обратил внимание на случае, когда после продажи уровня, акция некоторое время продолжала рост и иного довольно приличный. Что бы использовать этот рост, я модернизировал Робота и заменил лимитную заявку на продажу на продажу уровня, посредством Trailing Stop это стоп заявка которая движется вслед за ценой если актив растет, и срабатывает только если актив начнет падать. И реально, некоторые сделки начали продаваться в 1,5-2 раза выгоднее, чем если использовать лимитные заявки. Кажется вот он наш Грааль, но если приглядеться становится грустно. Дело в том, что в Trailing Stop нам необходимо установить процент, на который должна упасть цена, что бы сработал стоп. Для минутных таймфреймов на которых работают мои Роботы, этот процент приходится устанавливать равным 0,2% - 0,3% (в зависимости от волатильности инструмента), а это между прочим равно 3-5 комиссиям которые платим брокеру за сделку. Вот и выходит, что выигрыш от того, что мы ставим Trailing Stop и цена растет выше уровня продаж, мы получаем иногда, а теряем 0,2% - 0,3% на каждой сделке, причем теряем много, до 5 комиссий брокера, при этом переход на использование вместо лимитных заявок Trailing Stop, мы затеяли ради экономии комиссий брокера пытаясь уменьшить количество сделок. В общем Грааль мы снова не нашли.
Проанализировав все эти варианты, я пришел к выводу, что использование различных экзотических конструкций ради мнимой прибыли, не целесообразно. В инвестициях важнее предсказуемость, и так на рынке достаточно неопределенности. Именно по этому я остановился на приведенном варианте с 5 уровнями на графике и в коде Робота. Идея заложена следующая, для того, что бы не попасть на плиту маркет - мейкер и точно знать по какой цене будет куплен и по какой цене продан наш актив, заявку нужно выставлять заранее в зависимости от того, куда движется в настоящий момент цена, если направление движения цены изменится, мы просто переставим заявку. В итоге я добавил в Робота два дополнительных уровня. Уровень выставления заявки на продажу, и уровень выставления заявки на покупку. В итоге получилась следующая конструкция.
На скриншоте видно, что заявка на покупку выставлена, так как цена акции была выше уровня выставления заявки на продажу, и заявка на продажу уровня выставлена четко по уровню продажи. В итоге мы получаем полностью предсказуемые действия Робота и беглого взгляда на график достаточно, для понимания в каком состоянии находится Робот в настоящий момент.
PS.
В данной статье мы разложили примерно половину модернизированного Робота на составляющие блоки. Просто скопировав блоки и сложив по порядку в любом текстовом редакторе, мы получим полноценного Робота. каждый блок можно модернизировать отдельно или использовать в разработке собственных Роботов. Вторая половина блоков с комментариями будет выложена в самое ближайшее время, дачный сезон и круиз уже заканчивается. Так же готовится статья, как моя Ферма Роботов пережила падение рынка и какие идеи модернизации родились исходя из поведения рынка.
Напишите в комментариях нужно ли выложить отдельно полный собранный текст скрипта Робота.
Удачных вам всем инвестиций.