496 lines
15 KiB
MQL5
496 lines
15 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| MA Cross Trend + Single-Direction Recovery EA (MQL5) |
|
||
|
|
//| Cleaned: no chart objects, restart-safe state reconstruction |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property strict
|
||
|
|
|
||
|
|
#include <Trade\PositionInfo.mqh>
|
||
|
|
#include <Trade\Trade.mqh>
|
||
|
|
|
||
|
|
// ---------------- INPUTS ----------------
|
||
|
|
input int FastMAPeriod = 20; // Fast MA
|
||
|
|
input int SlowMAPeriod = 50; // Slow MA
|
||
|
|
input int MAMethod = MODE_SMA;
|
||
|
|
input int MAPrice = PRICE_CLOSE;
|
||
|
|
|
||
|
|
input double BaseOpenPct = 0.85; // Trigger loss threshold (%)
|
||
|
|
input double BaseClosePct = 0.70; // Target loss threshold (%)
|
||
|
|
input double StepIncrease = 5.0; // Increase thresholds per recovery (%)
|
||
|
|
|
||
|
|
input int Magic = 1;
|
||
|
|
input double MinLot = 0.1;
|
||
|
|
input double MaxLot = 10.0;
|
||
|
|
input double LotStep = 0.1;
|
||
|
|
input int Slippage = 30;
|
||
|
|
|
||
|
|
input double TargetNotionalPct = 0.3; // 0.3% of notional
|
||
|
|
input double TrailPercent = 30.0; // trail 30% off max profit
|
||
|
|
|
||
|
|
// 0 = RETURN, 1 = FOK, 2 = IOC, 3 = AUTO
|
||
|
|
input int FillingType = 1;
|
||
|
|
|
||
|
|
CPositionInfo m_position;
|
||
|
|
CTrade m_trade;
|
||
|
|
|
||
|
|
// ---------------- INTERNAL ----------------
|
||
|
|
int RecoveryCountBuy = 0;
|
||
|
|
int RecoveryCountSell = 0;
|
||
|
|
|
||
|
|
int fastMAHandle = INVALID_HANDLE;
|
||
|
|
int slowMAHandle = INVALID_HANDLE;
|
||
|
|
bool InitialOrdersPlaced = false;
|
||
|
|
|
||
|
|
double BasketMaxProfit = 0.0;
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// Convert FillingType input into actual filling mode
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
ENUM_ORDER_TYPE_FILLING GetFillingMode(string sym)
|
||
|
|
{
|
||
|
|
switch(FillingType)
|
||
|
|
{
|
||
|
|
case 0: return ORDER_FILLING_RETURN;
|
||
|
|
case 1: return ORDER_FILLING_FOK;
|
||
|
|
case 2: return ORDER_FILLING_IOC;
|
||
|
|
case 3:
|
||
|
|
{
|
||
|
|
long filling = 0;
|
||
|
|
if(!SymbolInfoInteger(sym, SYMBOL_FILLING_MODE, filling))
|
||
|
|
return ORDER_FILLING_RETURN;
|
||
|
|
|
||
|
|
if(filling & SYMBOL_FILLING_FOK) return ORDER_FILLING_FOK;
|
||
|
|
if(filling & SYMBOL_FILLING_IOC) return ORDER_FILLING_IOC;
|
||
|
|
return ORDER_FILLING_RETURN;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ORDER_FILLING_RETURN;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// Check if market is open for this symbol
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool IsMarketOpen(string sym)
|
||
|
|
{
|
||
|
|
long tradeMode = 0;
|
||
|
|
if(!SymbolInfoInteger(sym, SYMBOL_TRADE_MODE, tradeMode))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(tradeMode == SYMBOL_TRADE_MODE_DISABLED ||
|
||
|
|
tradeMode == SYMBOL_TRADE_MODE_CLOSEONLY)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
long sessionDeals = 0;
|
||
|
|
if(!SymbolInfoInteger(sym, SYMBOL_SESSION_DEALS, sessionDeals))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return (true);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// MA CROSS TREND DETECTION
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
ENUM_ORDER_TYPE GetTrendDirection()
|
||
|
|
{
|
||
|
|
double fastBuf[1];
|
||
|
|
double slowBuf[1];
|
||
|
|
|
||
|
|
if (CopyBuffer(fastMAHandle, 0, 0, 1, fastBuf) <= 0) return ORDER_TYPE_BUY;
|
||
|
|
if (CopyBuffer(slowMAHandle, 0, 0, 1, slowBuf) <= 0) return ORDER_TYPE_BUY;
|
||
|
|
|
||
|
|
double fastMA = fastBuf[0];
|
||
|
|
double slowMA = slowBuf[0];
|
||
|
|
|
||
|
|
if (fastMA > slowMA)
|
||
|
|
return ORDER_TYPE_BUY;
|
||
|
|
else
|
||
|
|
return ORDER_TYPE_SELL;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// Rebuild internal state from currently open trades (restart-safe)
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void RebuildState(string sym)
|
||
|
|
{
|
||
|
|
InitialOrdersPlaced = false;
|
||
|
|
RecoveryCountBuy = 0;
|
||
|
|
RecoveryCountSell = 0;
|
||
|
|
|
||
|
|
int total = CountPositions(sym);
|
||
|
|
if (total >= 3)
|
||
|
|
InitialOrdersPlaced = true;
|
||
|
|
|
||
|
|
double buyVol, buyAvg, sellVol, sellAvg;
|
||
|
|
GetSideStats(sym, POSITION_TYPE_BUY, buyVol, buyAvg);
|
||
|
|
GetSideStats(sym, POSITION_TYPE_SELL, sellVol, sellAvg);
|
||
|
|
|
||
|
|
// Count BUY recovery trades
|
||
|
|
if (buyVol > 0.0 && sellVol == 0.0)
|
||
|
|
{
|
||
|
|
int buyCount = 0;
|
||
|
|
for (int i = PositionsTotal() - 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
ulong t = PositionGetTicket(i);
|
||
|
|
if (!PositionSelectByTicket(t)) continue;
|
||
|
|
if (PositionGetString(POSITION_SYMBOL) != sym) continue;
|
||
|
|
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
|
||
|
|
buyCount++;
|
||
|
|
}
|
||
|
|
RecoveryCountBuy = MathMax(0, buyCount - 3);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Count SELL recovery trades
|
||
|
|
if (sellVol > 0.0 && buyVol == 0.0)
|
||
|
|
{
|
||
|
|
int sellCount = 0;
|
||
|
|
for (int i = PositionsTotal() - 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
ulong t = PositionGetTicket(i);
|
||
|
|
if (!PositionSelectByTicket(t)) continue;
|
||
|
|
if (PositionGetString(POSITION_SYMBOL) != sym) continue;
|
||
|
|
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
|
||
|
|
sellCount++;
|
||
|
|
}
|
||
|
|
RecoveryCountSell = MathMax(0, sellCount - 3);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
int OnInit()
|
||
|
|
{
|
||
|
|
fastMAHandle = iMA(_Symbol, PERIOD_CURRENT,
|
||
|
|
FastMAPeriod, 0,
|
||
|
|
(ENUM_MA_METHOD)MAMethod,
|
||
|
|
(ENUM_APPLIED_PRICE)MAPrice);
|
||
|
|
|
||
|
|
slowMAHandle = iMA(_Symbol, PERIOD_CURRENT,
|
||
|
|
SlowMAPeriod, 0,
|
||
|
|
(ENUM_MA_METHOD)MAMethod,
|
||
|
|
(ENUM_APPLIED_PRICE)MAPrice);
|
||
|
|
|
||
|
|
if (fastMAHandle == INVALID_HANDLE || slowMAHandle == INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
Print("Failed to create MA handles");
|
||
|
|
return(INIT_FAILED);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Rebuild state from open trades (for restart / VPS crash)
|
||
|
|
RebuildState(_Symbol);
|
||
|
|
|
||
|
|
BasketMaxProfit = 0.0;
|
||
|
|
|
||
|
|
return(INIT_SUCCEEDED);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void OnTick()
|
||
|
|
{
|
||
|
|
string sym = _Symbol;
|
||
|
|
double bid = SymbolInfoDouble(sym, SYMBOL_BID);
|
||
|
|
double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
|
||
|
|
if (bid <= 0 || ask <= 0) return;
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
// INITIAL 3 ORDERS BASED ON MA CROSS TREND (only once)
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
if (!InitialOrdersPlaced || CountPositions(_Symbol)<3)
|
||
|
|
{
|
||
|
|
if (!IsMarketOpen(sym))
|
||
|
|
{
|
||
|
|
Print("Market closed, waiting to place initial orders...");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
ENUM_ORDER_TYPE trendDir = GetTrendDirection();
|
||
|
|
double price = (trendDir == ORDER_TYPE_BUY ? ask : bid);
|
||
|
|
double lot = NormalizeLot(MinLot);
|
||
|
|
|
||
|
|
Print("Attempting initial 3 orders in MA CROSS trend direction: ",
|
||
|
|
(trendDir == ORDER_TYPE_BUY ? "BUY" : "SELL"));
|
||
|
|
|
||
|
|
int successCount = CountPositions(_Symbol);
|
||
|
|
|
||
|
|
for (int i = 0; i < 3; i++)
|
||
|
|
{
|
||
|
|
if (SendOrder(sym, trendDir, lot, price))
|
||
|
|
{
|
||
|
|
successCount++;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Print("Initial order ", i+1, " failed.");
|
||
|
|
}
|
||
|
|
|
||
|
|
Sleep(200);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (successCount > 0)
|
||
|
|
{
|
||
|
|
Print("Initial orders placed successfully: ", successCount);
|
||
|
|
InitialOrdersPlaced = true;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Print("All initial orders failed. Will retry on next tick.");
|
||
|
|
}
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
// BASKET PROFIT + NOTIONAL-BASED TARGET + TRAILING
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
double totalProfit = 0.0;
|
||
|
|
double totalNotional = 0.0;
|
||
|
|
|
||
|
|
for (int i = PositionsTotal() - 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
ulong ticket = PositionGetTicket(i);
|
||
|
|
if (!PositionSelectByTicket(ticket)) continue;
|
||
|
|
if (PositionGetString(POSITION_SYMBOL) != sym) continue;
|
||
|
|
|
||
|
|
double posProfit = PositionGetDouble(POSITION_PROFIT);
|
||
|
|
double posVolume = PositionGetDouble(POSITION_VOLUME);
|
||
|
|
double posPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
||
|
|
|
||
|
|
double contractSize = SymbolInfoDouble(sym, SYMBOL_TRADE_CONTRACT_SIZE);
|
||
|
|
if (contractSize <= 0.0) contractSize = 1.0;
|
||
|
|
|
||
|
|
double notional = posVolume * contractSize * posPrice;
|
||
|
|
|
||
|
|
totalProfit += posProfit;
|
||
|
|
totalNotional += notional;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (totalNotional <= 0.0)
|
||
|
|
{
|
||
|
|
BasketMaxProfit = 0.0;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (totalProfit > BasketMaxProfit)
|
||
|
|
BasketMaxProfit = totalProfit;
|
||
|
|
|
||
|
|
double targetProfit = totalNotional * (TargetNotionalPct / 100.0);
|
||
|
|
|
||
|
|
if (totalProfit >= targetProfit)
|
||
|
|
{
|
||
|
|
double trailDistance = BasketMaxProfit * (TrailPercent / 100.0);
|
||
|
|
double trailLevel = BasketMaxProfit - trailDistance;
|
||
|
|
|
||
|
|
if (totalProfit <= trailLevel)
|
||
|
|
{
|
||
|
|
Print("Trailing TP hit. Profit=", totalProfit,
|
||
|
|
" Max=", BasketMaxProfit,
|
||
|
|
" TrailLevel=", trailLevel,
|
||
|
|
" Notional=", totalNotional,
|
||
|
|
" Target=", targetProfit);
|
||
|
|
|
||
|
|
CloseAllSymbol(sym);
|
||
|
|
InitialOrdersPlaced = false;
|
||
|
|
BasketMaxProfit = 0.0;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
// Determine direction from existing positions
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
double buyVol, buyAvg, sellVol, sellAvg;
|
||
|
|
GetSideStats(sym, POSITION_TYPE_BUY, buyVol, buyAvg);
|
||
|
|
GetSideStats(sym, POSITION_TYPE_SELL, sellVol, sellAvg);
|
||
|
|
|
||
|
|
int posCount = CountPositions(sym);
|
||
|
|
if (posCount < 3) return;
|
||
|
|
|
||
|
|
if (buyVol > 0.0 && sellVol > 0.0)
|
||
|
|
{
|
||
|
|
Print("Both BUY and SELL positions detected. EA paused.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (buyVol > 0.0 && sellVol == 0.0)
|
||
|
|
{
|
||
|
|
HandleBuySide(sym, bid, ask, buyVol, buyAvg);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sellVol > 0.0 && buyVol == 0.0)
|
||
|
|
{
|
||
|
|
HandleSellSide(sym, bid, ask, sellVol, sellAvg);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// BUY SIDE LOGIC (no chart objects)
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void HandleBuySide(string sym, double bid, double ask, double vol, double avg)
|
||
|
|
{
|
||
|
|
double openThr = (BaseOpenPct/100.0) * MathPow(1.0 + StepIncrease/100.0, RecoveryCountBuy);
|
||
|
|
double closeThr = (BaseClosePct/100.0) * MathPow(1.0 + StepIncrease/100.0, RecoveryCountBuy);
|
||
|
|
|
||
|
|
double lossPct = (avg - bid) / bid;
|
||
|
|
|
||
|
|
if (lossPct >= openThr)
|
||
|
|
{
|
||
|
|
double lot = CalcRecoveryLotBuy(vol, avg, bid, closeThr);
|
||
|
|
lot = NormalizeLot(lot);
|
||
|
|
|
||
|
|
if (lot >= MinLot)
|
||
|
|
{
|
||
|
|
if (SendOrder(sym, ORDER_TYPE_BUY, lot, ask))
|
||
|
|
{
|
||
|
|
RecoveryCountBuy++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// SELL SIDE LOGIC (no chart objects)
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void HandleSellSide(string sym, double bid, double ask, double vol, double avg)
|
||
|
|
{
|
||
|
|
double openThr = (BaseOpenPct/100.0) * MathPow(1.0 + StepIncrease/100.0, RecoveryCountSell);
|
||
|
|
double closeThr = (BaseClosePct/100.0) * MathPow(1.0 + StepIncrease/100.0, RecoveryCountSell);
|
||
|
|
|
||
|
|
double lossPct = (ask - avg) / ask;
|
||
|
|
|
||
|
|
if (lossPct >= openThr)
|
||
|
|
{
|
||
|
|
double lot = CalcRecoveryLotSell(vol, avg, ask, closeThr);
|
||
|
|
lot = NormalizeLot(lot);
|
||
|
|
|
||
|
|
if (lot >= MinLot)
|
||
|
|
{
|
||
|
|
if (SendOrder(sym, ORDER_TYPE_SELL, lot, bid))
|
||
|
|
{
|
||
|
|
RecoveryCountSell++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// COUNT POSITIONS
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
int CountPositions(string sym)
|
||
|
|
{
|
||
|
|
int c = 0;
|
||
|
|
for (int i = PositionsTotal() - 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
ulong t = PositionGetTicket(i);
|
||
|
|
if (!PositionSelectByTicket(t)) continue;
|
||
|
|
if (PositionGetString(POSITION_SYMBOL) == sym)
|
||
|
|
c++;
|
||
|
|
}
|
||
|
|
return c;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// GET SIDE STATS
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void GetSideStats(string sym, int type, double &vol, double &avg)
|
||
|
|
{
|
||
|
|
vol = 0.0;
|
||
|
|
avg = 0.0;
|
||
|
|
double sum = 0.0;
|
||
|
|
|
||
|
|
for (int i = PositionsTotal() - 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
ulong t = PositionGetTicket(i);
|
||
|
|
if (!PositionSelectByTicket(t)) continue;
|
||
|
|
if (PositionGetString(POSITION_SYMBOL) != sym) continue;
|
||
|
|
|
||
|
|
if ((int)PositionGetInteger(POSITION_TYPE) != type) continue;
|
||
|
|
|
||
|
|
double v = PositionGetDouble(POSITION_VOLUME);
|
||
|
|
double p = PositionGetDouble(POSITION_PRICE_OPEN);
|
||
|
|
|
||
|
|
vol += v;
|
||
|
|
sum += v * p;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (vol > 0.0)
|
||
|
|
avg = sum / vol;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// RECOVERY LOT CALCULATIONS
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CalcRecoveryLotBuy(double V_old, double P_avg, double current, double thr)
|
||
|
|
{
|
||
|
|
double target = current * (1.0 + thr);
|
||
|
|
double num = V_old * (P_avg - target);
|
||
|
|
double den = current * thr;
|
||
|
|
if (den <= 0.0) return 0.0;
|
||
|
|
return MathMax(0.0, num / den);
|
||
|
|
}
|
||
|
|
|
||
|
|
double CalcRecoveryLotSell(double V_old, double P_avg, double current, double thr)
|
||
|
|
{
|
||
|
|
double target = current * (1.0 - thr);
|
||
|
|
double num = V_old * (target - P_avg);
|
||
|
|
double den = current * thr;
|
||
|
|
if (den <= 0.0) return 0.0;
|
||
|
|
return MathMax(0.0, num / den);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// LOT NORMALIZATION
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double NormalizeLot(double lot)
|
||
|
|
{
|
||
|
|
if (lot <= 0.0) return 0.0;
|
||
|
|
lot = MathMin(lot, MaxLot);
|
||
|
|
lot = MathMax(lot, MinLot);
|
||
|
|
lot = MathFloor(lot / LotStep) * LotStep;
|
||
|
|
return NormalizeDouble(lot, 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// SEND ORDER (uses FillingType parameter via GetFillingMode)
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool SendOrder(string sym, ENUM_ORDER_TYPE type, double lot, double price)
|
||
|
|
{
|
||
|
|
if (lot <= 0.0) return false;
|
||
|
|
|
||
|
|
MqlTradeRequest req;
|
||
|
|
MqlTradeResult res;
|
||
|
|
ZeroMemory(req);
|
||
|
|
ZeroMemory(res);
|
||
|
|
|
||
|
|
req.action = TRADE_ACTION_DEAL;
|
||
|
|
req.symbol = sym;
|
||
|
|
req.magic = Magic;
|
||
|
|
req.volume = lot;
|
||
|
|
req.type = type;
|
||
|
|
req.price = price;
|
||
|
|
req.deviation = Slippage;
|
||
|
|
req.type_time = ORDER_TIME_GTC;
|
||
|
|
req.type_filling = 1;
|
||
|
|
|
||
|
|
if (!OrderSend(req, res))
|
||
|
|
{
|
||
|
|
Print("OrderSend failed: retcode=", res.retcode);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (res.retcode == TRADE_RETCODE_DONE ||
|
||
|
|
res.retcode == TRADE_RETCODE_DONE_PARTIAL);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
// CLOSE ALL POSITIONS (uses CTrade)
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CloseAllSymbol(string sym)
|
||
|
|
{
|
||
|
|
for(int i=PositionsTotal()-1; i>=0; i--)
|
||
|
|
{
|
||
|
|
if(m_position.SelectByIndex(i))
|
||
|
|
{
|
||
|
|
if (m_position.Symbol()==sym)
|
||
|
|
m_trade.PositionClose(m_position.Ticket());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|