247 lines
6.6 KiB
Plaintext
247 lines
6.6 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| MartingaleBTCUSD_Safe.mq5 |
|
|
//| Classic martingale: double lot after loss, reset after win (BTC) |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Lab"
|
|
#property version "1.01"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
input group "=== Market ==="
|
|
input string InpSymbol = "BTCUSD";
|
|
input ENUM_TIMEFRAMES InpTf = PERIOD_M15;
|
|
input ulong InpMagic = 202604241;
|
|
input int InpSlippagePts = 50;
|
|
|
|
input group "=== Martingale (classic) ==="
|
|
input double InpBaseLots = 0.01;
|
|
input double InpLotMultiplier = 2.0; // traditional = 2.0
|
|
input int InpMaxDoublings = 16; // cap exponent (0..MaxDoublings); then lot stops growing
|
|
|
|
input group "=== Entry (RSI) ==="
|
|
input int InpRsiPeriod = 14;
|
|
input double InpRsiBuyBelow = 32.0;
|
|
input double InpRsiSellAbove = 68.0;
|
|
|
|
input group "=== SL / TP (optional) ==="
|
|
input bool InpUseSLTP = false;
|
|
input double InpSLPts = 4000.0;
|
|
input double InpTPPts = 3500.0;
|
|
|
|
CTrade g_trade;
|
|
int g_hRsi = INVALID_HANDLE;
|
|
int g_lossStreak = 0;
|
|
ulong g_lastPosId = 0;
|
|
|
|
string WorkSym() { return InpSymbol; }
|
|
double SymPoint() { return SymbolInfoDouble(WorkSym(), SYMBOL_POINT); }
|
|
|
|
void SetFilling()
|
|
{
|
|
const long fill = SymbolInfoInteger(WorkSym(), SYMBOL_FILLING_MODE);
|
|
if((fill & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
|
|
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
|
else if((fill & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
|
|
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
|
}
|
|
|
|
double NetProfitForPositionId(const ulong posId)
|
|
{
|
|
if(posId == 0)
|
|
return 0.0;
|
|
const datetime to = TimeCurrent();
|
|
if(!HistorySelect(0, to))
|
|
return 0.0;
|
|
double sum = 0.0;
|
|
const int n = HistoryDealsTotal();
|
|
for(int i = 0; i < n; i++)
|
|
{
|
|
const ulong deal = HistoryDealGetTicket(i);
|
|
if(deal == 0)
|
|
continue;
|
|
if((ulong)HistoryDealGetInteger(deal, DEAL_POSITION_ID) != posId)
|
|
continue;
|
|
sum += HistoryDealGetDouble(deal, DEAL_PROFIT);
|
|
sum += HistoryDealGetDouble(deal, DEAL_SWAP);
|
|
sum += HistoryDealGetDouble(deal, DEAL_COMMISSION);
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
int OurPositionCount()
|
|
{
|
|
int c = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
const ulong t = PositionGetTicket(i);
|
|
if(t == 0 || !PositionSelectByTicket(t))
|
|
continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != WorkSym())
|
|
continue;
|
|
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic)
|
|
c++;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
double LotsNow()
|
|
{
|
|
const int exp = MathMax(0, MathMin(g_lossStreak, InpMaxDoublings));
|
|
double lot = InpBaseLots * MathPow(InpLotMultiplier, (double)exp);
|
|
const double minLot = SymbolInfoDouble(WorkSym(), SYMBOL_VOLUME_MIN);
|
|
const double maxLot = SymbolInfoDouble(WorkSym(), SYMBOL_VOLUME_MAX);
|
|
const double stepLot = SymbolInfoDouble(WorkSym(), SYMBOL_VOLUME_STEP);
|
|
if(stepLot > 0.0)
|
|
lot = MathFloor(lot / stepLot) * stepLot;
|
|
if(lot < minLot)
|
|
lot = minLot;
|
|
if(lot > maxLot)
|
|
lot = maxLot;
|
|
return NormalizeDouble(lot, 8);
|
|
}
|
|
|
|
bool CopyRsi1(double &rsi1)
|
|
{
|
|
double buf[1];
|
|
if(CopyBuffer(g_hRsi, 0, 1, 1, buf) != 1)
|
|
return false;
|
|
rsi1 = buf[0];
|
|
return true;
|
|
}
|
|
|
|
void BuildSLTP(const bool isBuy, const double price, double &sl, double &tp)
|
|
{
|
|
sl = tp = 0.0;
|
|
if(!InpUseSLTP)
|
|
return;
|
|
const double pt = SymPoint();
|
|
if(pt <= 0.0)
|
|
return;
|
|
if(isBuy)
|
|
{
|
|
sl = price - InpSLPts * pt;
|
|
tp = price + InpTPPts * pt;
|
|
}
|
|
else
|
|
{
|
|
sl = price + InpSLPts * pt;
|
|
tp = price - InpTPPts * pt;
|
|
}
|
|
}
|
|
|
|
void OnClosedPosition()
|
|
{
|
|
const double net = NetProfitForPositionId(g_lastPosId);
|
|
if(net < 0.0)
|
|
g_lossStreak++;
|
|
else
|
|
g_lossStreak = 0;
|
|
Print("Martingale: closed net=", net, " lossStreak=", g_lossStreak, " next lot=", LotsNow());
|
|
g_lastPosId = 0;
|
|
}
|
|
|
|
bool OurPositionOpenById(const ulong posId)
|
|
{
|
|
if(posId == 0)
|
|
return false;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
const ulong t = PositionGetTicket(i);
|
|
if(t == 0 || !PositionSelectByTicket(t))
|
|
continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != WorkSym())
|
|
continue;
|
|
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
|
continue;
|
|
if((ulong)PositionGetInteger(POSITION_IDENTIFIER) == posId)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CaptureLastPositionId()
|
|
{
|
|
Sleep(20);
|
|
for(int k = 0; k < PositionsTotal(); k++)
|
|
{
|
|
const ulong t = PositionGetTicket(k);
|
|
if(t == 0 || !PositionSelectByTicket(t))
|
|
continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != WorkSym())
|
|
continue;
|
|
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
|
continue;
|
|
g_lastPosId = (ulong)PositionGetInteger(POSITION_IDENTIFIER);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int OnInit()
|
|
{
|
|
if(InpBaseLots <= 0.0 || InpLotMultiplier < 1.0 || InpMaxDoublings < 0)
|
|
return INIT_PARAMETERS_INCORRECT;
|
|
if(!SymbolSelect(WorkSym(), true))
|
|
Print("Martingale: SymbolSelect note ", WorkSym());
|
|
g_hRsi = iRSI(WorkSym(), InpTf, InpRsiPeriod, PRICE_CLOSE);
|
|
if(g_hRsi == INVALID_HANDLE)
|
|
return INIT_FAILED;
|
|
g_trade.SetExpertMagicNumber(InpMagic);
|
|
g_trade.SetDeviationInPoints(InpSlippagePts);
|
|
SetFilling();
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
void OnDeinit(const int reason)
|
|
{
|
|
if(g_hRsi != INVALID_HANDLE)
|
|
IndicatorRelease(g_hRsi);
|
|
}
|
|
|
|
void OnTick()
|
|
{
|
|
if(_Symbol != WorkSym())
|
|
return;
|
|
|
|
if(g_lastPosId != 0 && !OurPositionOpenById(g_lastPosId))
|
|
OnClosedPosition();
|
|
|
|
static datetime lastBar = 0;
|
|
const datetime tb = iTime(WorkSym(), InpTf, 0);
|
|
if(tb == 0 || tb == lastBar)
|
|
return;
|
|
lastBar = tb;
|
|
|
|
if(OurPositionCount() > 0)
|
|
return;
|
|
|
|
double rsi1 = 0.0;
|
|
if(!CopyRsi1(rsi1))
|
|
return;
|
|
|
|
const double lot = LotsNow();
|
|
if(lot <= 0.0)
|
|
return;
|
|
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(WorkSym(), tick))
|
|
return;
|
|
|
|
double sl = 0.0, tp = 0.0;
|
|
const bool wantBuy = (rsi1 <= InpRsiBuyBelow);
|
|
const bool wantSell = (rsi1 >= InpRsiSellAbove);
|
|
|
|
if(wantBuy && !wantSell)
|
|
{
|
|
BuildSLTP(true, tick.ask, sl, tp);
|
|
if(g_trade.Buy(lot, WorkSym(), tick.ask, sl, tp, "Martingale buy"))
|
|
CaptureLastPositionId();
|
|
}
|
|
else if(wantSell && !wantBuy)
|
|
{
|
|
BuildSLTP(false, tick.bid, sl, tp);
|
|
if(g_trade.Sell(lot, WorkSym(), tick.bid, sl, tp, "Martingale sell"))
|
|
CaptureLastPositionId();
|
|
}
|
|
}
|