dochian_channel ea@amazing_owner.mq4
Похожие материалы по тегам
Сообщите нам, если считаете, что материал размещён без разрешения.
Сообщить о материале
Обращение по товару «dochian_channel ea@amazing_owner.mq4» отправится на рассмотрение. Укажите контакт для ответа и опишите причину.
О программе
Этот советник для MetaTrader 4 реализует торговую логику на основе индикатора Dochian Channel. Он открывает и закрывает позиции по сигналам пересечения линий канала, поддерживает торговлю в длинную и короткую сторону с настраиваемой стороной торговли. Советник работает в режиме реального времени или по закрытию бара, обрабатывая события OnTick.
Настройки программы
| Настройка | Описание |
|---|---|
| Length | Длина канала — Период расчёта канала Dochian Channel (целое число). |
| 0 - Close (Mode) | Режим канала — Выбор линии канала для сигналов: 0 — цена закрытия, 1 — верх/низ канала. |
| Exit on center (exit_mode) | Режим выхода — Условие закрытия позиции: по центру канала или по верхней/нижней границе. |
| == General == (GeneralSection) | Общие настройки — Текстовый разделитель в окне параметров для группы общих настроек. |
| Trade live? (trade_live) | Торговый режим — Выбор режима торговли: в реальном времени или по закрытию бара. |
| Position size (lots_value) | Размер лота — Числовое значение размера позиции (зависит от типа размера лота). |
| Position size type (lots_type) | Тип размера лота — Способ задания размера позиции: в денежном эквиваленте, в контрактах, процент от equity или риск в процентах. |
| Leverage override (for bad brokers, use 0 to disable) (leverage_override) | Переопределение плеча — Числовое значение для принудительного задания плеча (0 — отключено). |
| Slippage, points (slippage_points) | Проскальзывание — Максимально допустимое проскальзывание в пунктах при исполнении ордеров. |
| What trades should be taken (trading_side) | Сторона торговли — Направление сделок: только длинные, только короткие или обе стороны. |
| Close on opposite signal (close_on_opposite) | Закрытие при противоположном сигнале — Переключатель: закрывать текущую позицию при появлении сигнала в противоположном направлении. |
| == Alerts == (AlertsSection) | Разделитель уведомлений — Текстовый разделитель в окне параметров для группы настроек уведомлений. |
| Popup message (popup_alert) | Всплывающее уведомление — Включает показ всплывающего сообщения на графике при событиях. |
| Push notification (notification_alert) | Push-уведомление — Включает отправку push-уведомлений на мобильное устройство. |
| Email (email_alert) | Email-уведомление — Включает отправку уведомлений по электронной почте. |
| Play sound on alert (play_sound) | Звуковой сигнал — Включает воспроизведение звукового файла при событиях. |
| Sound file (sound_file) | Файл звука — Путь к звуковому файлу для воспроизведения при уведомлениях. |
| Start external program (start_program) | Запуск программы — Включает запуск внешней программы при событиях через ShellExecute. |
| Advanced alert (Telegram/Discord/other platform (like another MT4)) (advanced_alert) | Расширенное уведомление — Включает отправку уведомлений через DLL расширенной системы оповещений. |
| Comment2 | Комментарий 2 — Информационный текстовый параметр для отображения в интерфейсе. |
| Comment3 | Комментарий 3 — Информационный текстовый параметр для отображения в интерфейсе. |
| Comment4 | Комментарий 4 — Информационный текстовый параметр для отображения в интерфейсе. |
Поиск по коду файла
#property version "1.0"
#property strict
#define REVERSABLE_LOGIC_FEATURE extern
#define STOP_LOSS_FEATURE
#define NET_STOP_LOSS_FEATURE extern
#define TAKE_PROFIT_FEATURE
#define NET_TAKE_PROFIT_FEATURE extern
#define MARTINGALE_FEATURE extern
#define WEEKLY_TRADING_TIME_FEATURE extern
#define TRADING_TIME_FEATURE extern
#define POSITION_CAP_FEATURE extern
enum ExitMode
{
ExitOnCenter, // On center
ExitOnUpBottom // Up/bottom
};
input int Length = 20;
input int Mode = 1; // 0 - Close
input ExitMode exit_mode = ExitOnCenter; // Exit on center
enum TradingMode
{
TradingModeLive, // Live
TradingModeOnBarClose // On bar close
};
input string GeneralSection = ""; // == General ==
input TradingMode trade_live = TradingModeLive; // Trade live?
enum PositionSizeType
{
PositionSizeAmount, // $
PositionSizeContract, // In contracts
PositionSizeEquity, // % of equity
PositionSizeRisk // Risk in % of equity
};
enum LogicDirection
{
DirectLogic, // Direct
ReversalLogic // Reversal
};
enum TradingSide
{
LongSideOnly, // Long
ShortSideOnly, // Short
BothSides // Both
};
extern double lots_value = 0.1; // Position size
extern PositionSizeType lots_type = PositionSizeContract; // Position size type
extern double leverage_override = 0; // Leverage override (for bad brokers, use 0 to disable)
extern int slippage_points = 3; // Slippage, points
extern TradingSide trading_side = BothSides; // What trades should be taken
REVERSABLE_LOGIC_FEATURE LogicDirection logic_direction = DirectLogic; // Logic type
extern bool close_on_opposite = true; // Close on opposite signal
POSITION_CAP_FEATURE string CapSection = ""; // == Position cap ==
POSITION_CAP_FEATURE bool position_cap = false; // Position Cap
POSITION_CAP_FEATURE int no_of_positions = 1; // Max # of buy+sell positions
POSITION_CAP_FEATURE int no_of_buy_position = 1; // Max # of buy positions
POSITION_CAP_FEATURE int no_of_sell_position = 1; // Max # of sell positions
MARTINGALE_FEATURE string MartingaleSection = ""; // == Martingale type ==
enum MartingaleType
{
MartingaleDoNotUse, // Do not use
MartingaleOnLoss // Open another position on loss
};
enum MartingaleLotSizingType
{
MartingaleLotSizingNo, // No lot sizing
MartingaleLotSizingMultiplicator, // Using miltiplicator
MartingaleLotSizingAdd // Addition
};
enum MartingaleStepSizeType
{
MartingaleStepSizePips, // Pips
MartingaleStepSizePercent, // %
};
MARTINGALE_FEATURE MartingaleType martingale_type = MartingaleDoNotUse; // Martingale type
MARTINGALE_FEATURE MartingaleLotSizingType martingale_lot_sizing_type = MartingaleLotSizingNo; // Martingale lot sizing type
MARTINGALE_FEATURE double martingale_lot_value = 1.5; // Matringale lot sizing value
MARTINGALE_FEATURE MartingaleStepSizeType martingale_step_type = MartingaleStepSizePercent; // Step unit
MARTINGALE_FEATURE double martingale_step = 5; // Open matringale position step
STOP_LOSS_FEATURE string StopLossSection = ""; // == Stop loss ==
enum TrailingType
{
TrailingDontUse, // No trailing
TrailingPips, // Use trailing in pips
TrailingPercent // Use trailing in % of stop
};
enum StopLimitType
{
StopLimitDoNotUse, // Do not use
StopLimitPercent, // Set in %
StopLimitPips, // Set in Pips
StopLimitDollar, // Set in $,
StopLimitRiskReward, // Set in % of stop loss
StopLimitAbsolute // Set in absolite value (rate)
};
STOP_LOSS_FEATURE StopLimitType stop_loss_type = StopLimitDoNotUse; // Stop loss type
STOP_LOSS_FEATURE double stop_loss_value = 10; // Stop loss value
STOP_LOSS_FEATURE TrailingType trailing_type = TrailingDontUse; // Trailing type
STOP_LOSS_FEATURE double trailing_step = 10; // Trailing step
STOP_LOSS_FEATURE double trailing_start = 0; // Min distance to order to activate the trailing
STOP_LOSS_FEATURE StopLimitType breakeven_type = StopLimitDoNotUse; // Trigger type for the breakeven
STOP_LOSS_FEATURE double breakeven_value = 10; // Trigger for the breakeven
STOP_LOSS_FEATURE double breakeven_level = 0; // Breakeven target
NET_STOP_LOSS_FEATURE StopLimitType net_stop_loss_type = StopLimitDoNotUse; // Net stop loss type
NET_STOP_LOSS_FEATURE double net_stop_loss_value = 10; // Net stop loss value
TAKE_PROFIT_FEATURE string TakeProfitSection = ""; // == Take Profit ==
TAKE_PROFIT_FEATURE StopLimitType take_profit_type = StopLimitDoNotUse; // Take profit type
TAKE_PROFIT_FEATURE double take_profit_value = 10; // Take profit value
NET_TAKE_PROFIT_FEATURE StopLimitType net_take_profit_type = StopLimitDoNotUse; // Net take profit type
NET_TAKE_PROFIT_FEATURE double net_take_profit_value = 10; // Net take profit value
enum DayOfWeek
{
DayOfWeekSunday = 0, // Sunday
DayOfWeekMonday = 1, // Monday
DayOfWeekTuesday = 2, // Tuesday
DayOfWeekWednesday = 3, // Wednesday
DayOfWeekThursday = 4, // Thursday
DayOfWeekFriday = 5, // Friday
DayOfWeekSaturday = 6 // Saturday
};
TRADING_TIME_FEATURE string OtherSection = ""; // == Other ==
TRADING_TIME_FEATURE int magic_number = 42; // Magic number
TRADING_TIME_FEATURE string start_time = "000000"; // Start time in hhmmss format
TRADING_TIME_FEATURE string stop_time = "000000"; // Stop time in hhmmss format
WEEKLY_TRADING_TIME_FEATURE bool use_weekly_timing = false; // Weekly time
WEEKLY_TRADING_TIME_FEATURE DayOfWeek week_start_day = DayOfWeekSunday; // Start day
WEEKLY_TRADING_TIME_FEATURE string week_start_time = "000000"; // Start time in hhmmss format
WEEKLY_TRADING_TIME_FEATURE DayOfWeek week_stop_day = DayOfWeekSaturday; // Stop day
WEEKLY_TRADING_TIME_FEATURE string week_stop_time = "235959"; // Stop time in hhmmss format
WEEKLY_TRADING_TIME_FEATURE bool mandatory_closing = false; // Mandatory closing for non-trading time
bool ecn_broker = false;
extern string AlertsSection = ""; // == Alerts ==
extern bool popup_alert = true; // Popup message
extern bool notification_alert = false; // Push notification
extern bool email_alert = false; // Email
extern bool play_sound = false; // Play sound on alert
extern string sound_file = ""; // Sound file
extern bool start_program = false; // Start external program
extern string program_path = ""; // Path to the external program executable
extern bool advanced_alert = false; // Advanced alert (Telegram/Discord/other platform (like another MT4))
extern string advanced_key = ""; // Advanced alert key
extern string Comment2 = "- You can get a key via @profit_robots_bot Telegram Bot. Visit ProfitRobots.com for discord/other platform keys -";
extern string Comment3 = "- Allow use of dll in the indicator parameters window -";
extern string Comment4 = "- Install AdvancedNotificationsLib.dll -";
#import "AdvancedNotificationsLib.dll"
void AdvancedAlert(string key, string text, string instrument, string timeframe);
#import
#import "shell32.dll"
int ShellExecuteW(int hwnd,string Operation,string File,string Parameters,string Directory,int ShowCmd);
#import
class Signaler
{
string _symbol;
ENUM_TIMEFRAMES _timeframe;
datetime _lastDatetime;
string _prefix;
public:
Signaler(const string symbol, ENUM_TIMEFRAMES timeframe)
{
_symbol = symbol;
_timeframe = timeframe;
}
void SetMessagePrefix(string prefix)
{
_prefix = prefix;
}
string GetSymbol()
{
return _symbol;
}
ENUM_TIMEFRAMES GetTimeframe()
{
return _timeframe;
}
string GetTimeframeStr()
{
switch (_timeframe)
{
case PERIOD_M1: return "M1";
case PERIOD_M5: return "M5";
case PERIOD_D1: return "D1";
case PERIOD_H1: return "H1";
case PERIOD_H4: return "H4";
case PERIOD_M15: return "M15";
case PERIOD_M30: return "M30";
case PERIOD_MN1: return "MN1";
case PERIOD_W1: return "W1";
}
return "M1";
}
void SendNotifications(const string subject, string message = NULL, string symbol = NULL, string timeframe = NULL)
{
if (message == NULL)
message = subject;
if (_prefix != "" && _prefix != NULL)
message = _prefix + message;
if (symbol == NULL)
symbol = _symbol;
if (timeframe == NULL)
timeframe = GetTimeframeStr();
if (start_program)
ShellExecuteW(0, "open", program_path, "", "", 1);
if (popup_alert)
Alert(message);
if (email_alert)
SendMail(subject, message);
if (play_sound)
PlaySound(sound_file);
if (notification_alert)
SendNotification(message);
if (advanced_alert && advanced_key != "" && !IsTesting())
AdvancedAlert(advanced_key, message, symbol, timeframe);
}
};
class InstrumentInfo
{
string _symbol;
double _mult;
double _point;
double _pipSize;
int _digits;
double _tickSize;
public:
InstrumentInfo(const string symbol)
{
_symbol = symbol;
_point = MarketInfo(symbol, MODE_POINT);
_digits = (int)MarketInfo(symbol, MODE_DIGITS);
_mult = _digits == 3 || _digits == 5 ? 10 : 1;
_pipSize = _point * _mult;
_tickSize = MarketInfo(_symbol, MODE_TICKSIZE);
}
static double GetBid(const string symbol) { return MarketInfo(symbol, MODE_BID); }
double GetBid() { return GetBid(_symbol); }
static double GetAsk(const string symbol) { return MarketInfo(symbol, MODE_ASK); }
double GetAsk() { return GetAsk(_symbol); }
static double GetPipSize(const string symbol)
{
double point = MarketInfo(symbol, MODE_POINT);
double digits = (int)MarketInfo(symbol, MODE_DIGITS);
double mult = digits == 3 || digits == 5 ? 10 : 1;
return point * mult;
}
double GetPipSize() { return _pipSize; }
double GetPointSize() { return _point; }
string GetSymbol() { return _symbol; }
double GetSpread() { return (GetAsk() - GetBid()) / GetPipSize(); }
int GetDigits() { return _digits; }
double GetTickSize() { return _tickSize; }
double GetMinLots() { return SymbolInfoDouble(_symbol, SYMBOL_VOLUME_MIN); };
double RoundRate(const double rate)
{
return NormalizeDouble(MathFloor(rate / _tickSize + 0.5) * _tickSize, _digits);
}
};
interface ICondition
{
public:
virtual bool IsPass(const int period) = 0;
};
class ABaseCondition : public ICondition
{
protected:
ENUM_TIMEFRAMES _timeframe;
InstrumentInfo *_instrument;
string _symbol;
public:
ABaseCondition(const string symbol, ENUM_TIMEFRAMES timeframe)
{
_instrument = new InstrumentInfo(symbol);
_timeframe = timeframe;
_symbol = symbol;
}
~ABaseCondition()
{
delete _instrument;
}
};
interface IConditionFactory
{
public:
virtual ICondition *CreateCondition(const int order) = 0;
};
class CrossOverTopCondition : public ABaseCondition
{
public:
CrossOverTopCondition(const string symbol, ENUM_TIMEFRAMES timeframe)
:ABaseCondition(symbol, timeframe)
{
}
bool IsPass(const int period)
{
double value0 = iCustom(_symbol, _timeframe, "Dochian_Channel", Length, Mode, 4, period);
double value1 = iCustom(_symbol, _timeframe, "Dochian_Channel", Length, Mode, 4, period + 1);
double close0 = iClose(_symbol, _timeframe, period);
double close1 = iClose(_symbol, _timeframe, period + 1);
return value0 >= close0 && value1 <= close1;
}
};
class CrossUnderBottomCondition : public ABaseCondition
{
public:
CrossUnderBottomCondition(const string symbol, ENUM_TIMEFRAMES timeframe)
:ABaseCondition(symbol, timeframe)
{
}
bool IsPass(const int period)
{
double value0 = iCustom(_symbol, _timeframe, "Dochian_Channel", Length, Mode, 0, period);
double value1 = iCustom(_symbol, _timeframe, "Dochian_Channel", Length, Mode, 0, period + 1);
double close0 = iClose(_symbol, _timeframe, period);
double close1 = iClose(_symbol, _timeframe, period + 1);
return value0 <= close0 && value1 >= close1;
}
};
class CrossOverCenterCondition : public ABaseCondition
{
public:
CrossOverCenterCondition(const string symbol, ENUM_TIMEFRAMES timeframe)
:ABaseCondition(symbol, timeframe)
{
}
bool IsPass(const int period)
{
double value0 = iCustom(_symbol, _timeframe, "Dochian_Channel", Length, Mode, 2, period);
double value1 = iCustom(_symbol, _timeframe, "Dochian_Channel", Length, Mode, 2, period + 1);
double close0 = iClose(_symbol, _timeframe, period);
double close1 = iClose(_symbol, _timeframe, period + 1);
return value0 >= close0 && value1 <= close1;
}
};
class CrossUnderCenterCondition : public ABaseCondition
{
public:
CrossUnderCenterCondition(const string symbol, ENUM_TIMEFRAMES timeframe)
:ABaseCondition(symbol, timeframe)
{
}
bool IsPass(const int period)
{
double value0 = iCustom(_symbol, _timeframe, "Dochian_Channel", Length, Mode, 2, period);
double value1 = iCustom(_symbol, _timeframe, "Dochian_Channel", Length, Mode, 2, period + 1);
double close0 = iClose(_symbol, _timeframe, period);
double close1 = iClose(_symbol, _timeframe, period + 1);
return value0 <= close0 && value1 >= close1;
}
};
class DisabledCondition : public ICondition
{
public:
bool IsPass(const int period) { return false; }
};
class OrCondition : public ICondition
{
ICondition *_conditions[];
public:
~OrCondition()
{
int size = ArraySize(_conditions);
for (int i = 0; i < size; ++i)
{
delete _conditions[i];
}
}
void Add(ICondition *condition)
{
int size = ArraySize(_conditions);
ArrayResize(_conditions, size + 1);
_conditions[size] = condition;
}
virtual bool IsPass(const int period)
{
int size = ArraySize(_conditions);
for (int i = 0; i < size; ++i)
{
if (_conditions[i].IsPass(period))
return true;
}
return false;
}
};
enum OrderSide
{
BuySide,
SellSide
};
enum CompareType
{
CompareLessThan
};
class OrdersIterator
{
bool _useMagicNumber;
int _magicNumber;
bool _useOrderType;
int _orderType;
bool _trades;
bool _useSide;
bool _isBuySide;
int _lastIndex;
bool _useSymbol;
string _symbol;
bool _useProfit;
double _profit;
bool _useComment;
string _comment;
CompareType _profitCompare;
bool _orders;
public:
OrdersIterator()
{
_useOrderType = false;
_useMagicNumber = false;
_useSide = false;
_lastIndex = INT_MIN;
_trades = false;
_useSymbol = false;
_useProfit = false;
_orders = false;
_useComment = false;
}
OrdersIterator *WhenSymbol(const string symbol)
{
_useSymbol = true;
_symbol = symbol;
return &this;
}
OrdersIterator *WhenProfit(const double profit, const CompareType compare)
{
_useProfit = true;
_profit = profit;
_profitCompare = compare;
return &this;
}
OrdersIterator *WhenTrade()
{
_trades = true;
return &this;
}
OrdersIterator *WhenOrder()
{
_orders = true;
return &this;
}
OrdersIterator *WhenSide(const OrderSide side)
{
_useSide = true;
_isBuySide = side == BuySide;
return &this;
}
OrdersIterator *WhenOrderType(const int orderType)
{
_useOrderType = true;
_orderType = orderType;
return &this;
}
OrdersIterator *WhenMagicNumber(const int magicNumber)
{
_useMagicNumber = true;
_magicNumber = magicNumber;
return &this;
}
OrdersIterator *WhenComment(const string comment)
{
_useComment = true;
_comment = comment;
return &this;
}
int GetOrderType() { return OrderType(); }
double GetProfit() { return OrderProfit(); }
double IsBuy() { return OrderType() == OP_BUY; }
double IsSell() { return OrderType() == OP_SELL; }
int GetTicket() { return OrderTicket(); }
int Count()
{
int count = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && PassFilter())
count++;
}
return count;
}
bool Next()
{
if (_lastIndex == INT_MIN)
_lastIndex = OrdersTotal() - 1;
else
_lastIndex = _lastIndex - 1;
while (_lastIndex >= 0)
{
if (OrderSelect(_lastIndex, SELECT_BY_POS, MODE_TRADES) && PassFilter())
return true;
_lastIndex = _lastIndex - 1;
}
return false;
}
bool Any()
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && PassFilter())
return true;
}
return false;
}
int First()
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && PassFilter())
return OrderTicket();
}
return -1;
}
void Reset()
{
_lastIndex = INT_MIN;
}
private:
bool PassFilter()
{
if (_useMagicNumber && OrderMagicNumber() != _magicNumber)
return false;
if (_useOrderType && OrderType() != _orderType)
return false;
if (_trades && !IsTrade())
return false;
if (_orders && IsTrade())
return false;
if (_useSymbol && OrderSymbol() != _symbol)
return false;
if (_useProfit)
{
switch (_profitCompare)
{
case CompareLessThan:
if (OrderProfit() >= _profit)
return false;
break;
}
}
if (_useSide)
{
if (_trades)
{
if (_isBuySide && !IsBuy())
return false;
if (!_isBuySide && !IsSell())
return false;
}
else
{
}
}
if (_useComment && OrderComment() != _comment)
return false;
return true;
}
bool IsTrade()
{
return (OrderType() == OP_BUY || OrderType() == OP_SELL) && OrderCloseTime() == 0.0;
}
};
class TradingCalculator
{
InstrumentInfo *_symbol;
TradingCalculator(const string symbol)
{
_symbol = new InstrumentInfo(symbol);
}
public:
static TradingCalculator *Create(const string symbol)
{
ResetLastError();
double temp = MarketInfo(symbol, MODE_POINT);
if (GetLastError() != 0)
return NULL;
return new TradingCalculator(symbol);
}
~TradingCalculator()
{
delete _symbol;
}
double GetPipSize() { return _symbol.GetPipSize(); }
string GetSymbol() { return _symbol.GetSymbol(); }
double GetBid() { return _symbol.GetBid(); }
double GetAsk() { return _symbol.GetAsk(); }
int GetDigits() { return _symbol.GetDigits(); }
double GetSpread() { return _symbol.GetSpread(); }
static bool IsBuyOrder()
{
switch (OrderType())
{
case OP_BUY:
case OP_BUYLIMIT:
case OP_BUYSTOP:
return true;
}
return false;
}
double GetBreakevenPrice(OrdersIterator &it1, const OrderSide side, double &totalAmount)
{
totalAmount = 0.0;
double lotStep = SymbolInfoDouble(_symbol.GetSymbol(), SYMBOL_VOLUME_STEP);
double price = side == BuySide ? _symbol.GetBid() : _symbol.GetAsk();
double totalPL = 0;
while (it1.Next())
{
double orderLots = OrderLots();
totalAmount += orderLots / lotStep;
if (side == BuySide)
totalPL += (price - OrderOpenPrice()) * (OrderLots() / lotStep);
else
totalPL += (OrderOpenPrice() - price) * (OrderLots() / lotStep);
}
if (totalAmount == 0.0)
return 0.0;
double shift = -(totalPL / totalAmount);
return side == BuySide ? price + shift : price - shift;
}
double GetBreakevenPrice(const int side, const int magicNumber, double &totalAmount)
{
totalAmount = 0.0;
OrdersIterator it1();
it1.WhenMagicNumber(magicNumber);
it1.WhenSymbol(_symbol.GetSymbol());
it1.WhenOrderType(side);
return GetBreakevenPrice(it1, side == OP_BUY ? BuySide : SellSide, totalAmount);
}
double CalculateTakeProfit(const bool isBuy, const double takeProfit, const StopLimitType takeProfitType, const double amount, double basePrice)
{
int direction = isBuy ? 1 : -1;
switch (takeProfitType)
{
case StopLimitPercent:
return RoundRate(basePrice + basePrice * takeProfit / 100.0 * direction);
case StopLimitPips:
return RoundRate(basePrice + takeProfit * _symbol.GetPipSize() * direction);
case StopLimitDollar:
return RoundRate(basePrice + CalculateSLShift(amount, takeProfit) * direction);
case StopLimitAbsolute:
return takeProfit;
}
return 0.0;
}
double CalculateStopLoss(const bool isBuy, const double stopLoss, const StopLimitType stopLossType, const double amount, double basePrice)
{
int direction = isBuy ? 1 : -1;
switch (stopLossType)
{
case StopLimitPercent:
return RoundRate(basePrice - basePrice * stopLoss / 100.0 * direction);
case StopLimitPips:
return RoundRate(basePrice - stopLoss * _symbol.GetPipSize() * direction);
case StopLimitDollar:
return RoundRate(basePrice - CalculateSLShift(amount, stopLoss) * direction);
case StopLimitAbsolute:
return stopLoss;
}
return 0.0;
}
double GetLots(const PositionSizeType lotsType, const double lotsValue, const double stopDistance, const double leverageOverride = 0)
{
switch (lotsType)
{
case PositionSizeAmount:
return GetLotsForMoney(lotsValue, leverageOverride);
case PositionSizeContract:
return LimitLots(RoundLots(lotsValue));
case PositionSizeEquity:
return GetLotsForMoney(AccountEquity() * lotsValue / 100.0, leverageOverride);
case PositionSizeRisk:
{
double affordableLoss = AccountEquity() * lotsValue / 100.0;
double unitCost = MarketInfo(_symbol.GetSymbol(), MODE_TICKVALUE);
double tickSize = _symbol.GetTickSize();
double possibleLoss = unitCost * stopDistance / tickSize;
if (possibleLoss <= 0.01)
return 0;
return LimitLots(RoundLots(affordableLoss / possibleLoss));
}
}
return lotsValue;
}
bool IsLotsValid(const double lots, PositionSizeType lotsType, string &error)
{
switch (lotsType)
{
case PositionSizeContract:
return IsContractLotsValid(lots, error);
}
return true;
}
double NormalizeLots(const double lots)
{
return LimitLots(RoundLots(lots));
}
double RoundRate(const double rate)
{
return _symbol.RoundRate(rate);
}
private:
bool IsContractLotsValid(const double lots, string &error)
{
double minVolume = _symbol.GetMinLots();
if (minVolume > lots)
{
error = "Min. allowed lot size is " + DoubleToString(minVolume);
return false;
}
double maxVolume = SymbolInfoDouble(_symbol.GetSymbol(), SYMBOL_VOLUME_MAX);
if (maxVolume < lots)
{
error = "Max. allowed lot size is " + DoubleToString(maxVolume);
return false;
}
return true;
}
double GetLotsForMoney(const double money, const double leverageOverride = 0)
{
if (leverageOverride != 0)
{
double lotSize = MarketInfo(_symbol.GetSymbol(), MODE_LOTSIZE);
double lots = RoundLots(money * leverageOverride / lotSize);
return LimitLots(lots);
}
double marginRequired = MarketInfo(_symbol.GetSymbol(), MODE_MARGINREQUIRED);
if (marginRequired <= 0.0)
{
Print("Margin is 0. Server misconfiguration?");
return 0.0;
}
double lots = RoundLots(money / marginRequired);
return LimitLots(lots);
}
double RoundLots(const double lots)
{
double lotStep = SymbolInfoDouble(_symbol.GetSymbol(), SYMBOL_VOLUME_STEP);
if (lotStep == 0)
return 0.0;
return floor(lots / lotStep) * lotStep;
}
double LimitLots(const double lots)
{
double minVolume = _symbol.GetMinLots();
if (minVolume > lots)
return 0.0;
double maxVolume = SymbolInfoDouble(_symbol.GetSymbol(), SYMBOL_VOLUME_MAX);
if (maxVolume < lots)
return maxVolume;
return lots;
}
double CalculateSLShift(const double amount, const double money)
{
double unitCost = MarketInfo(_symbol.GetSymbol(), MODE_TICKVALUE);
double tickSize = _symbol.GetTickSize();
return (money / (unitCost / tickSize)) / amount;
}
};
interface IOrder
{
public:
virtual void AddRef() = 0;
virtual void Release() = 0;
virtual bool Select() = 0;
};
class OrderByMagicNumber : public IOrder
{
int _magicMumber;
int _references;
public:
OrderByMagicNumber(int magicNumber)
{
_magicMumber = magicNumber;
_references = 1;
}
void AddRef()
{
++_references;
}
void Release()
{
--_references;
if (_references == 0)
delete &this;
}
virtual bool Select()
{
OrdersIterator it();
it.WhenMagicNumber(_magicMumber);
int ticketId = it.First();
return OrderSelect(ticketId, SELECT_BY_TICKET, MODE_TRADES);
}
};
class OrderByTicketId : public IOrder
{
int _ticket;
int _references;
public:
OrderByTicketId(int ticket)
{
_ticket = ticket;
_references = 1;
}
void AddRef()
{
++_references;
}
void Release()
{
--_references;
if (_references == 0)
delete &this;
}
virtual bool Select()
{
return OrderSelect(_ticket, SELECT_BY_TICKET, MODE_TRADES);
}
};
interface IAction
{
public:
virtual void AddRef() = 0;
virtual void Release() = 0;
virtual bool DoAction() = 0;
};
#ifndef AAction_IMP
class AAction : public IAction
{
protected:
int _references;
AAction()
{
_references = 1;
}
public:
void AddRef()
{
++_references;
}
void Release()
{
--_references;
if (_references == 0)
delete &this;
}
};
#define AAction_IMP
#endif
class MoveToBreakevenAction : public AAction
{
Signaler* _signaler;
double _trigger;
double _target;
InstrumentInfo *_instrument;
IOrder* _order;
string _name;
public:
MoveToBreakevenAction(double trigger, double target, string name, IOrder* order, Signaler *signaler)
{
_signaler = signaler;
_trigger = trigger;
_target = target;
_name = name;
_order = order;
_order.AddRef();
_order.Select();
string symbol = OrderSymbol();
if (_instrument == NULL || symbol != _instrument.GetSymbol())
{
delete _instrument;
_instrument = new InstrumentInfo(symbol);
}
}
~MoveToBreakevenAction()
{
delete _instrument;
_order.Release();
}
virtual bool DoAction()
{
if (!_order.Select())
return false;
int ticket = OrderTicket();
string error;
if (!TradingCommands::MoveSL(ticket, _target, error))
{
Print(error);
return false;
}
if (_signaler != NULL)
{
_signaler.SendNotifications(GetNamePrefix() + "Trade " + IntegerToString(ticket) + " has reached "
+ DoubleToString(_trigger, _instrument.GetDigits()) + ". Stop loss moved to "
+ DoubleToString(_target, _instrument.GetDigits()));
}
return true;
}
private:
string GetNamePrefix()
{
if (_name == "")
return "";
return _name + ". ";
}
};
class ActionOnConditionController
{
bool _finished;
ICondition *_condition;
IAction* _action;
public:
ActionOnConditionController()
{
_action = NULL;
_condition = NULL;
_finished = true;
}
~ActionOnConditionController()
{
delete _action;
delete _condition;
}
bool SetOrder(IAction* action, ICondition *condition)
{
if (!_finished || action == NULL)
return false;
if (_action != NULL)
_action.Release();
_action = action;
_action.AddRef();
_finished = false;
delete _condition;
_condition = condition;
return true;
}
void DoLogic(const int period)
{
if (_finished)
return;
if ( _condition.IsPass(period))
{
if (_action.DoAction())
_finished = true;
}
}
};
class ActionOnConditionLogic
{
ActionOnConditionController* _controllers[];
public:
~ActionOnConditionLogic()
{
int count = ArraySize(_controllers);
for (int i = 0; i < count; ++i)
{
delete _controllers[i];
}
}
void DoLogic(const int period)
{
int count = ArraySize(_controllers);
for (int i = 0; i < count; ++i)
{
_controllers[i].DoLogic(period);
}
}
bool AddActionOnCondition(IAction* action, ICondition* condition)
{
int count = ArraySize(_controllers);
for (int i = 0; i < count; ++i)
{
if (_controllers[i].SetOrder(action, condition))
return true;
}
ArrayResize(_controllers, count + 1);
_controllers[count] = new ActionOnConditionController();
return _controllers[count].SetOrder(action, condition);
}
};
class HitProfitCondition : public ICondition
{
IOrder* _order;
double _trigger;
InstrumentInfo *_instrument;
public:
HitProfitCondition()
{
_order = NULL;
_instrument = NULL;
}
~HitProfitCondition()
{
delete _instrument;
if (_order != NULL)
_order.Release();
}
void Set(IOrder* order, double trigger)
{
if (!order.Select())
return;
_order = order;
_order.AddRef();
_trigger = trigger;
string symbol = OrderSymbol();
if (_instrument == NULL || symbol != _instrument.GetSymbol())
{
delete _instrument;
_instrument = new InstrumentInfo(symbol);
}
}
virtual bool IsPass(const int period)
{
if (_order == NULL || !_order.Select())
return false;
int type = OrderType();
if (type == OP_BUY)
return _instrument.GetAsk() >= _trigger;
else if (type == OP_SELL)
return _instrument.GetBid() <= _trigger;
return false;
}
};
interface IBreakevenLogic
{
public:
virtual void CreateBreakeven(const int order, const int period) = 0;
};
class DisabledBreakevenLogic : public IBreakevenLogic
{
public:
void CreateBreakeven(const int order, const int period) {}
};
class BreakevenLogic : public IBreakevenLogic
{
StopLimitType _triggerType;
double _trigger;
double _target;
TradingCalculator *_calculator;
Signaler *_signaler;
ActionOnConditionLogic* _actions;
public:
BreakevenLogic(const StopLimitType triggerType, const double trigger,
const double target, Signaler *signaler, ActionOnConditionLogic* actions)
{
Init();
_signaler = signaler;
_triggerType = triggerType;
_trigger = trigger;
_target = target;
_actions = actions;
}
BreakevenLogic()
{
Init();
}
~BreakevenLogic()
{
delete _calculator;
}
void CreateBreakeven(const int order, const int period, const StopLimitType triggerType,
const double trigger, const double target)
{
if (triggerType == StopLimitDoNotUse)
return;
if (!OrderSelect(order, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0.0)
return;
string symbol = OrderSymbol();
if (_calculator == NULL || symbol != _calculator.GetSymbol())
{
delete _calculator;
_calculator = TradingCalculator::Create(symbol);
if (_calculator == NULL)
return;
}
int isBuy = TradingCalculator::IsBuyOrder();
double basePrice = OrderOpenPrice();
double targetValue = _calculator.CalculateTakeProfit(isBuy, target, StopLimitPips, OrderLots(), basePrice);
double triggerValue = _calculator.CalculateTakeProfit(isBuy, trigger, triggerType, OrderLots(), basePrice);
CreateBreakeven(order, triggerValue, targetValue, "");
}
void CreateBreakeven(const int orderId, const int period)
{
CreateBreakeven(orderId, period, _triggerType, _trigger, _target);
}
private:
void Init()
{
_calculator = NULL;
_signaler = NULL;
_triggerType = StopLimitDoNotUse;
_trigger = 0;
_target = 0;
}
void CreateBreakeven(const int ticketId, const double trigger, const double target, const string name)
{
if (!OrderSelect(ticketId, SELECT_BY_TICKET, MODE_TRADES))
return;
IOrder *order = new OrderByMagicNumber(OrderMagicNumber());
HitProfitCondition* condition = new HitProfitCondition();
condition.Set(order, trigger);
IAction* action = new MoveToBreakevenAction(trigger, target, name, order, _signaler);
if (!_actions.AddActionOnCondition(action, condition))
{
delete action;
delete condition;
}
}
};
class NetBreakevenController
{
int _order;
bool _finished;
double _trigger;
double _target;
StopLimitType _triggerType;
Signaler *_signaler;
TradingCalculator *_calculator;
public:
NetBreakevenController(TradingCalculator *calculator, Signaler *signaler)
{
_calculator = calculator;
_signaler = signaler;
_finished = true;
}
~NetBreakevenController()
{
}
bool SetOrder(const int order, const double trigger, const StopLimitType triggerType, const double target)
{
if (!_finished)
{
return false;
}
if (!OrderSelect(order, SELECT_BY_TICKET, MODE_TRADES))
return false;
string symbol = OrderSymbol();
if (symbol != _calculator.GetSymbol())
return false;
_finished = false;
_trigger = trigger;
_target = target;
_order = order;
_triggerType = triggerType;
return true;
}
void DoLogic(const int period)
{
if (_finished || !OrderSelect(_order, SELECT_BY_TICKET, MODE_TRADES))
{
_finished = true;
return;
}
int type = OrderType();
double totalAmount;
int magicNumber = OrderMagicNumber();
double orderLots = OrderLots();
int ticket = OrderTicket();
double orderOpenPrice = OrderOpenPrice();
double orderTakeProfit = OrderTakeProfit();
double averagePrice = _calculator.GetBreakevenPrice(type, magicNumber, totalAmount);
double trigger = _calculator.CalculateTakeProfit(type == OP_BUY, _trigger, _triggerType, orderLots, averagePrice);
if (type == OP_BUY)
{
if (_calculator.GetAsk() >= trigger)
{
double target = averagePrice + _target * _calculator.GetPipSize();
_signaler.SendNotifications("Trade " + IntegerToString(ticket) + " has reached "
+ DoubleToString(_trigger, 1) + ". Stop loss moved to "
+ DoubleToString(target, _calculator.GetDigits()));
int res = OrderModify(ticket, orderOpenPrice, target, orderTakeProfit, 0, CLR_NONE);
_finished = true;
}
}
else if (type == OP_SELL)
{
if (_calculator.GetBid() < trigger)
{
double target = averagePrice - _target * _calculator.GetPipSize();
_signaler.SendNotifications("Trade " + IntegerToString(ticket) + " has reached "
+ DoubleToString(_trigger, 1) + ". Stop loss moved to "
+ DoubleToString(target, _calculator.GetDigits()));
int res = OrderModify(ticket, orderOpenPrice, target, orderTakeProfit, 0, CLR_NONE);
_finished = true;
}
}
}
};
class NetBreakevenLogic : public IBreakevenLogic
{
NetBreakevenController *_breakeven[];
StopLimitType _triggerType;
double _trigger;
double _target;
TradingCalculator *_calculator;
Signaler *_signaler;
public:
NetBreakevenLogic(TradingCalculator *calculator, const StopLimitType triggerType, const double trigger,
const double target, Signaler *signaler)
{
_signaler = signaler;
_calculator = calculator;
_triggerType = triggerType;
_trigger = trigger;
_target = target;
}
~NetBreakevenLogic()
{
int i_count = ArraySize(_breakeven);
for (int i = 0; i < i_count; ++i)
{
delete _breakeven[i];
}
}
void DoLogic(const int period)
{
int i_count = ArraySize(_breakeven);
for (int i = 0; i < i_count; ++i)
{
_breakeven[i].DoLogic(period);
}
}
void CreateBreakeven(const int order, const int period)
{
if (_triggerType == StopLimitDoNotUse)
return;
if (!OrderSelect(order, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0.0)
return;
string symbol = OrderSymbol();
if (symbol != _calculator.GetSymbol())
{
Print("Error in breakeven logic usage");
return;
}
int i_count = ArraySize(_breakeven);
for (int i = 0; i < i_count; ++i)
{
if (_breakeven[i].SetOrder(order, _trigger, _triggerType, _target))
{
return;
}
}
ArrayResize(_breakeven, i_count + 1);
_breakeven[i_count] = new NetBreakevenController(_calculator, _signaler);
_breakeven[i_count].SetOrder(order, _trigger, _triggerType, _target);
}
};
interface ITrailingLogic
{
public:
virtual void DoLogic() = 0;
virtual void Create(const int order, const double stop) = 0;
};
class DisabledTrailingLogic : public ITrailingLogic
{
public:
void DoLogic() {};
void Create(const int order, const double stop) {};
};
enum TrailingControllerType
{
TrailingControllerTypeStandard
,TrailingControllerTypeStream
};
interface ITrailingController
{
public:
virtual bool IsFinished() = 0;
virtual void UpdateStop() = 0;
virtual TrailingControllerType GetType() = 0;
};
class TrailingController : public ITrailingController
{
Signaler *_signaler;
int _order;
bool _finished;
double _distance;
double _trailingStep;
double _trailingStart;
InstrumentInfo *_instrument;
public:
TrailingController(Signaler *signaler = NULL)
{
_finished = true;
_order = -1;
_signaler = signaler;
_instrument = NULL;
}
~TrailingController()
{
delete _instrument;
}
bool IsFinished()
{
return _finished;
}
bool SetOrder(const int order, const double distance, const double trailingStep, const double trailingStart = 0)
{
if (!_finished)
{
return false;
}
if (!OrderSelect(order, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0.0)
{
return false;
}
string symbol = OrderSymbol();
if (_instrument == NULL || _instrument.GetSymbol() != symbol)
{
delete _instrument;
_instrument = new InstrumentInfo(symbol);
}
_trailingStep = _instrument.RoundRate(trailingStep);
if (_trailingStep == 0)
return false;
_trailingStart = trailingStart;
_finished = false;
_order = order;
_distance = distance;
return true;
}
void UpdateStop()
{
if (_finished || !OrderSelect(_order, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0.0)
{
_finished = true;
return;
}
int type = OrderType();
if (type == OP_BUY)
{
UpdateStopForLong();
}
else if (type == OP_SELL)
{
UpdateStopForShort();
}
}
TrailingControllerType GetType()
{
return TrailingControllerTypeStandard;
}
private:
void UpdateStopForLong()
{
double initialStop = OrderStopLoss();
if (initialStop == 0.0)
return;
double ask = _instrument.GetAsk();
double openPrice = OrderOpenPrice();
if (openPrice > ask + _trailingStart * _instrument.GetPipSize())
return;
double newStop = initialStop;
int digits = _instrument.GetDigits();
while (NormalizeDouble(newStop + _trailingStep, digits) < NormalizeDouble(ask - _distance, digits))
{
newStop = NormalizeDouble(newStop + _trailingStep, digits);
}
if (newStop == initialStop)
return;
if (_signaler != NULL)
{
string message = "Trailing stop for " + IntegerToString(_order) + " to " + DoubleToString(newStop, digits);
_signaler.SendNotifications(message);
}
int res = OrderModify(OrderTicket(), openPrice, newStop, OrderTakeProfit(), 0, CLR_NONE);
if (res == 0)
{
int error = GetLastError();
switch (error)
{
case ERR_INVALID_TICKET:
_finished = true;
break;
}
}
}
void UpdateStopForShort()
{
double initialStop = OrderStopLoss();
if (initialStop == 0.0)
return;
double bid = _instrument.GetBid();
double openPrice = OrderOpenPrice();
if (openPrice < bid - _trailingStart * _instrument.GetPipSize())
return;
double newStop = initialStop;
int digits = _instrument.GetDigits();
while (NormalizeDouble(newStop - _trailingStep, digits) > NormalizeDouble(bid + _distance, digits))
{
newStop = NormalizeDouble(newStop - _trailingStep, digits);
}
if (newStop == initialStop)
return;
if (_signaler != NULL)
{
string message = "Trailing stop for " + IntegerToString(_order) + " to " + DoubleToString(newStop, digits);
_signaler.SendNotifications(message);
}
int res = OrderModify(OrderTicket(), openPrice, newStop, OrderTakeProfit(), 0, CLR_NONE);
if (res == 0)
{
int error = GetLastError();
switch (error)
{
case ERR_INVALID_TICKET:
_finished = true;
break;
}
}
}
};
class TrailingLogic : public ITrailingLogic
{
ITrailingController *_trailing[];
TrailingType _trailingType;
double _trailingStep;
double _atrTrailingMultiplier;
double _trailingStart;
ENUM_TIMEFRAMES _timeframe;
InstrumentInfo *_instrument;
Signaler *_signaler;
public:
TrailingLogic(TrailingType trailing, double trailingStep, double atrTrailingMultiplier
, double trailingStart, ENUM_TIMEFRAMES timeframe, Signaler *signaler)
{
_signaler = signaler;
_instrument = NULL;
_trailingStart = trailingStart;
_trailingType = trailing;
_trailingStep = trailingStep;
_atrTrailingMultiplier = atrTrailingMultiplier;
_timeframe = timeframe;
}
~TrailingLogic()
{
delete _instrument;
int i_count = ArraySize(_trailing);
for (int i = 0; i < i_count; ++i)
{
delete _trailing[i];
}
}
void DoLogic()
{
int i_count = ArraySize(_trailing);
for (int i = 0; i < i_count; ++i)
{
_trailing[i].UpdateStop();
}
}
void Create(const int order, const double distancePips, const TrailingType trailingType, const double trailingStep
, const double trailingStart)
{
if (!OrderSelect(order, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0.0)
return;
string symbol = OrderSymbol();
if (_instrument == NULL || symbol != _instrument.GetSymbol())
{
delete _instrument;
_instrument = new InstrumentInfo(symbol);
}
double distance = distancePips * _instrument.GetPipSize();
switch (trailingType)
{
case TrailingPips:
CreateTrailing(order, distance, trailingStep * _instrument.GetPipSize(), trailingStart);
break;
case TrailingPercent:
CreateTrailing(order, distance, distance * trailingStep / 100.0, trailingStart);
break;
}
}
void Create(const int order, const double distancePips)
{
Create(order, distancePips, _trailingType, _trailingStep, _trailingStart);
}
private:
void CreateTrailing(const int order, const double distance, const double trailingStep, const double trailingStart)
{
int i_count = ArraySize(_trailing);
for (int i = 0; i < i_count; ++i)
{
if (_trailing[i].GetType() != TrailingControllerTypeStandard)
continue;
TrailingController *trailingController = (TrailingController *)_trailing[i];
if (trailingController.SetOrder(order, distance, trailingStep, trailingStart))
{
return;
}
}
TrailingController *trailingController = new TrailingController(_signaler);
trailingController.SetOrder(order, distance, trailingStep, trailingStart);
ArrayResize(_trailing, i_count + 1);
_trailing[i_count] = trailingController;
}
};
#ifndef MoveNetStopLossAction_IMP
class MoveNetStopLossAction : public AAction
{
TradingCalculator *_calculator;
int _magicNumber;
double _stopLoss;
StopLimitType _type;
Signaler *_signaler;
public:
MoveNetStopLossAction(TradingCalculator *calculator, StopLimitType type, const double stopLoss, Signaler *signaler, const int magicNumber)
{
_type = type;
_calculator = calculator;
_stopLoss = stopLoss;
_signaler = signaler;
_magicNumber = magicNumber;
}
virtual bool DoAction()
{
MoveStopLoss(OP_BUY);
MoveStopLoss(OP_SELL);
return true;
}
private:
void MoveStopLoss(const int side)
{
OrdersIterator it();
it.WhenMagicNumber(_magicNumber);
it.WhenOrderType(side);
it.WhenTrade();
if (it.Count() <= 1)
return;
double totalAmount;
double averagePrice = _calculator.GetBreakevenPrice(side, _magicNumber, totalAmount);
if (averagePrice == 0.0)
return;
double stopLoss = _calculator.CalculateStopLoss(side == OP_BUY, _stopLoss, _type, totalAmount, averagePrice);
OrdersIterator it1();
it1.WhenMagicNumber(_magicNumber);
it1.WhenSymbol(_calculator.GetSymbol());
it1.WhenOrderType(side);
it1.WhenTrade();
int count = 0;
while (it1.Next())
{
if (OrderStopLoss() != stopLoss)
{
int res = OrderModify(OrderTicket(), OrderOpenPrice(), stopLoss, OrderTakeProfit(), 0, CLR_NONE);
if (res == 0)
{
int error = GetLastError();
switch (error)
{
case ERR_NO_RESULT:
break;
case ERR_INVALID_TICKET:
break;
}
}
else
++count;
}
}
if (_signaler != NULL && count > 0)
_signaler.SendNotifications("Moving net stop loss to " + DoubleToStr(stopLoss));
}
};
#define MoveNetStopLossAction_IMP
#endif
#ifndef MoveNetTakeProfitAction_IMP
class MoveNetTakeProfitAction : public AAction
{
TradingCalculator *_calculator;
int _magicNumber;
double _takeProfit;
StopLimitType _type;
Signaler *_signaler;
public:
MoveNetTakeProfitAction(TradingCalculator *calculator, StopLimitType type, const double takeProfit, Signaler *signaler, const int magicNumber)
{
_type = type;
_calculator = calculator;
_takeProfit = takeProfit;
_signaler = signaler;
_magicNumber = magicNumber;
}
virtual bool DoAction()
{
MoveTakeProfit(OP_BUY)Показано первые 50% кода (символов: 71111)
Открыть по тарифу
Купите тариф с пакетом открытий или войдите, если тариф уже есть. Открытый файл останется в вашем кабинете навсегда.
Разовая покупка файла
На этой странице откроется полный исходный код файла вместо ознакомительного фрагмента.
Зачем нужен email? После подтверждения оплаты файл сохранится в кабинете на этот email. Если аккаунта ещё нет, мы создадим его и пришлём ссылку для задания пароля. Если аккаунт уже есть, покупка добавится в него. Укажите адрес, к которому у вас есть доступ.
Чтобы оформить заказ, введите email и перейдите к оплате.