Отчего так ?

 

Почему-то на случайных входах с (по идее) равными вероятностями не выходит случайного блуждания.

Вполне возможно что в коде где-то "детская" ошибка или чудеса подсчёта double.

Уже замотался смотреть, взгляд замылился, может кто свежим взглядом найдёт 

Без учёта спреда, в тестере типично выходит :

ещё раз - тут нет спреда (считайте что спред=0), входы равновероятны, стоп-лосс=тейк-профит, прибыль/убыток на сделку = 5

советник прикладываю, он пишет файл отчёта undead.csv, в котором 8-я колонка, как раз и есть результат без спредов

сам себе это мартингейл, вырезка для теста из более рабочей совы :-)

//+------------------------------------------------------------------+
//|                                                 PublicUndead.mq4 |
//|                                Copyright 2020, Maxim A.Kuznetsov |
//|                                          https://www.luxtrade.tk |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, Maxim A.Kuznetsov"
#property link      "https://www.luxtrade.tk"
#property version   "1.00"
#property strict

input int SL_POINTS=60; // стоп-лосс, пунктов
input int TP_POINTS=60;// тейк-профит, пунктов
input int SPREAD=10;    // включая спред, пунктов
extern double LOTS=0.1; // начальный торговый лот
/**
   советник ведёт отдельный учёт потерь на спред+своп+комиссию+проскальзывания
   и пытается их компенсировать жёстким мартингейлом
   ----
   торговля ведётся по случайным входам Buy/Sell 50/50
   чтобы не попадать в магию уровней, между сделками случайная пауза от 10 до 30 мин
   ----
   этот вариант советника - исключительно для тестера, ради краткости и демонстрации алгоритма 
**/
double debet;        /// сумма потерь (недополученная прибыль), а-ля дебиторский долг
double currBalance;  /// текущий баланс
double maxBalance;   /// максимально достигнутый баланс
double nominalBalance;/// номинальный баланс, без спредов и прочих расходов
double supernomBalance; /// "супер" номинальный - не корректируемый, должен быть как СБ

double nominalTake;  /// номинальная прибыль по TP = (TP-SPREAD)*TICK*LOTS
                     /// !! за вычетом скольжений,свопов и комиссий совпадает с реальным

double nominalLoss;  /// номинальный убыток по SL = (SL-SPREAD)*TICK*LOTS
                     /// !! он меньше реально-получаемого на 1 спред и прочие расходы

double lotSum;       /// заодно посчитаем оборот в лотах
double spreadSum;    /// и деньги по спредам, подаренные в DC

int ticket;          /// текущий ордер
datetime dealtime;   /// время для открытия следующей сделки

/// торговые условия, для тестера - постоянны
double tickValue;
double minLot;
double maxLot;
double lotStep;

/// будем пачками сохранять результаты в CSV файл
const string reportFileName="undead.csv";
int totalDeals;
struct TradeResult {
   int num;             // номер трейда
   int type;            // тип ордера buy/sell
   int pl;              // результат выиграл/проиграл
   double realResult;   // OrderProfit+Swap+Commission
   double nominalResult;// номинальный результат
   double currBalance;  // баланс после закрытия
   double nominalBalance;// номинальный после закрытия
   double supernomBalance;// супер-номинальный после закрытия
};
TradeResult Results[];  // массив результатов

int OnInit()
{
   MathSrand(GetTickCount());
   totalDeals=0;
   debet=0;
   maxBalance=nominalBalance=currBalance=AccountBalance();
   supernomBalance=currBalance;
   nominalTake=(TP_POINTS-SPREAD)*MarketInfo(_Symbol,MODE_TICKVALUE)*LOTS;
   nominalLoss=(SL_POINTS-SPREAD)*MarketInfo(_Symbol,MODE_TICKVALUE)*LOTS;
   ticket=-1;
   dealtime=TimeCurrent()+MathRand()%600;  // (30-10)/2=10 минут
   // мелкая статистика
   lotSum=0;
   spreadSum=0;
   // для тестера - торговые условия
   tickValue=MarketInfo(_Symbol,MODE_TICKVALUE);
   minLot=MarketInfo(_Symbol,MODE_MINLOT);
   maxLot=MarketInfo(_Symbol,MODE_MAXLOT);
   lotStep=MarketInfo(_Symbol,MODE_LOTSTEP);
   // привести в порядок LOTS
   LOTS=minLot+MathRound((LOTS-minLot)/lotStep)*lotStep;
   // Резервируем 100 результатов
   ArrayResize(Results,0,100);
   Show();
   Comment(StringFormat("nominalTake=%s\nnominalLoss=%s",DoubleToString(nominalTake,_Digits),DoubleToString(nominalLoss,_Digits)));
   if (FileIsExist(reportFileName)) {
      FileDelete(reportFileName);
   }
   return(INIT_SUCCEEDED);
}
double CalcLots()
{
   // если долгов нет, то обычный лот
   if (debet<=0.0) return LOTS;
   // если долг есть, то увеличиваем лот чтобы их покрыть
   // не забывая про спред, поэтому множитель 2.0
   // округление вниз, потому-что мартингейлы, там вверх больно
   double adds=MathFloor(tickValue*debet/(TP_POINTS-2.0*SPREAD)/lotStep)*lotStep;
   // LOTS заранее приведён в порядок, поэтому просто сложение
   return LOTS+adds;
}

// ордер выбранный по OrderSelect закрылся
// провести учёт
void OnOrderClose()
{
   if (OrderType()==OP_SELL) {
      spreadSum+=tickValue*OrderLots()*(Ask-Bid)/_Point;
   }
   if (OrderProfit()>=0) {
      // из долгов списывается вся прибыль сверх номинала 
      debet-=(OrderProfit()+OrderSwap()+OrderCommission()) - nominalTake;
      nominalBalance+=nominalTake;
      supernomBalance+=nominalTake;
      if (debet<0) debet=0;
   } else {
      // в долг начисляются все убытки сверх номинала
      debet+=- ( (OrderProfit()+OrderSwap()+OrderCommission()) + nominalLoss);
      nominalBalance-=nominalLoss;
      supernomBalance-=nominalLoss;
   }
   currBalance=AccountBalance();
   if (currBalance>maxBalance) {
      maxBalance=currBalance;
      nominalBalance=currBalance;
      debet=0;
   }
   dealtime=TimeCurrent()+10*60+(MathRand()%(20*60)); // след.сделка в промежутке 10-30 минут
   // сохраняем результаты
   int pos=ArraySize(Results);
   ArrayResize(Results,pos+1);
   Results[pos].num=totalDeals;
   Results[pos].type=OrderType();
   Results[pos].pl=(OrderProfit()>0);
   Results[pos].realResult=OrderProfit()+OrderSwap()+OrderCommission();
   Results[pos].nominalResult=(OrderProfit()>=0?nominalTake:-nominalLoss);
   Results[pos].currBalance=currBalance;
   Results[pos].nominalBalance=nominalBalance;
   Results[pos].supernomBalance=supernomBalance;
   if (pos==99) SaveResults();
   DeleteTradeArrows();
}
// расставить StopLoss TakeProfit выбранному ордеру после его открытия
bool PlaceSLTP() {
   double stopLoss;
   double takeProfit;
   if (OrderType()==OP_BUY) {
      stopLoss=OrderOpenPrice()-SL_POINTS*_Point;
      takeProfit=OrderOpenPrice()+(TP_POINTS-SPREAD)*_Point;
   } else {
      stopLoss=OrderOpenPrice()+SL_POINTS*_Point;
      takeProfit=OrderOpenPrice()-(TP_POINTS-SPREAD)*_Point;
   }
   return OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(stopLoss,_Digits),NormalizeDouble(takeProfit,_Digits),0);
}
void OnTick()
{
   if (ticket==-1 && TimeCurrent()>dealtime) {
      ticket=TradeByMarket(MathRand()&1);
   } else if (OrderSelect(ticket,SELECT_BY_TICKET)) {
      if (OrderCloseTime()!=0) {
         OnOrderClose();
         Show();
         ticket=-1;
      } else if (OrderTakeProfit()==0 || OrderStopLoss()==0) {
         PlaceSLTP();
      }
   }
}

int TradeByMarket(int op)
{
   double lots=CalcLots();
   if(AccountFreeMarginCheck(Symbol(),op,lots)<=0 || GetLastError()==134) {
      ExpertRemove();
      return -1;
   }
   int ok=OrderSend(_Symbol,op,lots,op==OP_BUY?Ask:Bid,0,0,0);
   if (ok>=0) {
      lotSum+=lots;
      if (op==OP_BUY) {
         spreadSum+=tickValue*OrderLots()*(Ask-Bid)/_Point;
      }
      totalDeals++;
      Show();
   }
   return ok;
}

void OnDeinit(const int reason)
{
   SaveResults();
}

/// отобразить всякое
void Show()
{
   Label("currBalance",StringFormat("currBalance=%.2f",currBalance),15);
   Label("maxBalance",StringFormat("maxBalance=%.2f",maxBalance),30);
   Label("nominalBalance",StringFormat("nominalBalance=%.2f",nominalBalance),45);

   Label("curr-nominal",StringFormat("curr-nominal=%.2f",currBalance-nominalBalance),75);
   Label("debet",StringFormat("debet=%.2f",debet),90);

   Label("lotSum",StringFormat("lotsSum=%.2f",lotSum),120);
   Label("spreadSum",StringFormat("spreadSum=%.2f",spreadSum),135);

}
void Label(string name,string text,int y,int x=200)
{
   if (ObjectType(name)==OBJ_LABEL || ObjectCreate(0,name,OBJ_LABEL,0,0,0)) {
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
   }
}
void SaveResults()
{
   int f=FileOpen(reportFileName,FILE_WRITE|FILE_READ|FILE_REWRITE|FILE_CSV,',',CP_UTF8);
   if (f==INVALID_HANDLE) {
      Alert("Unable to open file");
      return;
   }
   FileSeek(f,0,SEEK_END);
   int total=ArraySize(Results);
   for(int pos=0;pos<total;pos++) {
      FileWrite(f,
         IntegerToString(Results[pos].num),
         IntegerToString(Results[pos].type),
         IntegerToString(Results[pos].pl),
         DoubleToString(Results[pos].realResult,_Digits),
         DoubleToString(Results[pos].nominalResult,_Digits),
         DoubleToString(Results[pos].currBalance,2),
         DoubleToString(Results[pos].nominalBalance,2),
         DoubleToString(Results[pos].supernomBalance,2)
      );
   }
   FileClose(f);
   ArrayResize(Results,0,100);
}
void DeleteTradeArrows()
{
   string id=StringFormat("#%d",totalDeals);
   for(int pos=ObjectsTotal(0,0);pos>=0;pos--) {
      string name = ObjectName(0,pos,0);
      if (StringGetChar(name,0)=='#' && StringFind(name,id,0)!=0) ObjectDelete(0,name);
   }
}
Файлы:
 

считайте что вышесказанное, это просто сокращённый метод монте-карло. Ошибок в программной реализации я не изыскал. 

по EURUSD имеются волшебные 65/95, интуитивный анализ представлю завтра

завтра - потому что идея как женщина, с ней надо переспать, до вывода в свет

 

Может быть тренд на участке тестирования?

Для чистоты эксперимента надо протестировать на СБ.

 
Aleksey Nikolayev:

Может быть тренд на участке тестирования?

Для чистоты эксперимента надо протестировать на СБ.

участок тестирования с начала 2010 по конец 2019...там всякое бывало, и тренды и флеты

и на СБ нельзя тестировать, потому как у уважаемых оппонентов нет уверенности что это СБ и что реальный ценовой ряд можно эмулировать генератором(то есть должного генератора нету)

 
Maxim Kuznetsov:

участок тестирования с начала 2010 по конец 2019...там всякое бывало, и тренды и флеты

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

Maxim Kuznetsov:

и на СБ нельзя тестировать, потому как у уважаемых оппонентов нет уверенности что это СБ и что реальный ценовой ряд можно эмулировать генератором(то есть должного генератора нету)

Если проблема в качестве генерации СБ, то можно взять с квантового компьютера.

 
Aleksey Nikolayev:

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

Если проблема в качестве генерации СБ, то можно взять с квантового компьютера.

качества обычного Random хватает с головой ( вообще не ясно отчего к нему подозрения),

но все прочие выводы всё-же завтра..

есть риск, что в существуют программные ошибки (где нить +-1 или округление не то) - тогда тем более надо подождать

 

вот полезно брать паузу, искать баги, а не стремглав шатать фундамент  :-)

в программе присутствует не то чтобы ошибка, но хитрый недочёт, и если его устранение повлечёт существенное изменение распределений, то значит пойман недельный цикл

Не думаю что сильно повлияет на результат, но баг прикольный - случайная задержка между сделками измерялась в календарном времени, а торговли в выходные и на праздниках нету. В результате с несколько большим шансом сделки начинают открываться прямо в начале недели, на первом-же тике. Это несмотря на большой торговый диапазон (65+95=160) и незначительную задержку между трейдами (от 10 до 30 минут)

чтобы всё было честно, и можно было продолжить, задержу надо считать как "сколько секунд торгового времени прошло между двумя календарными моментами"

видимо потребуется помощь зала в такой функции - может у кого завалялась, не требующая подкачки поминутной истории за дцать-лет :-)

 
Maxim Kuznetsov:

чтобы всё было честно, и можно было продолжить, задержу надо считать как "сколько секунд торгового времени прошло между двумя календарными моментами"

у Вас вроде версия для тестера, почему нельзя запомнить в статик переменных необходимые даты в нужные моменты?


PS: чем то Ваш анализ напоминает статью https://habr.com/ru/post/499910/ , информации в статье  мало, а пример на Питон очень хороший ;)

 
Igor Makanu:

у Вас вроде версия для тестера, почему нельзя запомнить в статик переменных необходимые даты в нужные моменты?


PS: чем то Ваш анализ напоминает статью https://habr.com/ru/post/499910/ , информации в статье  мало, а пример на Питон очень хороший ;)

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

не хватает таймера по рынку.

Прикольный недочёт, более чем уверен что подобный попадос есть и в прокшене и в понедельник утром свершаются чудеса. 

 
Igor Makanu:

у Вас вроде версия для тестера, почему нельзя запомнить в статик переменных необходимые даты в нужные моменты?


PS: чем то Ваш анализ напоминает статью https://habr.com/ru/post/499910/ , информации в статье  мало, а пример на Питон очень хороший ;)

прочёл статью, в принципе если инструментарий после всех баг-фиксов, будет давать схожие с прежними результаты, ТО

честно смогу предположить, что у нас тут порылось "двухголовое распределение", при этом чётно-фрактальное. То есть изначально имеет два горба и от масштабного коэфф. между любыми двумя "пиками" вырисовываются ещё два вторичных пика.

я такое видел, единожды, поэтому предполагаю знакомое зло..

 

насчет четно-фрактального не в курсе, а помню, что профиль рынка "крутил" там всегда то смещенное в одну сторону распределение, то вот двухгорбое будет

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

Причина обращения: