Блог им. gotps

Трендовая стратегия на фРТС

Рабочая внутридневная трендовая стратегия, с кодом автоматизации теста для Multicharts и кодом для «боевой» торговли через MetaTrader 5. Все описанное разработанно и проверено на фьючерсе на индекс РТС (ФОРТС). Я не претендую ни на какие звания и не принимаю никаких претензий или критики по поводу использования данного материала.

Небольшое лирическое отступление.

Почему пишу публично, если «такая корова самому нужна»? На то есть ряд причин:

  1. несмотря на автоматизацию, процесс требует постоянного контроля, учета. Это работа. У меня на это нет ни лишнего времени, ни желания. Кроме того это постоянный стресс от убытков и страхов, что “все пропало, все сломалось, больше не заработать на торговле”.
  2. для зарабатывания существенных сумм, чтобы затраченное время себя окупало, нужен существенный капитал на депозите. Плечами в данной стратегии лучше не злоупотреблять, поскольку во времена всплесков волатильности могут быть резко изменены параметры ГО и денег на счете может не хватить для входа в позицию в самый не подходящий момент. Начинать торговать нужно хотя бы от трехсот тысяч рублей, лучше иметь семьсот-миллион, а для какого-то прокорма только с торговли нужно несколько миллионов рублей.
  3. за реализацию программы-робота и тестов мне должны были заплатить, но человек съехал с темы и я бросил эту затею. Однако свой труд хоронить мне не хочется. На разработку, тестирование и проторговку на реальном счете было потрачено порядка года личного времени.

Помимо вышесказанного, я никому ничего не продаю, не консультирую, не управляю никакими капиталами и не являюсь публичной персоной, так что никакого подвоха. Мой личный интерес лежит в области реального сектора, где я заработал денег много больше, чем на этих ваших биржах.

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

Тестирование лучше проводить в Multicharts, так как в MetaTrader 5 есть особенности исполнения по ценам bid/ask/lastprice, что иногда искажает реальную картину (если бы велась торговля по биржевым ценам).
Торговля на MT5 велась через брокера «Открытие», скорость исполнения приказов более чем удовлетворительная. Это не реклама и не отсылка, а только голый факт.
Программист из меня так себе, код программ не слишком красивый и не оптимизирован, зато работает.

 

Общий алгоритм работы.

  1. Величина параметра стоп-лосс (StopLoss) составляет 500 пунктов.
  2. Начало торгов (StartTime) определяется по открытию лондонской биржи, в зависимости от разницы во времени либо в 11:00:00, либо в 12:00:00. Это критически важный параметр, от него очень сильно. Окончание (CloseTime) в 23:45:00, время (FinishTime), после которого ордера не выставляются, задается равным 23:00:00;
  3. Объем позиции (LotSize) определяется исходя из величины текущего ГО и составляет от трех до девяти контрактов на каждые сто тысяч рублей депозита. Девять контрактов на сто тысяч можно нагружать только в случае предоставления услуги пониженного ГО;
  4. Вход в позицию всегда осуществляеся всем объемом;
  5. В начале торгов StartTime сохраняется цена открытия инструмента (TargetPrice). К этой цене прибавляется StopLoss и получается цена входа для покупок (TargetLong), от этой цены отнимается StopLoss и получается цена продажи (TargetShort). Стоп-приказы для позиций будут установлены на уровне TargetPrice. Все приказы выставляются сразу же после формирования минутного бара открытия, в StartTime + 1 минута.
  6. Выход из позиции всегда осуществляется либо полностью по StopLoss, либо тремя равными частями: первая часть после достижения ценой отметки входа плюс StopLoss (таким образом риски снижаются до 1/3 от объема входа), вторая часть подтягивается трейлинг-стопом на уровне текущая цена минус StopLoss умноженный на 3; третья часть закрывается в конце рабочей сессии CloseTime.


Существенные замечания.

Параметр стоп-лосс подобран империческим путем. Для каждого инструмента и в зависимости от текущей волатильности он должен быть скорректирован. В действительности, для фьючерса на индекс РТС этот параметр остается неизменным, а вот для фьючерса на валютную пару доллар-рубль он изменился из-за сильного изменения стоимости контракта.

Бэк-тесты показали сильную зависимость доходность системы сильно влияет время открытия LSE. Не нужно пытаться это объяснить, просто принять как факт.

Как избежать «распила». В основе всей идеи лежит факт того, что рыночные процессы цикличны. Периоды застоя сменяют тренды и наоборот. Для того, чтобы «не лезть в рынок» в неподходящее время, нужно дождатся расчетного (виртуального) убытка на счете.
Статистически верхняя граница убыточной зоны приблизительно составляет десять тысяч пунктов, нижняя составляет двадцать пять тысяч пунктов на каждый задействованный контракт. Для того, чтобы высчитать просадку, нужно суммировать все полученные прибыли и убытки на базовом количестве контрактов (три).
На развитых рынках с хорошей ликвидностью трендовые периоды очень тяжело выявлять, там нет таких движений цены внутри дня, как на наших Ri и Si, так что там прийдется менять параметры и глубоко тестировать.

Пример.
В первый день торгов произошел вход в позицию, первая часть была закрыта 1 х 500 пунктов, вторая часть (трейлинг стоп) была закрыта по 1 х 800, и третья часть была закрыта 1 х 1500. Итого: 500 + 800 + 1500 = 2800 пунктов. Во второй день произошел вход в позицию и после этого вся позиция была закрыта по стоп-лоссу 3 х (500) пунктов. После этого произошел еще один вход в позицию, первая часть закрылась по 1 х 500 пунктов, оставшиеся две трети были закрыты по стоп-лоссу 2 х (500) пунктов. Итого: (1500) + 500 + (1000) = (2000) пунктов.
После этого сальдируем накопленные прибыли и убытки и получаем: 2800 + (2000) = 800 пунктов. Это и есть необходимое расчетное текущее значение просадки.

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


Исходный код PowerLanguage для тестирования на исторических данных:

[IntrabarOrderGeneration = True]

inputs: StopLoss(500), LotSize(3), StartTime(1100), FinishTime(2300), CloseTime(2345);
vars: TargetPrice(0), TargetLong(0), TargetShort(0), TargetLongTake(0), TargetShortTake(0), trailing(0);

setstopcontract;
SetStopLoss(StopLoss);

if time = StartTime then
begin
TargetPrice = Open;

TargetLong = TargetPrice + StopLoss;
TargetShort = TargetPrice — StopLoss;


TargetLongTake = TargetLong + StopLoss;
TargetShortTake = TargetShort — StopLoss;
end;


if marketposition = 0 and time >= StartTime and time <= FinishTime Then
begin
trailing = TargetPrice;
buy («TargetLong») LotSize contracts next bar at TargetLong stop;
sell short («TargetShort») LotSize contracts next bar at TargetShort stop;
end;

if marketposition > 0 and time >= StartTime and time <= FinishTime Then
begin
sell («PartialCoverLong») (LotSize / 3) contracts next bar at TargetLongTake limit;

if trailing < High — StopLoss * 3 then
begin
trailing = High — StopLoss * 3;
text_new(D, Time, High, text(trailing));
end;
sell («TrailingStopLong») (LotSize / 3) contracts next bar at trailing stop;
end;

if marketposition < 0 and time >= StartTime and time <= FinishTime Then
begin
buy to cover («PartialCoverShort») (LotSize / 3) contracts next bar at TargetShortTake limit;
if trailing > Low + StopLoss * 3 then
begin
trailing = Low + StopLoss * 3;
text_new(D, Time, Low, text(trailing));
end;
buy to cover («TrailingStopShort») (LotSize / 3) contracts next bar at trailing stop;
end;

if time = CloseTime then
begin
if marketposition < 0 then
begin
buytocover («EOD short») next bar market;
end;
if marketposition > 0 then
begin
sell («EOD long») next bar market;
end;
end;


Исходный код MQL5 для MetaTrader 5:

#include <Trade/Trade.mqh>
#include <Trade/SymbolInfo.mqh>

#property version «5.0»

//--- input variables
input double StopLoss = 500; // Initial stop-loss
input double lotSize = 3; // Initial lot size
input double lotSizeStep = 3; // Initial lot size increment
input double TargetPriceOverride = 0; // Override TargetPrice

input int startHour = 11; // Start time
input int endHour = 22; // Finish time
input int EODH = 23; // End Of Day Hour
input int EODM = 40; // End Of Day Minute

// Debugging
input double const InitialBalance = 500000;
input bool const DEBUG_ENABLE = false;

//--- variables
MqlDateTime CurrentTime;
MqlTick Tick;
CTrade Trade;
CPositionInfo Position;
CSymbolInfo Sym;
COrderInfo Order;

double TargetPrice, TargetLong, TargetShort, Lots, Balance = 100, MaxBalance = 0, Xbalance = 0, Xmin = 100, Xinit = 0, Y = 0, TrailingStop = 0;
ulong TrailingTicket = -1;
string const TrailingComment = «trailing», InfoLabelName = «InfoLabel»;

void OnTick() {
SymbolInfoTick(Symbol(), Tick);
TimeCurrent(CurrentTime);

if(CurrentTime.hour >= startHour && CurrentTime.hour < endHour) {
CalculatePrice();
if(OrdersTotal() < 4 && PositionsTotal() < 1) {
if((Tick.last < TargetLong — StopLoss / 1.5) && (Tick.last > TargetShort + StopLoss / 1.5)) {
DEBUG(«CurrentTime to refill orders!»);
CleanUpOrders();
SetupOrders();
}
}
}

if(CurrentTime.hour == endHour) {
// possible caveat
if(OrdersTotal() > 1) {
DEBUG(«CurrentTime to wipe out orders»);
CleanUpOrders();
}
}

if(CurrentTime.hour == EODH && (CurrentTime.min == EODM || CurrentTime.min == EODM + 1 || CurrentTime.min == EODM + 2 || CurrentTime.min == EODM + 3 || CurrentTime.min == EODM + 4)) {
if(PositionsTotal() > 0) {
DEBUG(«CurrentTime to close up positions»);
CleanUpOrders(false);
CleanUpPositions();
}
}

// trailing stop
for(int i = 0; i < PositionsTotal(); i++) {
if(Symbol() == PositionGetSymbol(i)) {
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
if (Tick.last — StopLoss * 2 > TrailingStop) {
TrailingStop = Tick.last — StopLoss * 2;

if (TrailingTicket == -1) {
DEBUG(«setting up trailing SellStop = » + (string)TrailingStop);

bool tres = Trade.SellStop(volume() / 3, TrailingStop, _Symbol, 0, 0, ORDER_TIME_DAY, 0, TrailingComment);
if (tres) {
TrailingTicket = Trade.ResultOrder();
DEBUG(«trailing set up, ticket = » + (string)TrailingTicket);
}
} else {
if (Order.Select(TrailingTicket)) {
DEBUG(«moving up trailing SellStop = » + (string)TrailingStop);
Trade.OrderModify(TrailingTicket, TrailingStop, 0, 0, ORDER_TIME_DAY, 0, 0);
}
}
}
}

if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) {
if (Tick.last + StopLoss * 2 < TrailingStop) {
TrailingStop = Tick.last + StopLoss * 2;

if (TrailingTicket == -1) {
DEBUG(«setting up trailing BuyStop = » + (string)TrailingStop);

bool tres = Trade.BuyStop(volume() / 3, TrailingStop, _Symbol, 0, 0, ORDER_TIME_DAY, 0, TrailingComment);
if (tres) {
TrailingTicket = Trade.ResultOrder();
DEBUG(«trailing set up, ticket = » + (string)TrailingTicket);
}
} else {
if (Order.Select(TrailingTicket)) {
DEBUG(«moving down trailing BuyStop = » + (string)TrailingStop);
Trade.OrderModify(TrailingTicket, TrailingStop, 0, 0, ORDER_TIME_DAY, 0, 0);
} else {
DEBUG(«unable to select TrailingTicket: » + (string)TrailingTicket);
}
}
}
}
}
}

DisplayStats();
}

void CalculatePrice() {
double price[];
MqlDateTime OpenTime;

TimeCurrent(OpenTime);
OpenTime.hour = startHour;
OpenTime.min = 00;
OpenTime.sec = 00;

CopyOpen(_Symbol, PERIOD_CURRENT, StructToTime(OpenTime), 1, price);

TargetPrice = price[0];
TargetLong = TargetPrice + StopLoss;
TargetShort = TargetPrice — StopLoss;
}

void SetupOrders() {
TrailingTicket = -1;
TrailingStop = TargetPrice;

Trade.SetTypeFilling(ORDER_FILLING_RETURN);

Trade.BuyStop (volume(), TargetLong, _Symbol, TargetLong — StopLoss, 0, ORDER_TIME_DAY);
Trade.SellStop(volume(), TargetShort, _Symbol, TargetShort + StopLoss, 0, ORDER_TIME_DAY);

// First out
Trade.SellLimit(volume() / 3, TargetLong + StopLoss, _Symbol, 0, 0, ORDER_TIME_DAY);
Trade.BuyLimit (volume() / 3, TargetShort — StopLoss, _Symbol, 0, 0, ORDER_TIME_DAY);

DisplayStats();
}

void OnChartEvent(const int id, // идентификатор события 
const long& lparam, // параметр события типа long
const double& dparam, // параметр события типа double
const string& sparam // параметр события типа string
) {

if (id == CHARTEVENT_KEYDOWN) {
//DEBUG(«key pressed = » + lparam);
switch (lparam)
{
case 116: // F5
if (TargetPriceOverride > 0) {
TargetPrice = TargetPriceOverride;
DEBUG(«manual recalculate prices!»);
}
CalculatePrice();
TrailingStop = TargetPrice;
DisplayStats();
break;
case 220: // \
DEBUG(«manual setup orders!»);
SetupOrders();
break;
}
}
}

void DisplayStats() {
CalculateBalance();
string info = StringFormat(
"\n InitialBalance = %0.2f\n CurrentBalance = %0.2f\n MaxBalance = %0.2f" +
"\n =======================================" +
"\n InitialBalanceFactor = %0.2f\n CurrentBalanceFactor = %0.2f\n MinBalanceFactor = %0.2f" +
"\n =======================================" +
"\n InitialMargin = %0.2f\n CurrentLotSize = %0.2f" +
"\n =======================================" +
"\n TargetPrice = %0.2f\n TargetLong = %0.2f\n TargetShort = %0.2f\n TrailingTicket = %d\n TrailingStop = %0.2f\n",
InitialBalance, Balance, MaxBalance,
Y, Xbalance, Xmin,
Sym.MarginInitial(), volume(),
TargetPrice, TargetLong, TargetShort, TrailingTicket, TrailingStop);
Comment(info);
}

void CleanUpPositions() {
//--- close up all positions
DEBUG(«PositionsTotal = » + (string)PositionsTotal());
Trade.PositionClose(_Symbol);
}

//--- clean up all orders
void CleanUpOrders(bool keepTrailing = true) {
ulong ticket = 0;

DEBUG(«OrdersTotal = » + (string)OrdersTotal());
for(int pos = OrdersTotal(); pos >= 0; pos--) {
ticket = OrderGetTicket(pos);
if(Order.Select(ticket)) {
DEBUG(«got ticket = » + (string)ticket + " by index = " + (string)pos + " | comment = " + Order.Comment());
}

if(Order.Symbol() == _Symbol) {
if (Order.Comment() == TrailingComment && keepTrailing) {
DEBUG(«this is trailing stop, requested to keep-alive»);
continue;
}

DEBUG(«going to delete ticket = » + (string)ticket);
Trade.OrderDelete(ticket);
}
}
}

//+------------------------------------------------------------------+
//| volume |
//+------------------------------------------------------------------+
double volume() {
CalculateBalance();

if (Xbalance > 90 && Xbalance <= 95) {
return lotSize + lotSizeStep;
}

if (Xbalance > 85 && Xbalance <= 90)
return lotSize + lotSizeStep * 2;

if (Xbalance > 80 && Xbalance <= 85)
return lotSize + lotSizeStep * 3;

if (Xbalance <= 80)
return lotSize + lotSizeStep * 4;

return lotSize;
}

// updates new balance high
void CalculateBalance() {
Balance = AccountInfoDouble(ACCOUNT_BALANCE);

if(MaxBalance < Balance)
MaxBalance = Balance;

Xbalance = Balance * 100 / MaxBalance;
Y = Balance * 100 / InitialBalance;

if(Xbalance < Xmin)
Xmin = Xbalance;
}

void DEBUG(string message) {
if (!DEBUG_ENABLE)
return;

Print(" — DEBUG: ", message);
}

//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
Trade.LogLevel(LOG_LEVEL_NO);

Sym.Name(_Symbol);
CalculateBalance();
DisplayStats();

return(0);
}

★24



Пользователь запретил комментарии к топику.

....все тэги
Регистрация
UPDONW