Библиотеки: Virtual - страница 51

 

Интересно стало почему же моя ф-я расчета комиссии по % в USD на лот добавляла аж 60%.
1) Я использовал GetSymbol() для передачи в функцию, а там работа с CharArrayToString - основная часть тормозов была тут, решил просто передавать _Symbol, но
2) оказывается если в функцию передается строка, то она вызывается намного медленнее, чем без неё. Если делать мультивалютник, то надо будет передавать SymbolID.

Новые замеры:
без моих ф-й, как ваш вариант, но со всеми ускорителями обнаруженными ранее:
shortest pass 0:00:00.152, longest pass 0:00:00.326, average pass 0:00:00.155 EURUSD USD
shortest pass 0:00:00.171, longest pass 0:00:00.372, average pass 0:00:00.173 по  EURCHF EUR

Все мои функции
shortest pass 0:00:00.158, longest pass 0:00:00.323, average pass 0:00:00.160 по EURUSD USD без запроса цен - замедление 3%

Все с сложным расчетом по  EURCHF CHF с запросом 2х цен
shortest pass 0:00:00.180, longest pass 0:00:00.381, average pass 0:00:00.183 - замедление 6%

Итого: от 3 до 6% замедления за получение комиссии, свопов, контроля времени и перевода прибыли в валюту депозита.
Наверное все таки буду пользоваться.

ПС. У вас там тоже есть обращения к GetSymbol() в Order_Base(), но они вроде нечасто вызываются, не смотрел детально. Отказаться бы от CharArrayToString.

Symbol можно хранить в массиве структур символов и их параметров (для свопов, торг. сессии и т.д.), в осн. расчетах использовать только SymbolID.
А для комментариев может отдельный строковый массив сделать и использовать ID ордеров для чтения/записи.
 
Forester #:

Если делать мультивалютник, то надо будет передавать SymbolID.

На мультивалютке море проблем, это только одна из них, к сожалению.

ПС. У вас там тоже есть обращения к GetSymbol() в Order_Base(), но они вроде нечасто вызываются, не смотрел детально. Отказаться бы от CharArrayToString.

GetSymbol не используется. Ускорение работы сделано здесь.
bool ORDER_BASE::Copy( const bool WithoutHistory = false );


Для Тестера можете попробовать вместо _Symbol использовать NULL. Честно говоря, не понимаю, почему MQ выбрали дорогой string для SymbolInfo-функций.

 
fxsaber #:

На мультивалютке море проблем, это только одна из них, к сожалению.

Да я тоже хотел было, да передумал)
fxsaber #:
Для Тестера можете попробовать вместо _Symbol использовать NULL. 
Для определения комиссии и перевода к валюте депозита, я все строковые варианты (BASE_USD, USD_BASE, PROFIT_USD,... ACCOUNT ....)    заранее склеил при создании тестера. В работе с тиками строковых операций не провожу, а использую эти готовые склейки по которым и запрашиваю нужные цены из терминала. Т.е. максимально быстро. Могу скинуть, если надо будет. Времени нормально потратил, для подбора точного совпадения с тестером MQ (где-то Ask, где-то Bid, иногда наоборот или только Ask или Bid).
Пока в отдельных переменных храню, но можно в массив структур для индексации инструментов все скинуть.
 
Вернулся к свопам. Возможно вам пригодится.
При пересчете их в валюту депозита нужна цена по первому тику в момент начисления каждый день. Расчет в конце теста, дает свою цену. Т.е. совпадения с тестером MQ не получилось.
Сделал проверку ролловера на каждом тике и расчет свопов на лету в NewTick( const MqlTick &Tick ):
  #ifdef ORDER_SWAP
  int Day = TimeDay(Tick.time); //номер дня сменится при первом тике дня
  if (Day > prewDay_ && TimeSec(Tick.time) >= this.RolloverTime_){//прохождение RolloverTime, считаем свопы
      this.prewDay_ = Day;//до смены дня не выполнять
       for (int i = this.AmountOrders-1; i >=0; i--){
         if (this.Orders[i].IsPosition() && this.Orders[i].IsNotNull()) {//открытые + //проверка как в AddHistoryOrder() + не открыт на этом тике
           double newSwap = (this.Orders[i].GetType() == 1 ? this.SwapShort_ : this.SwapLong_) * this.Orders[i].GetLots() * (this.Rollover3Days_ + 1 == TimeDayOfWeek( Tick.time ) ? 3.0 : 1.0);
         #ifdef TO_ACCOUNT_CURRENCY
           newSwap *= K_CURRENCY_TO_ACC_CURRENCY( this.Orders[i].GetType()==1 ? 0 : 1 );//
         #endif
           this.Orders[i].SetSwap( this.Orders[i].GetSwap() + newSwap );
         }
       }
  }
  #endif 

  и в соответстрвующих местах добавил:
  в Order_Base.mqh
  void SetSwap( const double NewSwap ){ this.Swap = ::NormalizeDouble(NewSwap, ORDER_BASE::DigitsCurrency); }

  в глобале
  int TimeDay      ( datetime time ){return((int)( time / 86400));}//current Day from on 1 Jan 1971
  int TimeDayOfWeek( datetime time ){return((int)((time / 86400 + THURSDAY) % 7));}//current DayOfWeek. 86400 sec in day.  7 days in week.  THURSDAY on 1 Jan 1971

Тормозов нет, т.к. на каждом тике всего 1 раз делается time / 86400, и 1 проверка Day > prewDay_. Все остальные расчеты 1 раз в день. Очень быстро. Всего на 1мс или 0,6% медленнее.
Для замера скорости, решил исключить влияние увеличения размера массива истории и задал ему сразу 200000 строк, в тесте около 150000 будет использовано. Это дало ускорение на 11%


массив 2000 без всех доп. функций
2023.12.07 18:13:21.984    Statistics    shortest pass 0:00:00.184,

массив 200000 без всех доп. функций
2023.12.07 18:23:12.170    Statistics    shortest pass 0:00:00.165,  11% быстрее от 184

свопы - массив 200000
2023.12.07 18:27:50.399    Statistics    shortest pass 0:00:00.166, на 0,6% медленнее от 165
торг сессия -  массив 200000
2023.12.07 18:38:46.890    Statistics    shortest pass 0:00:00.165, то же время
пересчет в валюту депозита - массив 200000
2023.12.07 18:40:02.505    Statistics    shortest pass 0:00:00.175, на 6% медленнее от 165
комиссия в % -  массив 200000
2023.12.07 18:41:25.570    Statistics    shortest pass 0:00:00.184, на 11% медленнее от 165
всё - массив 200000
2023.12.07 18:42:40.264    Statistics    shortest pass 0:00:00.194,  на 17% медленнее от 165

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

ПС.
Столкнулся с тем, что свопы были начислены случайным сделкам открытым сразу после ролловера. Видимо ячейка Swap не переписывается при перемещении строки на новое место в массиве сделок (когда она не задана). Задал в создании ордера Create(...){.... this.Swap = 0.0; ...}  После этого лишние свопы не появляются.

Соответствие с тестром MQ точное, кроме инструментов, у которых свопы начисляются в конце теста, если их вычесть, то получится точно. Если исправят тестер MQ к одинаковому поведению, то будет точно совпадать со всеми инструментами.

 
Forester #:

Если делать мультивалютник, то надо будет передавать SymbolID.

Архитектурно создал мультивалютный Virtual. Можно запустить у себя на таком примере.
// "Hello Wolrd!" мультивалютного Virtual.

#define MAX_ORDERS 1000 // Максимальное количество живых ордеров.
#include <fxsaber\Virtual\Virtual.mqh> // https://www.mql5.com/ru/code/22577

bool IsSelected( const string &Symbols[] )
{
  bool Res = true;
  
  for (uint i = ArraySize(Symbols); Res && (bool)i--;)
    Res &= SymbolInfoInteger(Symbols[i], SYMBOL_SELECT);
    
  return(Res);
}

void OnStart()
{
  const string Symbols[] = {"EURUSD", "GBPCHF", "AUDJPY"}; // Обзор рынка.
  
  if (IsSelected(Symbols) &&                             // Если все символы выбраны,
      VIRTUAL::SelectByHandle(VIRTUAL::Create(Symbols))) // создаем и выбираем мультивалютное виртуальное окружение.
  {
    VIRTUAL::MultiTick(); // Мультивалютный тик.
    
    MqlTick Tick;
    
    // Открыли позицию по каждому символу.
    for (uint i = ArraySize(Symbols); (bool)i--;)
      if (SymbolInfoTick(Symbols[i], Tick))
        OrderSend(Symbols[i], OP_BUY, 1, Tick.ask, 0, 0, 0, "Hello World!");
        
    // Показали окружение.
    for (uint i = OrdersTotal(); (bool)i--;)
      if (OrderSelect(i, SELECT_BY_POS))
        OrderPrint();
  }
}


Результат.

#4 2023.12.08 01:39:29.339 buy 1.00 EURUSD 1.07912 0.00000 0.00000 1.07910 0.00 0.00 -0.02 Hello World! 0: -2 (-2) - 00:00:00
#3 2023.12.08 01:39:29.339 buy 1.00 GBPCHF 1.10210 0.00000 0.00000 1.10190 0.00 0.00 -0.20 Hello World! 0: -20 (-20) - 00:00:00
#2 2023.12.08 01:39:29.339 buy 1.00 AUDJPY 95.033 0.000 0.000 95.026 0.00 0.00 -7.00 Hello World! 0: -7 (-7) - 00:00:00


Остальное дописать - рутина, т.к. архитектурно все решилось. Реализация довольно простая. SymbolID не понадобился. Критика приветствуется.

 
Forester #:

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

Спасибо за исследование, посмотрю эту тему точно не раньше завершения мультивалютки.
 
fxsaber #:
Архитектурно создал мультивалютный Virtual. Можно запустить у себя на таком примере.

...
Остальное дописать - рутина, т.к. архитектурно все решилось. Реализация довольно простая. SymbolID не понадобился. Критика приветствуется.

Класс! Что можно тестировать? Что должно работать, а что нет?

 
Andrey Khatimlianskii #:

Класс! Что можно тестировать? Что должно работать, а что нет?

Пока не сделан выбор по тикету, поэтому не работают функции, где это используется: OrderModify, OrderDelete, OrderClose, SELECT_BY_TICKET.

Как тикеты будут сделаны, станет полноценным. Сейчас можно (не проверял) выставить маркет/отложку с SL/TP и увидеть все этапы срабатывания.


Работает внутреннее время Обзора рынка.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Библиотеки: Virtual

fxsaber, 2023.12.08 00:42

#4 2023.12.08 01:39:29.339 buy 1.00 EURUSD 1.07912 0.00000 0.00000 1.07910 0.00 0.00 -0.02 Hello World! 0: -2 (-2) - 00:00:00
#3 2023.12.08 01:39:29.339 buy 1.00 GBPCHF 1.10210 0.00000 0.00000 1.10190 0.00 0.00 -0.20 Hello World! 0: -20 (-20) - 00:00:00
#2 2023.12.08 01:39:29.339 buy 1.00 AUDJPY 95.033 0.000 0.000 95.026 0.00 0.00 -7.00 Hello World! 0: -7 (-7) - 00:00:00

Например, секунду назад был последний тик EURUSD, а сейчас пришел тик GBPUSD. На этом тике решили открыть позицию по EURUSD - время открытия будет равно тику GBPUSD.

 
fxsaber #:
Архитектурно создал мультивалютный Virtual. Можно запустить у себя на таком примере.


Результат.


Остальное дописать - рутина, т.к. архитектурно все решилось. Реализация довольно простая. SymbolID не понадобился. Критика приветствуется.

Все таки решили мультивалютник сделать. Возможно понадобится и мне, после тестов на одной валюте. Я только начинаю работу с виртуалкой и начну с 1 валюты (и боюсь это минимум на полгода-год), а вы, видимо, уже все возможности 1-валютных экспертов исследовали и хотите к мультикам перейти.

SymbolID полезен был бы для избавления от строковых полей в структурах (Symbol меняем на ID, коммент в отд. массиве хранить) и от  медленного CharArrayToString и StringToCharArray при создании каждого ордера (не знаю насколько он медленный). Плюс по SymbolID хранение параметров символа в отд. массиве.

Из критики:
По оптимизации скорости:
- зачем в MultiTick( void ) каждый раз создавать массив Ticks[], может где-то 1 раз его создать и потом использовать?

- в CalcOrders( void ) не очень понял что она делает... похоже что при изменении истории, коприрует  все ордера по всем символам в один OrdersCounter[]. Но не вижу что с этим дальше делается. Может можно только 1 измененный добавлять? Возможно эти копирования будут медленнее, чем просто пересчитать все инструменты в валюту депозита (~17%)
Зачем все инструменты пересчитывать? Если собирать мульти тики с индикаторов на нужных инструментах, то можно как-то передать имя инструмента и только ему делать пересчет. По другим-то изменений нет. Если не с индикаторов, а по тикам основного символа, то да - всех пересчитывать.

- в   int CorrectTicksTime( MqlTick &Ticks[] ){
    const int Amount = ::MathMin(this.Size, ::ArraySize(Ticks));
размер Ticks вроде бы такой же как this.Size, ниже есть ArrayResize(Ticks, this.Size)

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

По идее:
как я понял, для каждого символа делается свой new ORDERS и все расчеты идут в своих массивах. С одной стороны удобно смотреть баланс по конкретному инструменту.
Но обычно мультивалютник на одном балансе работает. Как потом сделать общий отчет, общий баланс? Видимо и итоговых отчетов через Report.mqh надо несколько делать?
Я думал, что можно сделать мультик в лоб - в одном ORDERS: прописывать символ (не использовать основные _Symbol, _Points... а из заранее заполненных переменных брать) и пересчитывать всё в валюту депозита. Пересчет добавит ~17%, но будет точный результат. Если не пересчитывать, а оставлять в пипсах, то будет конечно несоразмерный общий баланс, но он и с отдельными массивами несоразмерный, да еще и склеивать их как то придется (если по последней цене, то тоже несоразмерно, за полгода стоимость инструментов может и на 10% измениться). Как вариант аналогичный вашему, балансы разным инструментам в пипсах можно и в одном ORDERS считать по SymbolID, чтобы потом анализировать отдельные символы, если надо.
 
Forester #:

SymbolID полезен был бы для избавления от строковых полей в структурах (Symbol меняем на ID, коммент в отд. массиве хранить) и от  медленного CharArrayToString и StringToCharArray при создании каждого ордера (не знаю насколько он медленный).

shortest pass 0:00:00.119,

убрал вызовы  CharArrayToString и StringToCharArray вместо них сохраняю NULL и возвращаю ""
shortest pass 0:00:00.114, - 4,4 % ускорения

Плюс ускорится, если функция не будет содержать входных параметров типа
string .