Алгоритм как-то нейдёт:
- обнаруживать треугольники и циклы (когда куплено GBPUSD, продано GBPJPY и куплено USDJPY то толку мало - это треугольник/кольцо, толку мало, в основном своп течёт)
- закрывать с наименьшей погрешностью (посчитать объём к частичному закрытию, учитывая мин.лоты по парам)
пока дошёл до "считать валютную корзину" - например сколько по отдельности куплено и продано USD,GBP,JPY. Если одновременно есть много покупок и продаж, то это где-то локи и неэффективность.
Если таких валют 3 и больше - похоже что кольцо/цикл и надо от этого избавляться при первой возможности.
Исходные данные - многомерный массив, число измерений равно числу котируемых инструментов. Элементы массива - открытые позиции по соответствующим парам.
Алгоритм: берётся базовый инструмент и ищется цепочка, приводящая к нему же.
Алгоритм как-то нейдёт:
- обнаруживать треугольники и циклы (когда куплено GBPUSD, продано GBPJPY и куплено USDJPY то толку мало - это треугольник/кольцо, толку мало, в основном своп течёт)
- закрывать с наименьшей погрешностью (посчитать объём к частичному закрытию, учитывая мин.лоты по парам)
пока дошёл до "считать валютную корзину" - например сколько по отдельности куплено и продано USD,GBP,JPY. Если одновременно есть много покупок и продаж, то это где-то локи и неэффективность.
Если таких валют 3 и больше - похоже что кольцо/цикл и надо от этого избавляться при первой возможности.
Все позиции надо привести к стоимости в единой валюте (я к USD приводил). Далее определяется объем закрытия, он соответствует минимальному объему (в единой валюте, естественно) среди всех составляющих кольцо инструментов. Затем производится закрытие с пересчетом в лоты по каждому инструменту. Все эти пересчеты неизбежно приведут к погрешностям, которые тоже придется как-то учитывать, дабы мелкие открытые объемы не зависали на счёте.
про многомерный массив Алексей видимо чёго-то поторопился. Граф получается, в узлах валюты, на рёбрах пары. Как его в памяти представить третий вопрос, он небольшой 8 узлов всего.
конечно если торговать сразу через USD (вместо сделок по кроссу открывать 2 через доллар), то ситуация с циклами вырождается в отдельные локи по символам с которыми проще. Но двойной спред. Но возможно своп меньше и маржа (от встречных позиций). Если сделка живёт больше 2-3 ночей то наверное имеет смысл делать именно так.
пока склоняюсь к практическому решению - в 22-23 перед двойным свопом, переоткрывать кроссы через USD (то есть в приведённом примере закрыть GBPJPY и открыть максимально близкие GBPUSD,USDJPY), и вторым проходом по долларовым парам удалить полученные локи через CloseBy. Или вообще всю халабуду вечером закрыть, у утром переоткрыть только остатки через usd.
Это конечно просто и надёжно как топор, но так и спреды вставят конечно и с точки зрения математики и алгоритмов не то, должны быть более красивые решения.
Позиция по кроссу как правило более выгодна, нежели 2 позиции по мажорам. Что касается закрытие перед полночью и восстановлением через некоторое время - мне кажется, на спредах будет больше потерь, чем на свопах. Может быть в ночь на среду это оправдано, да под выходные.
не то чтобы более выгодна, более точна скоре. Если торговать мин.лотами то от кроссов никуда. А когда объём 10-20 минимальных лотов, то уже раскладывается с допустимой точностью. (это к теме "не торгуйте мин.лотами - лучше влить денег, на край увеличить плечо")
переоткрывать всё конечно не выход. Это одну-две позиции можно переоткрыть - закрыть и после 0:00 выставить лимитки на спред лучше закрытия. Как правило к 9, за азиатскую сессию, оно срабатывает. Не сработает, как-бы и не сильно обидно, редкая проблема.
а когда их 6-8 то почти гарантируется что хотя-бы одна лимитка несработает и проблема станет постоянной.
не то чтобы более выгодна, более точна скоре. Если торговать мин.лотами то от кроссов никуда. А когда объём 10-20 минимальных лотов, то уже раскладывается с допустимой точностью. (это к теме "не торгуйте мин.лотами - лучше влить денег, на край увеличить плечо")
переоткрывать всё конечно не выход. Это одну-две позиции можно переоткрыть - закрыть и после 0:00 выставить лимитки на спред лучше закрытия. Как правило к 9, за азиатскую сессию, оно срабатывает. Не сработает, как-бы и не сильно обидно, редкая проблема.
а когда их 6-8 то почти гарантируется что хотя-бы одна лимитка несработает и проблема станет постоянной.
Не понял фразу о том, что алгоритм как-то нейдёт.
скрипт по мотивам обсуждений,
переоткрывающий позиции кроссов эквивалентным объёмом через 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 надо поменять местами
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Алгоритм как-то нейдёт:
- обнаруживать треугольники и циклы (когда куплено GBPUSD, продано GBPJPY и куплено USDJPY то толку мало - это треугольник/кольцо, толку мало, в основном своп течёт)
- закрывать с наименьшей погрешностью (посчитать объём к частичному закрытию, учитывая мин.лоты по парам)
пока дошёл до "считать валютную корзину" - например сколько по отдельности куплено и продано USD,GBP,JPY. Если одновременно есть много покупок и продаж, то это где-то локи и неэффективность.
Если таких валют 3 и больше - похоже что кольцо/цикл и надо от этого избавляться при первой возможности.