Код "ручных" StopLoss/TakeProfit (далее SL/TP) для использования в стратегиях. (Ничего "революционного", м.б. кому-то пригодится.)
Цель - пользовательский расчет уровней и сигналов по ним для последующей реакции не через стоповые заявки, а через "обычные" операции. Это позволяет на длинных таймфреймах
- избегать закрытия позиций по мгновенным колебаниям цен;
- организовать "боковой коридор" удержания позиции.
Также делается расчет уровня переоткрытия позиции после выхода по SL/TP, по которому можно вернуться в позицию, если "рабочий" индикатор в ближайшем будущем будет показывать прежнее направление движения.
В параметрах задаемся целевым процентом прибыли и допустимым уровнем потерь. В пределах этого коридора движение цен игнорируется (боковик), при пробое выдается соответствующий сигнал.
Еще один параметр (возможно, озаглавленный не очень корректно) - процент слежения за TP после достижения первоначальной цели по TP (лучшая цена минус этот процент).
Пороговые значения, естественно, следует оптимизировать. При оптимизации следует выбирать длительные участки с "нормальным" ходом торгов без слишком резких скачков, иначе пороги м.б. излишне завышены. Например, при оптимизации за весь 2018 год пороги SL/TP в моем случае получились аж 6.5/9,5% за счет больших движений в апреле, оптимизация за период с 1 мая дала более консервативные 2,5/8%.
Финальный результат оптимизации по сравнению с вариантом без SL/TP дает 5-10% дополнительной прибыли, т.е. не очень много, но тоже на дороге не валяется.
Код выделен в функции ResetSLTP и CalcSLTP для облегчения использования и перетаскивания в другие роботы.
Код: Выделить всё
function Initialize()
{
StrategyName = "MyRobot";
AddInput("Input1", Inputs.Candle, 30, true, "SBER=МБ ЦК"); // Т-Ф 30 мин, торгуемый
//-----------------------------------------
// Параметры и переменные SL/TP
AddParameter("StpLs", 2, "Допустимые потери, %");
AddParameter("TkPft", 2, "Цель прибыли, %");
AddParameter("TkPftTrack", 0.5, "Точность цели прибыли, %");
AddParameter("LogLevel", 0, "Уровень лога");
AddGlobalVariable("TP", Types.Double, -1.0); // еще не рассчитывалось
AddGlobalVariable("SL", Types.Double, -1.0); // еще не рассчитывалось
AddGlobalVariable("TPinProgress", Types.Boolean, false); // цены были лучше TP
AddGlobalVariable("TPpeak", Types.Double, -1.0); // пик цен лучше TP/уровень переоткрытия позиции
//-----------------------------------------
}
function OnUpdate()
{
//-----------------------------------------
Action ResetSLTP = () =>
{
SL=TP=-1.0; TPinProgress=false;
};
// выходной сигнал: '0'-удержание/нет позиции, '-1'-на выход по SL, '+1'-на выход по TP
// позиция учетная цена текущая цена
Func<double, double, double, int> CalcSLTP = (double AccPos, double AccPrc, double CurPrc) =>
{
if(AccPrc==0) { ResetSLTP(); return 0; } // нет позиции
if(TP<0.0)
{ // только что вошли в позицию, расчет уровней
double s=AccPrc*(StpLs/100.0);
double t=AccPrc*(TkPft/100.0);
if(AccPos>0) { SL=AccPrc-s; TP=AccPrc+t; TPinProgress=(CurPrc>=TP); }
else { SL=AccPrc+s; TP=AccPrc-t; TPinProgress=(CurPrc<=TP); }
}
if(AccPos>0)
{ // длинная позиция
if(CurPrc<SL)
{ // сигнал на закрытие по SL
ResetSLTP();
TPpeak=AccPrc; // для переоткрытия ждать цены, равной нынешней учетной
return -1;
}
if(CurPrc<AccPrc) { TPinProgress=false; return 0; } // цена ниже учетной, но пока держим
// цена выше учетной
if(TPinProgress)
{ // ранее цена превышала TP
TPpeak=Math.Max(TPpeak, CurPrc); // пиковая цена над TP
if(CurPrc<TP)
{ // сигнал на закрытие по TP
TPpeak=(TPpeak+TP)/2.0; // уровень переоткрытия позиции
TPinProgress=false;
return 1;
}
TP=Math.Max(TP, CurPrc*(1.0-TkPftTrack/100.0)); // удержание, новый уровень TP
return 0;
}
else { TPinProgress=(CurPrc>=TP); return 0; } // ранее цена не превышала TP; удержание
}
else // AccPos<0
{ // короткая позиция
if(CurPrc>SL)
{ // сигнал на закрытие по SL
ResetSLTP();
TPpeak=AccPrc; // для переоткрытия ждать цены, равной нынешней учетной
return -1;
}
if(CurPrc>AccPrc) { TPinProgress=false; return 0; } // цена выше учетной, но пока держим
// цена ниже учетной
if(TPinProgress)
{ // ранее цена была ниже TP
TPpeak=(TPpeak>0.0 ? Math.Min(TPpeak, CurPrc) : CurPrc); // пиковая цена под TP
if(CurPrc>TP) // сигнал на закрытие по TP
{
TPpeak=(TPpeak+TP)/2.0; // уровень переоткрытия позиции
TPinProgress=false;
return 1;
}
TP=Math.Min(TP, CurPrc*(1.0+TkPftTrack/100.0)); // удержание, новый уровень TP
return 0;
}
else { TPinProgress=(CurPrc<=TP); return 0; } // удержание
} // AccPos<0
};
//-----------------------------------------
// код робота - пример использования
double cpr=AverPrice(); // текущая уч. цена позиции
int cp=(int)CurrentPosition(); // текущая позиция
double P=(Input1.High[0]+Input1.Low[0])/2; // последняя цена (желательно как-то сглаженная)
if(cp!=LastPosition) // LastPosition-позиция на предыдущем шаге
{ // позиция изменилась
ResetSLTP(); // надо начать слежение заново
LastPosition=cp;
LastPrice=cpr;
}
// SLTP
if(cp!=0)
{
int SigSLTP=CalcSLTP(cp, cpr, P);
if(LogLevel>0)
{
WriteLine(LogFile, String.Format(@"{0:dd/MM/yy} {1} TPSLsig:{2} Pos:{3} AccPrc:{4:F2} Prc:{5:F2} TP:{6:F2} SL:{7:F2} Peak:{8:F2} %:{9:F2} Prorgess:{10}", BarDate(), BarTime(), SigSLTP, cp, cpr, P, TP, SL, TPpeak, CurrentPLper(), TPinProgress));
}
if(SigSLTP!=0)
{
ReopenInProgress=(cp<0 ? -1 : 1); // флаг на дальнейшее переоткрытие (код не показан)
ClosePosition();
if(LogLevel>0)
WriteLine(LogFile, String.Format("Closed by {0}; Reop:{1} WaitFor:{2:F2}", (SigSLTP<0 ? "SL":"TP"), ReopenInProgress, TPpeak));
goto Finish; // грубо против немедленного переоткрытия на этом же шаге робота; можно и без этого, если правильно реализовать переоткрытие ;)
} // SigSLTP
} // SLTP
// "основной код" робота
.....
Finish: ;
// что-то еще
.....
}
Если есть замечания, прошу сообщить (ради того и опубликовано; в процессе написания поста нашел у себя неточность - уже польза).
В плане написание индикатора для отрисовки процесса и использования в роботах для получения сигналов, что даст возможность не включать код в робот, а использовать готовый внешний индикатор, но сейчас пока нет возможности этим заниматься.