Larry FX
Похожие материалы по тегам
Сообщите нам, если считаете, что материал размещён без разрешения.
Сообщить о материале
Обращение по товару «larry fx.mq5» отправится на рассмотрение. Укажите контакт для ответа и опишите причину.
О программе
Larry FX — торговый советник для MetaTrader 5, реализующий сеточную стратегию с использованием отложенных ордеров BUYSTOP и SELLSTOP. Советник размещает сетку ордеров в обе стороны, увеличивая объём лота по мартингейл-множителю при достижении новых уровней. Поддерживается трейлинг стопа в нескольких режимах (по свечам, фракталам, в пипах), а также фильтр по времени торговли.
Советник отслеживает статистику по открытым позициям и отложенным ордерам, отображает информационную панель с данными о балансе, эквити, профите/убытке по направлениям и таблицей P&L за последние 5 торговых дней. Включены граничные кнопки для ручного закрытия позиций (все, текущие, длинные, короткие, по профиту, по убытку).
В работе используются торговые API MetaTrader 5 (CTrade) для размещения и управления ордерами. Советник требует хеджинговый режим счёта. Параметры сетки, лотов, трейлинга и управления рисками настраиваются через входные параметры.
Настройки программы
| Настройка | Описание |
|---|---|
| InpEnableBuy | Включение или отключение сетки BUYSTOP |
| InpEnableSell | Включение или отключение сетки SELLSTOP |
| InpFirstOrder | Разрешение размещения первого ордера в сетке |
| InpOpenTrend | Размещение ордеров сетки в направлении тренда |
| InpFirstStep | Расстояние от текущей цены до первого ордера в сетке (в пунктах) |
| InpMinDistance | Минимальное расстояние между уровнями сетки (в пунктах) |
| InpStep | Шаг сетки — расстояние между ордерами (в пунктах) |
| InpMinDistance1 | Минимальное расстояние между уровнями альтернативной сетки (при превышении InpMaxOrderCount) |
| InpStep1 | Шаг альтернативной сетки — расстояние между ордерами при превышении лимита (в пунктах) |
| InpMaxOrderCount | Максимальное количество ордеров, после которого используются альтернативные параметры сетки (MinDistance1 и Step1) |
| InpStepTrailOrders | Шаг перемещения цены отложенных ордеров при трейлинге |
| InpMaxLoss | Уровень убытка, при превышении которого блокируется размещение новых ордеров |
| InpMaxLossCloseAll | Порог убытка для отображения иконки предупреждения (только визуальный индикатор, не влияет на закрытие) |
| InpBaseLot | Базовый объём лота для первого ордера в сетке |
| InpPlusLot | Дополнительный объём лота, добавляемый к каждому уровню сетки |
| InpLotMultiplier | Множитель увеличения лота на каждом уровне сетки (мартингейл) |
| InpLotDigits | Количество знаков после запятой при округлении объёма лота |
| InpCloseAll | Уровень общего профита, при достижении которого закрываются все позиции |
| InpStopProfit | Тейк-профит по каждому направлению (закрытие при достижении профита) |
| InpStopLoss | Стоп-лосс по каждому направлению (закрытие при достижении убытка) |
| InpMagic | Магический номер для идентификации ордеров и позиций советника |
| InpFontSize | Размер шрифта для текстовых меток на графике |
| InpTextColor | Цвет текста информационных меток на графике |
| InpTrailingMode | Режим трейлинга стопа (0 — выключен, 1 — по свечам, 2 — по фракталам, более 2 — в пипах) |
| InpTrailingStop | Включение и режим трейлинга стоп-лосса для открытых позиций |
| InpTrailingStep | Шаг смещения стоп-лосса при трейлинге открытых позиций |
| InpMinProfit | Минимальный профит на позиции, при котором трейлинг стопа становится активным |
| InpDelta | Смещение дельты при расчёте уровня трейлинга |
| InpTrailTF | Таймфрейм, используемый для расчёта уровня трейлинга |
| InpTimeMode | Режим работы временного фильтра |
| InpUseTimeFilter | Включение или отключение фильтра по времени торговли |
| InpStartHour | Начальный час для фильтра времени торговли |
| InpEndHour | Конечный час для фильтра времени торговли |
| InpShowStatistics | Отображение информационной панели со статистикой на графике |
| InpShowButtons | Отображение граничных кнопок управления на графике |
Поиск по коду файла
#property version "1.00"
#include <Trade\Trade.mqh>
input bool InpEnableBuy = true; // Enable Buy (BUYSTOP) grid
input bool InpEnableSell = true; // Enable Sell (SELLSTOP) grid
input bool InpFirstOrder = true; // Allow first order
input bool InpOpenTrend = false; // Trend-following placement
input int InpFirstStep = 50; // First order distance (points)
input int InpMinDistance = 80; // Min distance between grid levels
input int InpStep = 80; // Grid step distance
input int InpMinDistance1 = 30; // Min distance altitude levels
input int InpStep1 = 80; // Grid step altitude levels
input int InpMaxOrderCount = 999999; // Max orders before using alt params
input int InpStepTrailOrders = 5; // Pending order trailing step
input double InpMaxLoss = 100000.0; // Max loss before warning
input double InpMaxLossCloseAll = 5.0; // Warning icon threshold (display only)
input double InpBaseLot = 0.01; // Base lot size
input double InpPlusLot = 0.0; // Extra lot per level
input double InpLotMultiplier = 1.3; // Lot multiplier (martingale)
input int InpLotDigits = 2; // Lot rounding digits
input double InpCloseAll = 100000.0; // Total profit -> close all
input double InpStopProfit = 30.0; // Take profit per direction
input double InpStopLoss = 10000000.0;// Stop loss per direction
input int InpMagic = 666888; // Magic number
input int InpFontSize = 10; // Font size
input color InpTextColor = clrLime; // Text color
input string InpTrailingMode = "0-off 1-Candle 2-Fractals >2-pips"; // Trailing mode
input int InpTrailingStop = 1; // Trailing stop mode
input int InpTrailingStep = 0; // Trailing step
input int InpMinProfit = 10; // Minimum profit to trail
input int InpDelta = 0; // Delta offset
input int InpTrailTF = 0; // Trailing timeframe
input int InpKey = 0; // Key (unused)
input string InpTimeMode = "time"; // Time mode
input bool InpUseTimeFilter = false; // Enable time filter
input int InpStartHour = 1; // Start hour
input int InpEndHour = 20; // End hour
input bool InpShowStatistics = true; // Show meeting statistics panel
input bool InpShowButtons = true; // Show boundary buttons
CTrade trade;
int g_StopLevel = 0;
int g_Slippage = 3;
int g_CurMinDist = 30;
int g_CurStep = 30;
int g_Cur1MinDist = 30;
int g_Cur1Step = 30;
ENUM_TIMEFRAMES g_TrailTF = PERIOD_CURRENT;
int g_Step = 0;
int g_FirstStep = 0;
int g_FractalHandle = INVALID_HANDLE;
int g_PanelX = 10;
int g_PanelY = 10;
int g_PanelW = 360;
int g_PanelH = 400;
int g_BuyCount = 0;
int g_SellCount = 0;
int g_BuyStopCount = 0;
int g_SellStopCount = 0;
double g_BuyLots = 0.0;
double g_SellLots = 0.0;
double g_BuyProfit = 0.0;
double g_SellProfit= 0.0;
double g_BuyAvgPrice = 0.0;
double g_SellAvgPrice = 0.0;
double g_BuyMaxOpen = 0.0;
double g_BuyMinOpen = 0.0;
double g_SellMaxOpen = 0.0;
double g_SellMinOpen = 0.0;
ulong g_BuyStopTicket = 0;
ulong g_SellStopTicket = 0;
double g_BuyStopPrice = 0.0;
double g_SellStopPrice = 0.0;
double g_DayProfit[5];
double g_DayLots[5];
double g_DayOrderCount[5];
datetime g_DayStart[5];
datetime g_DayEnd[5];
double g_TotalPositionPF = 0.0;
double g_MinTotalPF = 0.0;
string g_btnCloseAll = "LFX_CloseAll";
string g_btnCloseCur = "LFX_CloseCur";
string g_btnCloseLongs = "LFX_CloseLongs";
string g_btnCloseShorts = "LFX_CloseShorts";
string g_btnCloseProfit = "LFX_CloseProfit";
string g_btnCloseLoss = "LFX_CloseLoss";
double CurrentBid() { return SymbolInfoDouble(_Symbol, SYMBOL_BID); }
double CurrentAsk() { return SymbolInfoDouble(_Symbol, SYMBOL_ASK); }
int DayOfWeekOf(datetime t)
{
MqlDateTime st;
TimeToStruct(t, st);
return st.day_of_week;
}
int MonthOf(datetime t)
{
MqlDateTime st;
TimeToStruct(t, st);
return st.mon;
}
int DayOf(datetime t)
{
MqlDateTime st;
TimeToStruct(t, st);
return st.day;
}
int YearOf(datetime t)
{
MqlDateTime st;
TimeToStruct(t, st);
return st.year;
}
int CurrentHour()
{
MqlDateTime st;
TimeToStruct(TimeCurrent(), st);
return st.hour;
}
ENUM_TIMEFRAMES NormalizeTimeframe(int tf)
{
if (tf == 0) return PERIOD_CURRENT;
if (tf > 43200) return PERIOD_CURRENT;
if (tf > 10080) return PERIOD_MN1;
if (tf > 1440) return PERIOD_W1;
if (tf > 240) return PERIOD_D1;
if (tf > 60) return PERIOD_H4;
if (tf > 30) return PERIOD_H1;
if (tf > 15) return PERIOD_M30;
if (tf > 5) return PERIOD_M15;
if (tf > 1) return PERIOD_M5;
if (tf == 1) return PERIOD_M1;
return PERIOD_CURRENT;
}
datetime TradingDayStart(int daysAgo)
{
datetime day = TimeCurrent();
string dstr = TimeToString(day, TIME_DATE);
day = StringToTime(dstr);
while (DayOfWeekOf(day) < 1 || DayOfWeekOf(day) > 5)
day -= 86400;
for (int i = 0; i < daysAgo; i++)
{
day -= 86400;
while (DayOfWeekOf(day) < 1 || DayOfWeekOf(day) > 5)
day -= 86400;
}
return day;
}
datetime CurrentWeekStart()
{
datetime day = TimeCurrent();
string dstr = TimeToString(day, TIME_DATE);
day = StringToTime(dstr);
while (DayOfWeekOf(day) != 1)
day -= 86400;
return day;
}
double CalcRangeProfit(datetime from, datetime to, double &lots)
{
double profit = 0.0;
lots = 0.0;
if (!HistorySelect(from, to))
return 0.0;
for (int i = HistoryDealsTotal() - 1; i >= 0; i--)
{
ulong ticket = HistoryDealGetTicket(i);
if (ticket == 0) continue;
if (HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol) continue;
if (HistoryDealGetInteger(ticket, DEAL_MAGIC) != InpMagic) continue;
long dtype = HistoryDealGetInteger(ticket, DEAL_TYPE);
if (dtype != DEAL_TYPE_BUY && dtype != DEAL_TYPE_SELL) continue;
long entry = HistoryDealGetInteger(ticket, DEAL_ENTRY);
if (entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_OUT_BY && entry != DEAL_ENTRY_INOUT)
continue;
datetime ctime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
if (ctime >= from && ctime < to)
{
profit += HistoryDealGetDouble(ticket, DEAL_PROFIT) +
HistoryDealGetDouble(ticket, DEAL_SWAP) +
HistoryDealGetDouble(ticket, DEAL_COMMISSION);
lots += HistoryDealGetDouble(ticket, DEAL_VOLUME);
}
}
return profit;
}
int OnInit()
{
// код доступен только после оплаты
}
void CreateLabel(string name, int x, int y)
{
if (ObjectFind(0, name) < 0)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
}
}
void OnDeinit(const int reason)
{
EventKillTimer();
if (g_FractalHandle != INVALID_HANDLE)
IndicatorRelease(g_FractalHandle);
ObjectsDeleteAll(0, -1);
}
void OnTimer()
{
if (InpShowStatistics)
UpdateDisplay();
}
void OnTick()
{
// код доступен только после оплаты
}
void CollectOrderStats()
{
g_BuyCount = 0;
g_SellCount = 0;
g_BuyStopCount = 0;
g_SellStopCount = 0;
g_BuyLots = 0.0;
g_SellLots = 0.0;
g_BuyProfit = 0.0;
g_SellProfit = 0.0;
g_BuyAvgPrice = 0.0;
g_SellAvgPrice = 0.0;
g_BuyMaxOpen = 0.0;
g_BuyMinOpen = 0.0;
g_SellMaxOpen = 0.0;
g_SellMinOpen = 0.0;
g_BuyStopTicket = 0;
g_SellStopTicket = 0;
g_BuyStopPrice = 0.0;
g_SellStopPrice = 0.0;
double buyCost = 0.0;
double sellCost = 0.0;
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if (ticket == 0) continue;
if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if (PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;
long type = PositionGetInteger(POSITION_TYPE);
double open = PositionGetDouble(POSITION_PRICE_OPEN);
double lots = PositionGetDouble(POSITION_VOLUME);
double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
if (type == POSITION_TYPE_BUY)
{
g_BuyCount++;
g_BuyLots += lots;
buyCost += open * lots;
g_BuyProfit += profit;
if (g_BuyMaxOpen == 0 || open > g_BuyMaxOpen) g_BuyMaxOpen = open;
if (g_BuyMinOpen == 0 || open < g_BuyMinOpen) g_BuyMinOpen = open;
}
else if (type == POSITION_TYPE_SELL)
{
g_SellCount++;
g_SellLots += lots;
sellCost += open * lots;
g_SellProfit += profit;
if (g_SellMaxOpen == 0 || open > g_SellMaxOpen) g_SellMaxOpen = open;
if (g_SellMinOpen == 0 || open < g_SellMinOpen) g_SellMinOpen = open;
}
}
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong ticket = OrderGetTicket(i);
if (ticket == 0) continue;
if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
if (OrderGetInteger(ORDER_MAGIC) != InpMagic) continue;
long type = OrderGetInteger(ORDER_TYPE);
double open = OrderGetDouble(ORDER_PRICE_OPEN);
if (type == ORDER_TYPE_BUY_STOP)
{
g_BuyStopCount++;
if (g_BuyStopPrice == 0 || open < g_BuyStopPrice)
g_BuyStopPrice = open;
g_BuyStopTicket = ticket;
}
else if (type == ORDER_TYPE_SELL_STOP)
{
g_SellStopCount++;
if (g_SellStopPrice == 0 || open > g_SellStopPrice)
g_SellStopPrice = open;
g_SellStopTicket = ticket;
}
}
if (g_BuyLots > 0) g_BuyAvgPrice = buyCost / g_BuyLots;
if (g_SellLots > 0) g_SellAvgPrice = sellCost / g_SellLots;
g_TotalPositionPF = g_BuyProfit + g_SellProfit;
if (g_TotalPositionPF < g_MinTotalPF)
g_MinTotalPF = g_TotalPositionPF;
}
void DrawAverageMarkers()
{
ObjectDelete(0, "SLb");
ObjectDelete(0, "SLs");
datetime t0 = iTime(_Symbol, PERIOD_CURRENT, 0);
if (g_BuyCount > 0)
{
ObjectCreate(0, "SLb", OBJ_ARROW, 0, t0, g_BuyAvgPrice);
ObjectSetInteger(0, "SLb", OBJPROP_ARROWCODE, 6);
ObjectSetInteger(0, "SLb", OBJPROP_COLOR, clrRed);
}
if (g_SellCount > 0)
{
ObjectCreate(0, "SLs", OBJ_ARROW, 0, t0, g_SellAvgPrice);
ObjectSetInteger(0, "SLs", OBJPROP_ARROWCODE, 6);
ObjectSetInteger(0, "SLs", OBJPROP_COLOR, clrBlue);
}
}
void TrailingStop()
{
double bid = CurrentBid();
double ask = CurrentAsk();
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if (ticket == 0) continue;
if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if (PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;
long type = PositionGetInteger(POSITION_TYPE);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double open = PositionGetDouble(POSITION_PRICE_OPEN);
double newSL = sl;
if (type == POSITION_TYPE_BUY)
{
double trail = GetTrailingLevel(1, bid, InpTrailingStop);
double basis = g_BuyAvgPrice;
if (basis <= 0) basis = open;
if (trail >= basis + InpMinProfit * _Point)
{
if (trail > sl + InpTrailingStep * _Point)
{
if ((bid - trail) / _Point > g_StopLevel)
newSL = trail;
}
}
if (newSL > sl && newSL > 0)
{
if (!trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), tp))
Print("TrailingStop Modify Buy SL ", sl, "->", newSL, " Err ", trade.ResultRetcode());
}
}
else if (type == POSITION_TYPE_SELL)
{
double trail = GetTrailingLevel(-1, ask, InpTrailingStop);
double basis = g_SellAvgPrice;
if (basis <= 0) basis = open;
if (trail <= basis - InpMinProfit * _Point)
{
if (trail < sl - InpTrailingStep * _Point || sl == 0)
{
if ((trail - ask) / _Point > g_StopLevel)
newSL = trail;
}
}
if (newSL < sl || (sl == 0 && newSL != 0))
{
if (!trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), tp))
Print("TrailingStop Modify Sell SL ", sl, "->", newSL, " Err ", trade.ResultRetcode());
}
}
}
}
double FractalAt(int buffer, int shift)
{
double buf[1];
if (g_FractalHandle == INVALID_HANDLE) return 0.0;
if (CopyBuffer(g_FractalHandle, buffer, shift, 1, buf) != 1) return 0.0;
if (buf[0] == EMPTY_VALUE) return 0.0;
return buf[0];
}
double GetTrailingLevel(int dir, double price, int mode)
{
double result = 0.0;
if (mode <= 2)
{
if (mode == 2) // Fractals
{
if (dir == 1)
{
for (int s = 1; s < 100; s++)
{
double fr = FractalAt(UPPER_LINE, s);
if (fr != 0)
{
result = fr - InpDelta * _Point;
if (price - g_StopLevel * _Point > result)
break;
result = 0;
}
}
}
else
{
for (int s = 1; s < 100; s++)
{
double fr = FractalAt(LOWER_LINE, s);
if (fr != 0)
{
result = fr + InpDelta * _Point;
if (g_StopLevel * _Point + price < result)
break;
result = 0;
}
}
}
}
else if (mode == 1) // Candle
{
if (dir == 1)
{
for (int s = 1; s < 500; s++)
{
double loRaw = iLow(_Symbol, g_TrailTF, s) - InpDelta * _Point;
double lo = NormalizeDouble(loRaw, _Digits);
result = lo; // set before break (matches original)
if (loRaw != 0)
{
if (price - g_StopLevel * _Point > loRaw)
break;
result = 0;
}
}
}
else
{
for (int s = 1; s < 500; s++)
{
double hiRaw = iHigh(_Symbol, g_TrailTF, s) + InpDelta * _Point;
double hi = NormalizeDouble(hiRaw, _Digits);
result = hi; // set before break (matches original)
if (hiRaw != 0)
{
if (g_StopLevel * _Point + price < hiRaw)
break;
result = 0;
}
}
}
}
}
else // Pip based
{
result = (dir == 1) ? price - mode * _Point : mode * _Point + price;
}
return result;
}
void CheckExitConditions()
{
double totalProfit = g_BuyProfit + g_SellProfit;
bool lossIcon = (g_BuyProfit <= -InpMaxLossCloseAll ||
g_SellProfit <= -InpMaxLossCloseAll);
if (lossIcon)
{
if (totalProfit >= InpCloseAll)
{
Print("CloseAll ", DoubleToString(totalProfit, 2));
CloseAllOrders(0);
return;
}
}
else
{
if (g_BuyProfit >= InpStopProfit)
{
Print("Buy Profit ", DoubleToString(g_BuyProfit, 2));
CloseAllOrders(1);
return;
}
if (g_SellProfit >= InpStopProfit)
{
Print("Sell Profit ", DoubleToString(g_SellProfit, 2));
CloseAllOrders(-1);
return;
}
}
if (g_BuyProfit <= -InpStopLoss)
{
Print("Buy Loss ", DoubleToString(g_BuyProfit, 2));
CloseAllOrders(1);
return;
}
if (g_SellProfit <= -InpStopLoss)
{
Print("Sell Loss ", DoubleToString(g_SellProfit, 2));
CloseAllOrders(-1);
return;
}
}
double MarginRequiredPerLot(ENUM_ORDER_TYPE type, double price)
{
double margin = 0.0;
if (!OrderCalcMargin(type, _Symbol, 1.0, price, margin))
return 0.0;
return margin;
}
void PlaceBuyGrid()
{
if (!InpEnableBuy) return;
if (g_BuyProfit <= -InpMaxLoss) return;
double ask = CurrentAsk();
double price;
if (g_BuyCount > 0)
{
price = ask + g_CurMinDist * _Point;
double ref = g_BuyMinOpen - g_CurStep * _Point;
if (price < ref)
price = ask + g_CurStep * _Point;
}
else
{
price = ask + g_FirstStep * _Point;
}
bool allow = false;
if (g_BuyCount == 0)
allow = true;
else if (g_BuyMaxOpen != 0 && price >= g_BuyMaxOpen + g_CurStep * _Point && InpOpenTrend)
allow = true;
else if (g_BuyMinOpen != 0 && price <= g_BuyMinOpen - g_CurStep * _Point)
allow = true;
if (!allow) return;
double lots;
if (g_BuyCount > 0)
lots = NormalizeDouble(InpBaseLot * MathPow(InpLotMultiplier, g_BuyCount) + g_BuyCount * InpPlusLot, InpLotDigits);
else
lots = InpBaseLot;
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
double marginReq = MarginRequiredPerLot(ORDER_TYPE_BUY, ask);
if (marginReq <= 0) return;
double maxLots = freeMargin / marginReq;
if ((lots < maxLots && g_BuyCount > 0) || InpFirstOrder)
{
if (TimeFilterAllowed())
{
if (!trade.BuyStop(lots, NormalizeDouble(price, _Digits), _Symbol, 0, 0,
ORDER_TIME_GTC, 0, "LarryFX BUY"))
Print("OrderSend BUYSTOP Error ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription());
}
}
}
void PlaceSellGrid()
{
if (!InpEnableSell) return;
if (g_SellProfit <= -InpMaxLoss) return;
double bid = CurrentBid();
double price;
if (g_SellCount > 0)
{
price = bid - g_Cur1MinDist * _Point;
double ref = g_SellMaxOpen + g_Cur1Step * _Point;
if (price < ref)
price = bid - g_Cur1Step * _Point;
}
else
{
price = bid - g_FirstStep * _Point;
}
bool allow = false;
if (g_SellCount == 0)
allow = true;
else if (g_SellMinOpen != 0 && price <= g_SellMinOpen - g_Cur1Step * Показано первые 50% кода (символов: 30270)
Открыть по тарифу
Купите тариф с пакетом открытий или войдите, если тариф уже есть. Открытый файл останется в вашем кабинете навсегда.
Разовая покупка файла
На этой странице откроется полный исходный код файла вместо ознакомительного фрагмента.
Зачем нужен email? После подтверждения оплаты файл сохранится в кабинете на этот email. Если аккаунта ещё нет, мы создадим его и пришлём ссылку для задания пароля. Если аккаунт уже есть, покупка добавится в него. Укажите адрес, к которому у вас есть доступ.
Чтобы оформить заказ, введите email и перейдите к оплате.