Новая версия платформы MetaTrader 5 build 5120: улучшения и исправления - страница 23

 
Forester #:
Удалось добиться появления баров и торговли:
если бары строятся по Bid - то достаточно в тиках цен ask и bid
если по Last, то в бары надо добавить и цены Last

Жаль что в справке не написано об этом.

А как может быть построения по Last без цен Last?

 
Aleksey Vyazmikin #:

А как может быть построения по Last без цен Last?

Бары можно формировать из любых цен, если их сам рассчитываешь и переписываешь. Я рассчитал от последних ask и bid. Но оказывается, что надо их и добавить и отметить флагом. При этом размер файла за 1 месяц с 70 до 100 мб увеличился. Смысл не добавлять last - в экономии диска.
 

Решил для себя отказаться от пометки, что бары построены по Last.

Кто-то знает, что эта пометка дает?

Видимо изменит только тесты по барам.
Будет high=ask, a low=bid. Это очень хорошо, т.к. стопы/лимитки будут срабатывать с учетом реального спреда, а не минимального за бар.
Open и close может быть и bid и ask, что было первым или последним. - Это может немного путать.

Свои бары буду помечать что они по bid, но формировать c high=ask. Буду точно знать максимальный аск, а не фиктивный по минимальному спреду.

Идеальными были бы бары у которых доступны 8 цен (OHLC и по bid и по ask), а не 4 + минимальный спред.
 
Forester #:
Бары можно формировать из любых цен, если их сам рассчитываешь и переписываешь. Я рассчитал от последних ask и bid. Но оказывается, что надо их и добавить и отметить флагом. При этом размер файла за 1 месяц с 70 до 100 мб увеличился. Смысл не добавлять last - в экономии диска.

У Вас получилась не торговая история, а история спроса и предложения. Т.е. информация о том, по каким ценам участники торгов желали осуществлять торговые операции. Цена Last - это про цены, по котором продавец договорился с покупателем и произвёл сделку. Не знаю, сильно ли будем это мешать МО, или наоборот...

 
Aleksey Vyazmikin #:

У Вас получилась не торговая история, а история спроса и предложения. Т.е. информация о том, по каким ценам участники торгов желали осуществлять торговые операции. Цена Last - это про цены, по котором продавец договорился с покупателем и произвёл сделку. Не знаю, сильно ли будем это мешать МО, или наоборот...

В данном случае я импортировал именно сделки. А они только по ask и bid могут быть проданы или куплены. Добавление last - только увеличит размер файлов на 30%, но не добавит новой информации.
Где-то читал, что могут быть last внутри спреда. Но думаю это просто до терминала не дошла информация, что аск или бид изменился на бирже - кто то выставил лимитку и ее тут же выкупили.

 
Forester #:
В данном случае я импортировал именно сделки. А они только по ask и bid могут быть проданы или куплены. Добавление last - только увеличит размер файлов на 30%, но не добавит новой информации.
Где-то читал, что могут быть last внутри спреда. Но думаю это просто до терминала не дошла информация, что аск или бид изменился на бирже - кто то выставил лимитку и ее тут же выкупили.

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

Есть важный нюанс, стоп ордера, в т.ч. SL/TP срабатывают по Ask/Bid, если память мне не изменяет, поэтому для более точной эмуляции торгового процесса нужны отдельно цены Last и Ask/Bid.

Опять же про крипту не знаю, как там что срабатывает...

 
Aleksey Vyazmikin #:

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

Есть важный нюанс, стоп ордера, в т.ч. SL/TP срабатывают по Ask/Bid, если память мне не изменяет, поэтому для более точной эмуляции торгового процесса нужны отдельно цены Last и Ask/Bid.

Опять же про крипту не знаю, как там что срабатывает...

Между сделками, аск и бид могут "гулять", но совершаться сделки будут по аск и бид на момент сделки. Для чего нужен ласт не ясно. Разве что для определения аск это был или бид.  Это можно и флагом пометить, но флаги не учитываются, если реально не записан ласт.
 
Forester #:
Между сделками, аск и бид могут "гулять", но совершаться сделки будут по аск и бид на момент сделки. Для чего нужен ласт не ясно. Разве что для определения аск это был или бид.  Это можно и флагом пометить, но флаги не учитываются, если реально не записан ласт.

Нет...

Вот пример. Цена 83985 не была непосредственно перед сделкой Bid/Ask.

Объём может быть больше, чем есть на уровне Bid/Ask в стакане, и это так же будет причина смещения в моменте цены Last за границы Ask/Bid. При это ММ может закрыть сразу это смещение за один тик, в один матчинг.

 
Aleksey Vyazmikin #:

Нет...

Вот пример. Цена 83985 не была непосредственно перед сделкой Bid/Ask.

Объём может быть больше, чем есть на уровне Bid/Ask в стакане, и это так же будет причина смещения в моменте цены Last за границы Ask/Bid. При это ММ может закрыть сразу это смещение за один тик, в один матчинг.

С 580-й миллисекунды по 703-ю кто-то мог поставить лимитку на 83985 и тут прилетел рыночный ордер, который ее и забрал. Просто этот аск почему-то программа/биржа не показала отдельной записью. Рыночные же всегда об лимитные исполняются. Так что можно считать что аск был.
 
Aleksey Vyazmikin #:

На сколько помню, просьба была как раз про хранение в ОЗУ одной копии истории, а не для каждого агента отдельно.

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


Через RAMDrive.

В Common-папке запустил такой bat-файл.

rem Создали RAM-Drive для Тестера.
imdisk -a -o awe -s 3G -m Z: -p "/fs:ntfs /q /y /v:MT5Tester"
mkdir z:\RAMDrive
mklink /j RAMDrive z:\RAMDrive


Сохранил тики.

Скриптом сохранил тики.

void OnStart()
{
  MqlTick Ticks[];
  
  if (CopyTicksRange(_Symbol, Ticks, COPY_TICKS_ALL, D'2024.01.01' * 1000) > 0)
  {
    Print(MQLInfoInteger(MQL_MEMORY_USED));
    
    FileSave("RAMDrive\\Ticks.bin", Ticks, FILE_COMMON);
  }
}


Запустил оптимизацию на этих тиках.

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

#property tester_no_cache

sinput int inBlockSize = 1e4;
sinput int inCycles = 1;
input int inRange = 0;

double GetRes( const int &Handle, const int BlockSize, long &Amount )
{
  double Res = 0;

  MqlTick Ticks[];
  
  FileSeek(Handle,0, SEEK_SET);
  
  const uint Total = (uint)FileSize(Handle) / sizeof(MqlTick);
  
  for (uint i = 0; !IsStopped() && (i < Total);)
  {
    const uint Size = FileReadArray(Handle, Ticks, 0, BlockSize);
    
    for (uint j = 0; j < Size; j++)
      Res += Ticks[j].bid;
      
    i += Size;
    Amount += Size;
  }
  
  return(Res);
}

bool ToFrame( const double Value, const long &Amount )
{
  int Data[];
  
  return(FrameAdd(TerminalInfoString(TERMINAL_DATA_PATH), Amount, Value, Data));
}

double OnTester()
{
  const ulong StartTime = GetMicrosecondCount();
  
  MqlTick Ticks[];
  
  const int Handle = FileOpen("RAMDrive\\Ticks.bin", FILE_SHARE_READ | FILE_BIN | FILE_COMMON);
  
  double Sum = 0;
  long Amount = 0;
  
  if (Handle != INVALID_HANDLE)
  {
    for (int i = 0; i < inCycles; i++)
      Sum += GetRes(Handle, inBlockSize, Amount);
    
    FileClose(Handle);
  }  

  const double Res = (double)Amount / (GetMicrosecondCount() - StartTime);
  ToFrame(Res, Amount);
    
//  return(Sum); // Проверка, что все агенты на выходе получают один и тот же результат.
  return(NormalizeDouble(Res, 1)); // Ticks(millions)/sec.
}

bool IsNewAgent( const string &Name )
{
  static string Names[];
  
  int Pos = 0;  
  const int Size = ArraySize(Names);
  
  while ((Pos < Size) && (Names[Pos] != Name))
    Pos++;
    
  const bool Res = (Pos == Size);
  
  if (Res)
    Names[ArrayResize(Names, Size + 1) - 1] = Name;
  
  return(Res);
}

int Passes = 0;
int Agents = 0;
long AmountTicks = 0;
double Performance = 0;

void OnTesterPass()
{
  ulong Pass;
  string Name;
  long ID;
  double Value;

  while(FrameNext(Pass, Name, ID, Value))  
  {
    Passes++;
    Performance += Value;
    AmountTicks += ID;

    Agents += IsNewAgent(Name);
  }
}

#define TOSTRING(A) "\n" + #A + " = " + (string)(A)

void OnTesterDeinit()
{
  Performance = NormalizeDouble(Performance / Passes, 1);
  AmountTicks /= Passes;
  
  MessageBox(TOSTRING(Agents) + TOSTRING(Passes) + TOSTRING(AmountTicks) +
             TOSTRING(Performance) + " Ticks(millions)/sec.");
  
  ChartClose();
}


Получил производительность оптимизации.

На рисунке выше показано, что в оптимизации был задействован один агент, совершен 31 проход, тиков ~800 миллионов, средняя производительность 48.9 миллионов тиков в секунду.


Зависимость производительности от количества агентов.

Замерил производительность для разного количества агентов.


И составил соответствующую таблицу.

Количество Агентов Производительность (миллионов тиков в секунду)
1 48.9
2 32.5
3 24.7
4 18.4
5 15.1
6 16.8

Доступ пяти агентов к одному и тому же куску памяти (через RAMDRive) происходит в три раза медленнее, чем с включенным только одним агентом. Видимо, сказывается разруливание коллизий при одновременном чтении данных.


НО.

Потребление Агентами памяти практически нулевое! Память используется только для единого хранения исторических данных на всех агенты. Даже при 100 одновременно работающих агентах потребление памяти будет минимальным.


Например, на машине только 12 Гб. Через данный способ можно взять историю на 8 Гб и задействовать пять агентов. При обычном использовании Тестера понадобится, соответственно, 40 гигов свободной памяти. При обходном маневре - чуть больше 8 гигов. Соответственно, скорость оптимизации на машине вырастает в пять раз: вместо одного агента можно задействовать пять (было бы 100 агентов, то 12 Гб RAM хватило бы и на такое количество).


Можно очень круто ускорить оптимизацию ТС через RAMDrive. Ну и, конечно, MQ могли бы создать свой механизм разруливания коллизий одновременного чтения данных, тогда производительность была бы гораздо выше, чем RAMDrive-реализация.


ЗЫ

Производительность почти не зависит от размера блоков чтения (см. входной inBlockSize): пробовал 10К и 100К.