алг. обнаружить и удалить треугольники/циклы

 

Алгоритм как-то нейдёт:

- обнаруживать треугольники и циклы (когда куплено GBPUSD, продано GBPJPY и куплено USDJPY то толку мало - это треугольник/кольцо, толку мало, в основном своп течёт) 

- закрывать с наименьшей погрешностью (посчитать объём к частичному закрытию, учитывая мин.лоты по парам)

пока дошёл до "считать валютную корзину" - например сколько по отдельности куплено и продано USD,GBP,JPY. Если одновременно есть много покупок и продаж, то это где-то локи и неэффективность.
Если таких валют 3 и больше - похоже что кольцо/цикл и надо от этого избавляться при первой возможности. 

 
Maxim Kuznetsov:

Алгоритм как-то нейдёт:

- обнаруживать треугольники и циклы (когда куплено GBPUSD, продано GBPJPY и куплено USDJPY то толку мало - это треугольник/кольцо, толку мало, в основном своп течёт) 

- закрывать с наименьшей погрешностью (посчитать объём к частичному закрытию, учитывая мин.лоты по парам)

пока дошёл до "считать валютную корзину" - например сколько по отдельности куплено и продано USD,GBP,JPY. Если одновременно есть много покупок и продаж, то это где-то локи и неэффективность.
Если таких валют 3 и больше - похоже что кольцо/цикл и надо от этого избавляться при первой возможности. 

Исходные данные - многомерный массив, число измерений равно числу котируемых инструментов. Элементы массива - открытые позиции по соответствующим парам. 

Алгоритм: берётся базовый инструмент и ищется цепочка, приводящая к нему же. 

 
Maxim Kuznetsov:

Алгоритм как-то нейдёт:

- обнаруживать треугольники и циклы (когда куплено GBPUSD, продано GBPJPY и куплено USDJPY то толку мало - это треугольник/кольцо, толку мало, в основном своп течёт) 

- закрывать с наименьшей погрешностью (посчитать объём к частичному закрытию, учитывая мин.лоты по парам)

пока дошёл до "считать валютную корзину" - например сколько по отдельности куплено и продано USD,GBP,JPY. Если одновременно есть много покупок и продаж, то это где-то локи и неэффективность.
Если таких валют 3 и больше - похоже что кольцо/цикл и надо от этого избавляться при первой возможности. 

Все позиции надо привести к стоимости в единой валюте (я к USD приводил). Далее определяется объем закрытия, он соответствует минимальному  объему (в единой валюте, естественно) среди всех составляющих кольцо инструментов. Затем производится закрытие с пересчетом в лоты по каждому инструменту. Все эти пересчеты неизбежно приведут к погрешностям, которые тоже придется как-то учитывать, дабы мелкие открытые объемы не зависали на счёте.

Многомерный  массив IMHO нафиг не нужен.

 
Sergey Gridnev #:
Все позиции надо привести к стоимости в единой валюте (я к USD приводил). Далее определяется объем закрытия, он соответствует минимальному  объему (в единой валюте, естественно) среди всех составляющих кольцо инструментов. Затем производится закрытие с пересчетом в лоты по каждому инструменту. Все эти пересчеты неизбежно приведут к погрешностям, которые тоже придется как-то учитывать, дабы мелкие открытые объемы не зависали на счёте.

Многомерный  массив IMHO нафиг не нужен.

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

конечно если торговать сразу через USD (вместо сделок по кроссу открывать 2 через доллар), то ситуация с циклами вырождается в отдельные локи по символам с которыми проще. Но двойной спред. Но возможно своп меньше и маржа (от встречных позиций). Если сделка живёт больше 2-3 ночей то наверное имеет смысл делать именно так.

пока склоняюсь к практическому решению - в 22-23 перед двойным свопом, переоткрывать кроссы через USD (то есть в приведённом примере закрыть GBPJPY и открыть максимально близкие GBPUSD,USDJPY), и вторым проходом по долларовым парам удалить полученные локи через CloseBy. Или вообще всю халабуду вечером закрыть, у утром переоткрыть только остатки через usd.

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

 
Позиция по кроссу как правило более выгодна, нежели 2 позиции по мажорам. Что касается закрытие перед полночью и восстановлением через некоторое время - мне кажется, на спредах будет больше потерь, чем на свопах. Может быть в ночь на среду это оправдано, да под выходные.
 
Sergey Gridnev #:
Позиция по кроссу как правило более выгодна, нежели 2 позиции по мажорам. Что касается закрытие перед полночью и восстановлением через некоторое время - мне кажется, на спредах будет больше потерь, чем на свопах. Может быть в ночь на среду это оправдано, да под выходные.

не то чтобы более выгодна, более точна скоре. Если торговать мин.лотами то от кроссов никуда. А когда объём 10-20 минимальных лотов, то уже раскладывается с допустимой точностью. (это к теме "не торгуйте мин.лотами - лучше влить денег, на край увеличить плечо")

переоткрывать всё конечно не выход. Это одну-две  позиции можно переоткрыть - закрыть и после 0:00 выставить лимитки на спред лучше закрытия. Как правило к 9, за азиатскую сессию, оно срабатывает. Не сработает, как-бы и не сильно обидно, редкая проблема.

а когда их 6-8 то почти гарантируется что хотя-бы одна лимитка несработает и проблема станет постоянной.

 
Maxim Kuznetsov #:

не то чтобы более выгодна, более точна скоре. Если торговать мин.лотами то от кроссов никуда. А когда объём 10-20 минимальных лотов, то уже раскладывается с допустимой точностью. (это к теме "не торгуйте мин.лотами - лучше влить денег, на край увеличить плечо")

переоткрывать всё конечно не выход. Это одну-две  позиции можно переоткрыть - закрыть и после 0:00 выставить лимитки на спред лучше закрытия. Как правило к 9, за азиатскую сессию, оно срабатывает. Не сработает, как-бы и не сильно обидно, редкая проблема.

а когда их 6-8 то почти гарантируется что хотя-бы одна лимитка несработает и проблема станет постоянной.

Не понял фразу о том, что алгоритм как-то нейдёт. 

 
Алексей Тарабанов #:

Не понял фразу о том, что алгоритм как-то нейдёт. 

чисто практически решение найдено. Но не оптимально и не с высоты математики, теории графов, циклов и потоков :-)

 
Maxim Kuznetsov #:

чисто практически решение найдено. Но не оптимально и не с высоты математики, теории графов, циклов и потоков :-)

Ну и ладненько. 

 

скрипт по мотивам обсуждений,

переоткрывающий позиции кроссов эквивалентным объёмом через USD

//+------------------------------------------------------------------+
//|                                                       Unroll.mq5 |
//|                                                Maxim A.Kuznetsov |
//|                                          https://www.luxtrade.tk |
//+------------------------------------------------------------------+
#property copyright "Maxim A.Kuznetsov"
#property link      "https://www.luxtrade.tk"
#property version   "1.00"

#property description "Закрывает позы по кроссам, открывает эквивалентный объём через USD"
#property description "в результате : фиксируется PL по кроссам, снижается нагрузка на депо"
#property description "               валютная корзина в порядке (без встречных)"
#property description "на парах с usd могут образоваться локи - их надо потом закрыть/отрулить"

input bool ALLOW_TRADE=false;

#property script_show_inputs

#include <MT4Orders.mqh>
#ifdef __MQL5__
bool RefreshRates() {
   return false;
}
#endif

class CurrencyInfo {
public:
   CurrencyInfo(string _name) {
      name=_name;
      volume=-1;
   }
   ~CurrencyInfo(){
   }
public:
   double volume;   
   string name;
};
CurrencyInfo *Currencys[];
CurrencyInfo *GetCurrency(string name)
{
   int index=-1;
   for(int pos=ArraySize(Currencys)-1;pos>=0;pos--) {
      if (Currencys[pos]==NULL) {
         index=pos;
         continue;
      }
      if (Currencys[pos].name==name) return Currencys[pos];
   }
   if (index==-1) {
      index=ArraySize(Currencys);
      if (ArrayResize(Currencys,index+1)!=index+1) return NULL;
   }
   Currencys[index]=new CurrencyInfo(name);
   return Currencys[index];
}
string GetSymbol(string one,string two)
{
   for(int pos=SymbolsTotal(false)-1;pos>=0;pos--) {
      string name=SymbolName(pos,false);
      if (name=="") continue;
      string base=SymbolInfoString(name,SYMBOL_CURRENCY_BASE);
      string quote=SymbolInfoString(name,SYMBOL_CURRENCY_PROFIT);
      if (one==base && two==quote) return name;
      if (one==quote && two==base) return name;
   }
   return "";
}
bool UnRoll(bool allowTrade=false)
{
   // закрывает позиции по кроссам, считаем итоговый объём по usd
   double profitOrders=0.0;   // посчитаем закрываемый PL
   int countOrders=0;      // и кол-во
   for(int pos=OrdersTotal()-1;pos>=0;pos--) {
      // выбрали ордер
      if (!OrderSelect(pos,SELECT_BY_POS,MODE_TRADES)) {
         Sleep(50);
         RefreshRates();
         if (!OrderSelect(pos,SELECT_BY_POS,MODE_TRADES)) {
            PrintFormat("OrderSelect() failed %d",GetLastError());
            return false;
         }
      }
      // только действующие рыночные
      if (OrderCloseTime()!=0) continue;
      int type=OrderType();
      if (type!=OP_BUY && type!=OP_SELL) continue;
      // только кроссы
      string symbol=OrderSymbol();
      string baseName=SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);
      string quoteName=SymbolInfoString(symbol,SYMBOL_CURRENCY_PROFIT);
      if (baseName==quoteName || baseName=="" || quoteName=="") continue;
      if (baseName=="USD" || quoteName=="USD") continue;
      
      CurrencyInfo *base=GetCurrency(baseName);
      CurrencyInfo *quote=GetCurrency(quoteName);
      if ( GetSymbol(base.name,"USD")==NULL || GetSymbol(quote.name,"USD")==NULL) {
         continue;
      }
      // закрываем
      PrintFormat("Close symbol=%s lots=%.3f direction=%s",symbol,OrderLots(),type==OP_BUY?"BUY":"SELL");
      if (!allowTrade) {
         profitOrders+=OrderProfit()+OrderSwap()+OrderCommission();
         countOrders++;
      }
      if (allowTrade) {
         if (!OrderClose(OrderTicket(),OrderLots(),OrderClosePrice(),5)) {
            Sleep(50);
            if (!OrderClose(OrderTicket(),OrderLots(),OrderClosePrice(),5)) {
               PrintFormat("OrderCLose() failed %d",GetLastError());
               continue;
            }
         }
         profitOrders+=OrderProfit()+OrderSwap()+OrderCommission();
         countOrders++;
      }
      // учитываем объём
      double contract=SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);
      if (contract<2) contract=10000;
      if (type==OP_BUY) {
         quote.volume+=OrderLots()*contract;
         base.volume+=OrderLots()*contract/OrderClosePrice();
      } else {
         quote.volume-=OrderLots()*contract;
         base.volume-=OrderLots()*contract/OrderClosePrice();
      }
   }
   PrintFormat("Total %d orders with profit %.2f",countOrders,profitOrders);
   // напечатаем объёмы по валютам
   for(int pos=ArraySize(Currencys)-1;pos>=0;pos--) {
      CurrencyInfo *cur=Currencys[pos];
      if (cur==NULL) continue;         // перестарховка
      if (cur.volume==0) continue;
      PrintFormat("Currency=%s volume=%f direction=%s",cur.name,cur.volume,cur.volume>0?"BUY":"SELL");
   }      
   // Открываем остатки на USD
   for(int pos=ArraySize(Currencys)-1;pos>=0;pos--) {
      CurrencyInfo *cur=Currencys[pos];
      if (cur==NULL) continue;         // перестарховка
      if (cur.volume==0) continue;
      // символ по которому открываться
      string symbol=GetSymbol(cur.name,"USD");  
      if (symbol=="") continue;
      double ask=SymbolInfoDouble(symbol,SYMBOL_ASK);
      double bid=SymbolInfoDouble(symbol,SYMBOL_BID);
      double contract=SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);
      // направление и объём в контрактах
      int type=OP_BUY;
      double lots=0;
      if (SymbolInfoString(symbol,SYMBOL_CURRENCY_PROFIT)=="USD") {
         // прямая котировка
         if (cur.volume>0) {
            type=OP_BUY;
            lots=cur.volume/ask/contract;
         } else {
            type=OP_SELL;
            lots=-cur.volume/bid/contract;
         }
      } else {
         // обратная котировка
         if (cur.volume>0) {
            type=OP_BUY;
            lots=cur.volume/contract;
         } else {
            type=OP_SELL;
            lots=-cur.volume/contract;
         }
      }
      PrintFormat("Open symbol=%s lots=%.2f direction=%s",symbol,lots,type==OP_BUY?"BUY":"SELL");
      if (lots<SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN)*0.8) {
         PrintFormat("Too small lots");
         continue;
      }
      if (allowTrade) {
         lots=NormalizeLots(lots,symbol);
         long ticket=OrderSend(symbol,type,lots,type==OP_BUY?ask:bid,5,0,0,"unroll",0);
         if (ticket<0) {
            PrintFormat("OrderSend() error %d",GetLastError());
         }
      }
   }
   return true;
}
double NormalizeLots(double lots,string symbol)
{
   double minLot=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
   double maxLot=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
   double lotStep=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
   if (lots<=minLot) return minLot;
   if (lots>=maxLot) return maxLot;
   lots=minLot+MathRound((lots-minLot)/lotStep)*lotStep;
   return lots;
}
void OnStart()
{
   UnRoll(ALLOW_TRADE);
   for(int pos=ArraySize(Currencys)-1;pos>=0;pos--) {
      if (Currencys[pos]!=NULL) delete Currencys[pos];
   }
}

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

скрипте совсем-совсем свежий, могут быть опечатки и/или ошибки

 

кстати да, ошибка есть и на скриншоте её видно. 

в скрипте где "обратная котировка" OP_BUY , OP_SELL надо поменять местами