← К каталогу
Исходный код · ID 4525

Pingala Prasthara EA

pingala_prasthara_ea_v1_1.mq5
Вы автор или правообладатель?

Сообщите нам, если считаете, что материал размещён без разрешения.

Краткий обзор

О программе

Pingala Prasthara EA — торговый советник для MetaTrader 5, реализующий стратегию на основе анализа ценовых паттернов Пингала. Советник работает на структуре двух таймфреймов: сигнальный M5 и подтверждающий M15. Он анализирует свечные паттерны, направленное давление, концентрацию Меру и уровни Матрамеру на основе числовых последовательностей.

Вход в позицию осуществляется при выполнении комплекса условий: наличие паттерна Пингала с достаточным количеством гуру-кандлов, совпадение с уровнями Фибоначчи Матрамеру, подтверждение на старшем таймфрейме и прохождение фильтров по направленному давлению. Советник поддерживает частичное закрытие позиции при достижении первой цели (TP1) с возможностью переноса стоп-лосса в безубыток.

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

Настройки программы

НастройкаОписание
MagicNumber Уникальный идентификатор советника. Используйте разные числа для отдельных настроек советника.
SignalTF Таймфрейм для формирования сигнала. По умолчанию M5. На этом таймфрейме анализируется паттерн Пингала и рассчитываются основные показатели.
ConfirmTF Таймфрейм подтверждения (старший по отношению к сигнальному). По умолчанию M15. Давление на этом таймфрейме должно совпадать с направлением сделки для подтверждения сигнала.
OnlyClosedBars При активации (true) советник торгует только по завершённым свечам. Рекомендуется включать для фильтрации текущей незавершённой свечи.
PatternLengthN Количество свечей в паттерне Пингала. По умолчанию 8. Определяет длину анализируемой последовательности для расчёта бинарного кода и баллов.
GuruThresholdATR Доля ATR, при которой свеча считается Гуру. Свеча становится Гуру, если её движение (по модулю) больше или равно указанной доле ATR. По умолчанию 0.50.
MinDirectionalPressure Минимальное направленное давление B. Значение 0.25 означает ±25%. Сигнал не проходит, если давление ниже этого порога.
MinMeruConcentration Минимальный балл концентрации Меру (в диапазоне 0–100). Сигнал не проходит, если балл ниже этого значения.
MinPingalaScore Минимальный общий балл Пингала (в диапазоне 0–100). Сигнал не проходит, если итоговый балл ниже этого порога.
RequireGuruExpansion Требует наличия недавнего паттерна расширения от Лагху к Гуру. Для покупки: поздние Гуру свечи должны иметь больше бычьих, для продажи — больше медвежьих.
ExpansionLookback Количество баров, проверяемых на наличие паттерна расширения от Лагху к Гуру. По умолчанию 4.
RequireMatrameruAlign При активации требует совпадения показателя S с ближайшим уровнем Фибоначчи Матрамеру или движения к следующему уровню.
MatrameruMaxDistance Максимальное расстояние от ближайшего уровня Матрамеру (в единицах показателя S), при котором считается, что позиция находится вблизи уровня Фибоначчи.
AllowMovingTowardNext Разрешает принимать состояние, при котором текущий показатель S движется к следующему уровню Фибоначчи Матрамеру. Детерминистическая интерпретация: расстояние до следующего уровня Фибоначчи сокращается по сравнению с предыдущим окном.
RequireHigherTFConfirm При активации требует, чтобы давление на таймфрейме ConfirmTF совпадало с направлением сделки (бычье для покупки, медвежье для продажи).
ATRPeriod Период расчёта индикатора ATR (среднекратический диапазон). Используется для оценки волатильности при формировании сигналов и расчёте стоп-лосса.
MinATR Минимальное значение ATR в единицах цены. Если 0 — ограничение отключено. Сделки не открываются при значении ATR ниже этого порога.
MaxATR Максимальное значение ATR в единицах цены. Если 0 — ограничение отключено. Сделки не открываются при превышении этого порога волатильности.
MaxSpreadPoints Максимальный спред в точках брокера (POINTS). Если 0 — ограничение отключено. Сделки не открываются при превышении спреда.
StopATRMultiplier Множитель ATR для расчёта базового стоп-лосса. По умолчанию 1.20. Стоп-лосс рассчитывается как вход ± множитель × ATR.
SwingLookback Количество баров, используемых для определения защитного свечного максимума/минимума (swing high/low), который используется при расчёте стоп-лосса.
UseMinimumSL При активации применяется минимальное расстояние стоп-лосса, зависящее от типа инструмента: пипсы для форекса, цена для золота, цена для прочих символов.
MinimumSLForexPips Минимальное расстояние стоп-лосса для валютных пар (5- и 3-значные котировки), указываемое в пунктах (пипсах). По умолчанию 5.0 = 5 пипсов.
MinimumSLGoldPrice Минимальное расстояние стоп-лосса для символов XAU (золото), указываемое в единицах цены. 0 — отключено.
MinimumSLOtherPrice Минимальное расстояние стоп-лосса для прочих символов, указываемое в единицах цены. 0 — отключено.
ScaleTargetsWithMinSL Если минимальный стоп-лосс расширяет стоп, то цели TP1 и TP2 также расширяются на тот же коэффициент, сохраняя исходную геометрию вознаграждения/риска.
TP2Multiplier Множитель ATR, применяемый к соотношению Пингала для расчёта второй цели (TP2). По умолчанию 1.60.
TP1ClosePercent Процент позиции, закрываемый при достижении первой цели (TP1). По умолчанию 50.0%.
MoveSLToBEAfterTP1 При активации после достижения TP1 происходит частичное закрытие позиции и перенос стоп-лосса оставшейся части в безубыток.
BreakEvenPlusPoints Дополнительные точки брокера (POINTS) сверх точки входа, добавляемые при переносе стоп-лосса в безубыток после достижения TP1.
ExitOnPressureFlip При активации советник закрывает позицию, если направленное давление развернулось против позиции на требуемое количество закрытых баров.
PressureFlipBars Количество последовательных закрытых баров, необходимых для подтверждения разворота направленного давления против позиции при активированном ExitOnPressureFlip.
ExitOnOppositeSignal При активации советник закрывает позицию при формировании полного высокодостоверного противоположного сигнала Пингала.
UseRiskPercent При активации (true) объём лота рассчитывается автоматически на основе риска в процентах от баланса и расстояния стоп-лосса. При деактивации (false) используется фиксированный лот FixedLot.
RiskPerTradePercent Процент баланса счёта, рискуемый на одну сделку. Используется для автоматического расчёта объёма лота при активированном UseRiskPercent.
FixedLot Фиксированный размер лота, используемый только когда параметр UseRiskPercent отключён (false).
MaxDailyLossPercent Процент от баланса счёта. При достижении указанной реализованной убыли за день новые входы блокируются. 0 — ограничение отключено.
MaxConsecutiveLosses Максимальное количество последовательных убытков. При достижении этого числа новые входы блокируются. 0 — ограничение отключено.
MaxOpenPositions Максимальное количество одновременных открытых позиций для данного символа и магического числа.
UseSessionFilter При активации (true) советник торгует только в указанный временной интервал (с SessionStartHour до SessionEndHour) по серверному времени брокера.
SessionStartHour Час начала торговой сессии в серверном времени брокера (0–23). Работает при активированном UseSessionFilter.
SessionEndHour Час окончания торговой сессии в серверном времени брокера (0–23). Работает при активированном UseSessionFilter.
PrintDebug При активации выводятся в журнал (Journal) подробности сигналов, фильтров, стоп-лосса/тейков и торговых операций для отладки.
Получить полный код

Поиск по коду файла

#property strict
#property version   "1.10"
#property description "Pingala Prasthara strategy v1.10: M5/M15 structure, symbol-aware minimum SL, clearer inputs and diagnostics."

#include <Trade/Trade.mqh>
CTrade trade;

input group "1. GENERAL / TIMEFRAMES"
input ulong            MagicNumber              = 26090401;   // Unique EA ID - use different number for separate EA setups
input ENUM_TIMEFRAMES  SignalTF                 = PERIOD_M5;   // Signal timeframe - strategy default M5
input ENUM_TIMEFRAMES  ConfirmTF                = PERIOD_M15;  // Higher timeframe confirmation - strategy default M15
input bool             OnlyClosedBars           = true;       // true = trade only completed candles (recommended)
input int              PatternLengthN           = 8;          // Number of candles in Pingala pattern - default 8
input double           GuruThresholdATR         = 0.50;       // Candle becomes Guru when move >= this fraction of ATR

input group "2. ENTRY FILTERS / SIGNAL QUALITY"
input double           MinDirectionalPressure   = 0.25;       // Minimum directional pressure B; 0.25 = +/-25%
input double           MinMeruConcentration     = 65.0;       // Minimum Meru concentration score (0-100)
input double           MinPingalaScore          = 75.0;       // Minimum total Pingala score (0-100)
input bool             RequireGuruExpansion     = true;       // Require recent Laghu-to-Guru expansion pattern
input int              ExpansionLookback        = 4;          // Candles checked for expansion; default 4
input bool             RequireMatrameruAlign    = true;       // Require Matrameru/Fibonacci state alignment
input double           MatrameruMaxDistance     = 1.0;        // Maximum distance from nearest Matrameru level
input bool             AllowMovingTowardNext    = true;       // Also accept state moving toward next Matrameru level
input bool             RequireHigherTFConfirm   = true;       // Require ConfirmTF pressure in same trade direction

input group "3. ATR / VOLATILITY / SPREAD"
input int              ATRPeriod                = 14;         // ATR calculation period
input double           MinATR                   = 0.0;        // Minimum ATR in PRICE UNITS; 0 = disabled
input double           MaxATR                   = 0.0;        // Maximum ATR in PRICE UNITS; 0 = disabled
input int              MaxSpreadPoints          = 30;         // Maximum spread in broker POINTS; 0 = disabled

input group "4. STOP LOSS / TARGETS"
input double           StopATRMultiplier        = 1.20;       // Base ATR stop multiplier; strategy default 1.20
input int              SwingLookback            = 5;          // Bars used to find protective swing high/low
input bool             UseMinimumSL              = true;       // Enforce a minimum stop distance by symbol type
input double           MinimumSLForexPips        = 5.0;        // 5/3-digit forex: minimum SL in PIPS (5.0 = 5 pips)
input double           MinimumSLGoldPrice        = 0.0;        // XAU symbols: minimum SL in PRICE ($); 0 = disabled
input double           MinimumSLOtherPrice       = 0.0;        // Other symbols: minimum SL in PRICE units; 0 = disabled
input bool             ScaleTargetsWithMinSL     = true;       // If minimum SL widens stop, widen TP1/TP2 by same factor
input double           TP2Multiplier            = 1.60;       // TP2 ATR multiplier applied to Pingala ratio
input double           TP1ClosePercent          = 50.0;       // Percent of position closed when TP1 is reached
input bool             MoveSLToBEAfterTP1       = true;       // Move remaining position stop to breakeven after TP1
input int              BreakEvenPlusPoints      = 0;          // Extra broker POINTS beyond entry when moving to BE
input bool             ExitOnPressureFlip       = true;       // Exit if directional pressure flips against position
input int              PressureFlipBars         = 2;          // Consecutive closed bars required for pressure-flip exit
input bool             ExitOnOppositeSignal     = true;       // Exit on a full opposite high-confidence Pingala signal

input group "5. MONEY / RISK MANAGEMENT"
input bool             UseRiskPercent           = true;       // true = auto lot from SL risk; false = use FixedLot
input double           RiskPerTradePercent      = 0.50;       // Account balance risk per trade in percent
input double           FixedLot                 = 0.10;       // Lot size used only when UseRiskPercent=false
input double           MaxDailyLossPercent      = 2.00;       // Stop new entries after this realized daily loss; 0=off
input int              MaxConsecutiveLosses     = 3;          // Stop new entries after this many losses; 0=off
input int              MaxOpenPositions         = 1;          // Maximum simultaneous positions for this symbol/magic

input group "6. TRADING SESSION (BROKER SERVER TIME)"
input bool             UseSessionFilter         = false;      // true = only trade between start/end hours below
input int              SessionStartHour         = 8;          // Session start hour, broker/server time (0-23)
input int              SessionEndHour           = 18;         // Session end hour, broker/server time (0-23)

input group "7. DEBUG / JOURNAL LOGGING"
input bool             PrintDebug               = true;       // Print signal, filter, SL/TP and trade details to Journal

struct PingalaData
{
   bool   valid;
   int    N;
   int    guruCount;
   int    decimalValue;
   int    rank;
   int    matraS;
   int    nearestF;
   int    nextF1;
   int    nextF2;
   double ratio1;
   double ratio2;
   double pressureB;
   double meruScore;
   double directionalStrength;
   double matrameruAlignment;
   double patternStrength;
   double higherTFScore;
   double pingalaScore;
   bool   nastaValid;
   bool   matrameruAligned;
   bool   guruExpansionBuy;
   bool   guruExpansionSell;
};

datetime g_lastSignalBar = 0;
ulong    g_managedTicket  = 0;
double   g_initialVolume  = 0.0;
bool     g_tp1Done        = false;

void Dbg(string s)
{
   if(PrintDebug) Print("PINGALA: ", s);
}

int FibLevels[] = {1,2,3,5,8,13,21,34,55,89,144};

int FindNearestFib(const int S)
{
   int best=FibLevels[0];
   int bestDist=MathAbs(S-best);
   for(int i=1;i<ArraySize(FibLevels);i++)
   {
      int d=MathAbs(S-FibLevels[i]);
      if(d<bestDist)
      {
         best=FibLevels[i];
         bestDist=d;
      }
   }
   return best;
}

void GetFibTriplet(const int S,int &F,int &F1,int &F2)
{
   F=FindNearestFib(S);
   int idx=0;
   for(int i=0;i<ArraySize(FibLevels);i++)
      if(FibLevels[i]==F){ idx=i; break; }

   F1 = FibLevels[MathMin(idx+1,ArraySize(FibLevels)-1)];
   F2 = FibLevels[MathMin(idx+2,ArraySize(FibLevels)-1)];
}

double Combination(const int n,const int k)
{
   if(k<0 || k>n) return 0.0;
   int kk=MathMin(k,n-k);
   double r=1.0;
   for(int i=1;i<=kk;i++)
      r = r * (n-kk+i) / i;
   return r;
}

double GetATR(ENUM_TIMEFRAMES tf,int shift)
{
   int h=iATR(_Symbol,tf,ATRPeriod);
   if(h==INVALID_HANDLE) return 0.0;
   double b[];
   ArraySetAsSeries(b,true);
   double v=0.0;
   if(CopyBuffer(h,0,shift,1,b)==1) v=b[0];
   IndicatorRelease(h);
   return v;
}

bool IsGoldSymbol()
{
   string s=_Symbol;
   StringToUpper(s);
   return (StringFind(s,"XAU")>=0);
}

double ForexPipSize()
{
   int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
   if(digits==5 || digits==3) return 10.0*_Point;
   return _Point;
}

double MinimumSLDistancePrice()
{
   if(!UseMinimumSL) return 0.0;

   if(IsGoldSymbol())
      return MathMax(0.0,MinimumSLGoldPrice);

   int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
   if(digits==5 || digits==3)
      return MathMax(0.0,MinimumSLForexPips)*ForexPipSize();

   return MathMax(0.0,MinimumSLOtherPrice);
}

double NormalizePrice(double p)
{
   return NormalizeDouble(p,(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS));
}

double NormalizeVolume(double lots)
{
   double minv=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   double maxv=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   if(step<=0.0) step=minv;
   lots=MathMax(minv,MathMin(maxv,lots));
   lots=MathFloor(lots/step + 1e-9)*step;
   int vd=2;
   if(step>=1.0) vd=0;
   else if(step>=0.1) vd=1;
   else if(step>=0.01) vd=2;
   else vd=3;
   return NormalizeDouble(lots,vd);
}

bool InSession()
{
   if(!UseSessionFilter) return true;
   MqlDateTime dt; TimeToStruct(TimeCurrent(),dt);
   if(SessionStartHour==SessionEndHour) return true;
   if(SessionStartHour<SessionEndHour)
      return (dt.hour>=SessionStartHour && dt.hour<SessionEndHour);
   return (dt.hour>=SessionStartHour || dt.hour<SessionEndHour);
}

int CurrentSpreadPoints()
{
   MqlTick t;
   if(!SymbolInfoTick(_Symbol,t)) return 999999;
   return (int)MathRound((t.ask-t.bid)/_Point);
}

int CountOurOpenPositions()
{
   int count=0;
   for(int i=PositionsTotal()-1;i>=0;i--)
   {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0) continue;
      if(!PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL)==_Symbol &&
         (ulong)PositionGetInteger(POSITION_MAGIC)==MagicNumber)
         count++;
   }
   return count;
}

bool SelectOurPosition(ulong &ticket)
{
   for(int i=PositionsTotal()-1;i>=0;i--)
   {
      ulong t=PositionGetTicket(i);
      if(t==0 || !PositionSelectByTicket(t)) continue;
      if(PositionGetString(POSITION_SYMBOL)==_Symbol &&
         (ulong)PositionGetInteger(POSITION_MAGIC)==MagicNumber)
      {
         ticket=t;
         return true;
      }
   }
   return false;
}

double TodayProfit(int &consecutiveLosses)
{
   consecutiveLosses=0;
   MqlDateTime dt; TimeToStruct(TimeCurrent(),dt);
   dt.hour=0; dt.min=0; dt.sec=0;
   datetime dayStart=StructToTime(dt);
   if(!HistorySelect(dayStart,TimeCurrent())) return 0.0;

   double pnl=0.0;
   int deals=HistoryDealsTotal();

   for(int i=0;i<deals;i++)
   {
      ulong d=HistoryDealGetTicket(i);
      if(d==0) continue;
      if(HistoryDealGetString(d,DEAL_SYMBOL)!=_Symbol) continue;
      if((ulong)HistoryDealGetInteger(d,DEAL_MAGIC)!=MagicNumber) continue;
      if((ENUM_DEAL_ENTRY)HistoryDealGetInteger(d,DEAL_ENTRY)!=DEAL_ENTRY_OUT) continue;
      pnl += HistoryDealGetDouble(d,DEAL_PROFIT)
           + HistoryDealGetDouble(d,DEAL_SWAP)
           + HistoryDealGetDouble(d,DEAL_COMMISSION);
   }

   for(int i=deals-1;i>=0;i--)
   {
      ulong d=HistoryDealGetTicket(i);
      if(d==0) continue;
      if(HistoryDealGetString(d,DEAL_SYMBOL)!=_Symbol) continue;
      if((ulong)HistoryDealGetInteger(d,DEAL_MAGIC)!=MagicNumber) continue;
      if((ENUM_DEAL_ENTRY)HistoryDealGetInteger(d,DEAL_ENTRY)!=DEAL_ENTRY_OUT) continue;
      double p=HistoryDealGetDouble(d,DEAL_PROFIT)
              +HistoryDealGetDouble(d,DEAL_SWAP)
              +HistoryDealGetDouble(d,DEAL_COMMISSION);
      if(p<0.0) consecutiveLosses++;
      else if(p>0.0) break;
   }
   return pnl;
}

bool RiskLimitsOK()
{
   int consec=0;
   double pnl=TodayProfit(consec);
   double bal=AccountInfoDouble(ACCOUNT_BALANCE);
   if(MaxDailyLossPercent>0.0 && pnl <= -(bal*MaxDailyLossPercent/100.0))
   {
      Dbg(StringFormat("BLOCK daily loss: %.2f",pnl));
      return false;
   }
   if(MaxConsecutiveLosses>0 && consec>=MaxConsecutiveLosses)
   {
      Dbg(StringFormat("BLOCK consecutive losses: %d",consec));
      return false;
   }
   return true;
}

bool BuildPingalaData(ENUM_TIMEFRAMES tf,int startShift,PingalaData &pd)
{
   pd.valid=false;
   int N=MathMax(2,MathMin(PatternLengthN,16));
   pd.N=N;

   MqlRates rates[];
   ArraySetAsSeries(rates,true);

   if(CopyRates(_Symbol,tf,startShift,N+1,rates)<N+1)
      return false;

   int atrHandle=iATR(_Symbol,tf,ATRPeriod);
   if(atrHandle==INVALID_HANDLE) return false;
   double atr[];
   ArraySetAsSeries(atr,true);
   if(CopyBuffer(atrHandle,0,startShift,N+1,atr)<N+1)
   {
      IndicatorRelease(atrHandle);
      return false;
   }
   IndicatorRelease(atrHandle);

   int state[]; ArrayResize(state,N);       // 0=L,1=G
   int dir[];   ArrayResize(dir,N);         // -1,0,+1
   int weight[];ArrayResize(weight,N);      // 1 or 2

   int guru=0, S=0, bull=0, bear=0;
   int dec=0;

   for(int j=0;j<N;j++)
   {
      int i=N-1-j; // rates index: oldest selected candle toward newest
      double change=rates[i].close-rates[i+1].close;
      double a=atr[i];
      if(a<=0.0) return false;
      double M=MathAbs(change)/a;
      state[j]=(M>=GuruThresholdATR ? 1:0);
      weight[j]=(state[j]==1 ? 2:1);
      dir[j]=(change>0.0 ? 1 : (change<0.0 ? -1:0));
      if(state[j]==1) guru++;
      S+=weight[j];
      if(dir[j]>0) bull+=weight[j];
      if(dir[j]<0) bear+=weight[j];
      dec=(dec<<1)|state[j];
   }

   pd.guruCount=guru;
   pd.matraS=S;
   pd.decimalValue=dec;
   pd.rank=(1<<N)-dec; // one-based ordering per infographic

   int reconstructed=(1<<N)-pd.rank;
   pd.nastaValid=(reconstructed==dec);

   double ck=Combination(N,guru);
   double cmax=0.0;
   for(int k=0;k<=N;k++) cmax=MathMax(cmax,Combination(N,k));
   pd.meruScore=(cmax>0.0 ? (1.0-ck/cmax)*100.0 : 0.0);
   pd.meruScore=MathMax(0.0,MathMin(100.0,pd.meruScore));

   int denom=bull+bear;
   pd.pressureB=(denom>0 ? (double)(bull-bear)/(double)denom : 0.0);
   pd.directionalStrength=MathAbs(pd.pressureB)*100.0;

   GetFibTriplet(S,pd.nearestF,pd.nextF1,pd.nextF2);
   pd.ratio1=(pd.nearestF>0 ? (double)pd.nextF1/(double)pd.nearestF : 1.0);
   pd.ratio2=(pd.nearestF>0 ? (double)pd.nextF2/(double)pd.nearestF : 1.0);
   double dist=MathAbs((double)S-(double)pd.nearestF);
   bool near=(dist<=MatrameruMaxDistance);

   bool moving=false;
   if(AllowMovingTowardNext)
   {
      MqlRates prevRates[];
      ArraySetAsSeries(prevRates,true);
      if(CopyRates(_Symbol,tf,startShift+1,N+1,prevRates)>=N+1)
      {
         int prevS=0;
         int prevAtrHandle=iATR(_Symbol,tf,ATRPeriod);
         double prevAtr[]; ArraySetAsSeries(prevAtr,true);
         if(prevAtrHandle!=INVALID_HANDLE && CopyBuffer(prevAtrHandle,0,startShift+1,N+1,prevAtr)>=N+1)
         {
            for(int j=0;j<N;j++)
            {
               int i=N-1-j;
               double ch=prevRates[i].close-prevRates[i+1].close;
               double aa=prevAtr[i];
               int st=(aa>0.0 && MathAbs(ch)/aa>=GuruThresholdATR ? 1:0);
               p

Показано первые 50% кода (символов: 22488)

Открыть по тарифу

Купите тариф с пакетом открытий или войдите, если тариф уже есть. Открытый файл останется в вашем кабинете навсегда.

Разовая покупка файла

На этой странице откроется полный исходный код файла вместо ознакомительного фрагмента.

Чтобы оформить заказ, введите email и перейдите к оплате.

Стоимость полного кода
2 248,80₽ ≈ $27.49