Найти тему

Полный текст Робота собранный на основе доработанных и опубликованных блоков.

Оглавление

Здравствуйте уважаемые Подписчики и читатель. Как я и обещал,, что бы вам самим не собирать Робота из опубликованных блоков, привожу полный текст.

Текст Робота проверен, компилируется успешно, значит ошибок в нем не должно быть по определение. Но напоминаю вам, что скопировав текст Робота вы должны сами обязательно нажать кнопку "Компилировать". Кто из вас еще не установил 5 версию индикатора, обязательно установите для визуализации процесса работы Робота. Так же обратите внимание, что в "Библиотеке Стратегий" название робота должно до буквы совпадать с названием в тексте Робота. Если вы пожелаите изменить для себя название Робота, то вы должны поменять имя Робота в тексте стратегии в Блоке №1.

Так же имейте в виду, что Робот на основе стратегии |StepByStep, это прежде всего инвестиционный, а не спекулятивный и по большому счету является доходной альтернативой долгосрочного инвестирования, так как Робот позволяет получать по акциям и дивиденды и доход от роста котировок.

Полный текс Робота на основе модернизированных блоков.

function Initialize()
{
StrategyName = "SBS_v62";
AddInput("Input1", Inputs.Candle, 5, true, "CHMF=МБ ЦК");
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);
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);
AddChartIndicator("MY.str_Invest_Grid_5", new Dictionary <string, string>{{"Price", "MinPrice"},{"PL", "DeltaPercentBuyNew"},{"PL1", "DeltaPercentBuy1"},{"Delta", "DeltaPercentSell"},{"Delta1", "DeltaPercentSell1"}, {"Type", "Type"}});
}
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;
}
}
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;
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;//ждём сделки
}
//корректировка уровней
if(pos > xPosition)//позиция увеличилась
{
while (lastPriceSorted.Count > 0 && lastPriceSorted.First().Value <= 0)
lastPriceSorted.Remove(lastPriceSorted.First().Key);

int Qty2process = (int)(pos - xPosition);
double Lvl = GetSignalInfo(SignalType.Open, Qty2process)[0].PriceOfTrade;

var signal_1 = GetLastSignalInfo();
if (signal_1 != null && signal_1.ActionType == AvailableActions.ChangePosition) lastSignalProcessed = 0;
while (pos > xPosition) {
if(lastPriceSorted.ContainsKey(Lvl)) {
if (Q <= (int)lastPriceSorted[Lvl]) Qty2process = 0; // есть уровень
else Qty2process = (int)Math.Min(Q - (int)lastPriceSorted[Lvl], pos - xPosition);
lastPriceSorted[Lvl] += Qty2process;
} else { // нет уровня
Qty2process = (int)Math.Min(Q, pos - xPosition);
lastPriceSorted.Add(Lvl, Qty2process);
}
Lvl *= (1.0 + 0.01*DeltaPercentBuyNew);
lastSignalProcessed += Qty2process;
xPosition += Qty2process;
}
}
else if(pos < xPosition && lastPriceSorted.Count >= 1)
{
var b = xPosition;
while(b > pos)
{
if(lastPriceSorted.Count == 0) break;

var firstKey = lastPriceSorted.First().Key;
if(b - pos<lastPriceSorted[firstKey])
{
lastPriceSorted[firstKey] -= (b - pos);//Исполнили часть уровня
b -= (b - pos);
}
else
{
b -= lastPriceSorted[firstKey];//Исполнили весь уровень
lastPriceSorted.Remove(firstKey);
}
}
}

xPosition = pos;//позиция учтена выше

if (LastMinPrice != 0 && lastPriceSorted.Count <= 0)
{
MinPrice = LastMinPrice;
}
else
{
if(lastPriceSorted.Count <= 0)
{
MinPrice = StartPrice;
}
else
{
MinPrice = lastPriceSorted.First().Key;
LastMinPrice = MinPrice;//Запоминаем текущий MinPrice, что бы запомнить при распродаже в 0
}
}

//Правило 1.5. Если цена упала, восстанавливаем до Q нижний уровень
if (lastPriceSorted.Count > 0 &&
Input1.Low[0] <= lastPriceSorted.First().Key &&
lastPriceSorted.First().Value < Q &&
LongLimit > pos &&
Q > 0 )
{
if(activeOrder != null) {
if(activeOrder.OrderDirection != OrderDirection.Buy) CancelActiveOrders(true);
return; // ждём исполнения уже выставленной заявки
}
double pBuy = lastPriceSorted.First().Key;
int qBuy = (int)Math.Min(LongLimit - pos, Q - lastPriceSorted.First().Value);

EnterLongLimit(pBuy, qBuy);
var lastSignal = GetLastSignalInfo();
if(lastSignal != null) {
lastSignalId = lastSignal.SignalID;
} }

//Правило 2. Если цена упала и кол-во меньше допустимого,
// то покупаем и добавляем цену покупки в начало списка
if (Input1.Low[0] <= MinPrice* (1.0 - 0.001 * DeltaPercentBuy1) && pos + Q <= LongLimit || (xPosition == 0 && inited))
{
if(activeOrder != null && !activeOrder.IsStatusTerminal)
{
if(activeOrder.OrderDirection != OrderDirection.Buy) CancelActiveOrders(true);
SaveGlobalVars();
return;//ждём исполнения уже выставленной заявки
}

// если все распродал покупаем Q
if (xPosition == 0 && inited)
{
EnterLong(Q);
}
else
{
Delta = (LastPriceCount - (StartQ/Q));
DeltaPercentBuyNew = DeltaPercentBuy + kBuy*Delta;
SaveGlobalVars();
EnterLongLimit(Math.Min(MinPrice* (1.0 - 0.001 * DeltaPercentBuyNew ), Input1.Close[0]*(1.0 + 0.01 * OrderSlippage)), Q);
}

var lastSignal = GetLastSignalInfo();
if(lastSignal != null)
{
lastSignalId = lastSignal.SignalID;
lastSignalProcessed = 0;
}
}

//Правило 3. Если цена выше цены из начала списка, то продаем и удаляем 0-й элемент списка
if (Input1.High[0] >= MinPrice* (1.0 + 0.001 * DeltaPercentSell1) && pos > 0 )
{
if(activeOrder != null && !activeOrder.IsStatusTerminal)
{
if(activeOrder.OrderDirection != OrderDirection.Sell) CancelActiveOrders(true);
SaveGlobalVars();
return;//ждём исполнения уже выставленной заявки
}
var vSell = (int)lastPriceSorted.First().Value;
Delta = (LastPriceCount - (StartQ/Q));
DeltaPercentSellNew = DeltaPercentSell + kSell*Delta;
SaveGlobalVars();
CloseLongLimit(MinPrice* (1.0 + 0.001 * DeltaPercentSellNew), vSell);
var lastSignal = GetLastSignalInfo();
if(lastSignal != null)
{
lastSignalId = lastSignal.SignalID;
lastSignalProcessed = 0;
}
}
SaveGlobalVars();
}

Еще раз о Блеке № 9.

Давайте немного поговорим, о том как происходят модернизации и доработки Роботов. Я в предыдущей статье коротко рассказал, почему был добавлен блок №9 и каковы его перспективы, теперь остановимся на нем немного подробнее. Вот упрощенный график изменения котировок актива, которым торгует наш Робот.

№1.
№1.

Когда авторы рассказывают о принципе работы стратегии StepByStep это звучит обычно следующим образом. "Если цена от купленного нами уровня падает на заданный в параметрах процент, Робот покупает новый уровень, Если цена растет на заданный процент Робот уровень продает".

В принципе все верно, но я часто говорю, для успешной работы нужно научиться думать как Робот. Вот давайте посмотрим, а как у нас Робот узнает, цена актива падает или растет? Оказывается очень просто Робот сравнивает цену последнего купленного им уровня с текущей ценой актива и если цена актива сейчас меньше цены купленного уровня, значит для Робота цена падает. Теперь смотрим на скриншот №1., Синяя горизонтальная линия, это уровень который Робот купил последним. Зеленая горизонтальная, это уровень на котором Робот "должен купить" новый уровень. Красная ломанная линия это изменение котировок актива с течением времени.

Теперь внимание.

Получается, что все время пока цены ниже точек №1 и №2 Робот считает, что актив падает и ему нужно покупать новый уровень. Хотя цена актива от точки №1 до точки №3 реально падала с небольшими коррекциями., а от точки №3 до точки №2 реально росла с небольшими коррекциями. Причем из графика видно, что цены актива в точке №1 и точке №2 совершенно одинаковые, а значит и затраты на покупку уровня в этих точках и комиссии брокера будут одинаковые. Вот только время между точками может исчисляться в часах, днях, неделях или месяцах. А значит если мы купим уровень в точке №2 все время от точки №1 до точки №2 наш КЭШ будет свободен для других Роботов.

Кажется понятно, что покупать уровень нужно именно в точке №2. Но как всегда есть обратная сторона. Покупка уровней на падении актива позволяет нам более точно найти "дно", точку №3. И возможно отработать коррекции на падении. Получается совсем отказываться от покупок уровней на падении не целесообразно, ведь мы заранее не знаем где это пресловутое "Дно" рынка. Было бы хорошо, если бы мы при падении котировок покупали уровни малыми лотами, даже просто по одному лоту, а вот на росте актива докупали акции на уровнях до заданного нами для данного актива значения Q.

В принципе при запуске или перезапуске Робота после распродажи всех уровней, мы можем купить StartQ и расставить уровни хоть по одному лоту. Или в процессе работы мы решили увеличить значение Q, что бы увеличить доходность Робота. Тогда на ранее купленных уровнях у нас число акций окажется меньше текущего Q, и Робот нам дополнит уровень, докупив недостающие до текущего значения Q акции, так мы более экономно будем расходовать наш КЭШ.

Мозговой штурм.

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

Сразу поясню подводные камни, все скользящие имеют инерционность, и мы точно пропустим "Дно рынка". Нужно как то отсеять незначительные корректировки, это сглаживание тоже увеличивает инерционность скользящих.

Свой вариант, я специально не озвучиваю, он еще довольно сырой, что бы не сбивать вас с мыслей.

Ну и напишите, интересно ли вам идея "мозгового штурма".

Удачных всем подписчикам и просто заглянувшим почитать инвестиций.