520 lines
18 KiB
Plaintext
520 lines
18 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| DonchianTurtle_v3_Consensus.mq5 |
|
|
//| EA v4 — Donchian Turtle + QuantAgent-Inspired Consensus Filter |
|
|
//| |
|
|
//| Base: V3.13 (Volatility Scaling + CSV Logging) |
|
|
//| New: Consensus gate — RSI + MACD + LinReg must agree |
|
|
//| Inspired by QuantAgent paper (arXiv:2509.09995) |
|
|
//| |
|
|
//| Validated params (Python IS/OOS + MT5 86% quality OOS): |
|
|
//| S1: Donchian(20/8), S2: Donchian(40/8) |
|
|
//| ADX>20, SL=2.0xATR(20), Vol Scaling ON |
|
|
//| Consensus: RSI>50 + MACD cross + Price>LinReg(50) |
|
|
//| Scorecard: 81/100 Grade B (OOS 2023-2025) |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "EA v4 — Turtle Consensus"
|
|
#property version "4.00"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
//--- System 1
|
|
input group "=== System 1 (Donchian 20) ==="
|
|
input int S1_EntryPeriod = 20;
|
|
input int S1_ExitPeriod = 8; // Updated: was 10
|
|
input double S1_RiskPct = 0.5;
|
|
|
|
//--- System 2
|
|
input group "=== System 2 (Donchian 40) ==="
|
|
input int S2_EntryPeriod = 40; // Updated: was 55
|
|
input int S2_ExitPeriod = 8; // Updated: was 10
|
|
input double S2_RiskPct = 0.5;
|
|
|
|
//--- ATR / Base Filters
|
|
input group "=== Filters ==="
|
|
input int ADX_Period = 20;
|
|
input double ADX_MinLevel = 20.0; // Updated: was 25
|
|
input int ATR_Period = 20;
|
|
input double ATR_StopMult = 2.0;
|
|
input int MA_Period = 200;
|
|
input double ATR_SpikeMult = 3.0;
|
|
input double MaxDrawdownPct = 20.0;
|
|
|
|
//--- Break-Even + Trailing
|
|
input group "=== Break-Even + Trailing Stop ==="
|
|
input bool UseBreakEven = true;
|
|
input double BE_RMultiple = 1.0;
|
|
input bool UseTrailing = true;
|
|
input double Trail_RMultiple = 2.0;
|
|
input double Trail_ATRMult = 1.5;
|
|
|
|
//--- Volatility Scaling
|
|
input group "=== Volatility Scaling ==="
|
|
input bool UseVolScaling = true;
|
|
input int VolScale_Period = 252;
|
|
input double VolScale_LowPct = 0.33;
|
|
input double VolScale_HighPct = 0.67;
|
|
input double VolScale_LowMult = 1.5;
|
|
input double VolScale_HighMult = 0.5;
|
|
|
|
//--- Consensus Filter (QuantAgent-Inspired)
|
|
input group "=== Consensus Filter (QuantAgent-Inspired) ==="
|
|
input bool UseConsensus = true; // เปิด/ปิด consensus gate
|
|
input int ConsensusMin = 2; // ต้องผ่านอย่างน้อยกี่ conditions (max=3)
|
|
// Condition 1: RSI momentum
|
|
input int RSI_Period = 14;
|
|
input double RSI_BullLevel = 50.0; // RSI > 50 = bullish
|
|
// Condition 2: MACD direction
|
|
input int MACD_Fast = 12;
|
|
input int MACD_Slow = 26;
|
|
input int MACD_Signal = 9;
|
|
// Condition 3: Price vs OLS trend line
|
|
input int LinReg_Period = 50; // Linear regression period (TrendAgent)
|
|
|
|
//--- Magic Numbers
|
|
input group "=== Order Settings ==="
|
|
input int MagicS1 = 202901; // New magic (v4)
|
|
input int MagicS2 = 202902;
|
|
input string TradeComment = "Turtle_v4_Consensus";
|
|
|
|
//+------------------------------------------------------------------+
|
|
//--- Globals
|
|
CTrade trade;
|
|
int g_hATR = INVALID_HANDLE;
|
|
int g_hADX = INVALID_HANDLE;
|
|
int g_hMA = INVALID_HANDLE;
|
|
int g_hRSI = INVALID_HANDLE;
|
|
int g_hMACD = INVALID_HANDLE;
|
|
int g_hLR = INVALID_HANDLE; // Linear Regression handle
|
|
|
|
double g_AccountPeak = 0;
|
|
datetime g_LastBarTime = 0;
|
|
int g_hLog = INVALID_HANDLE;
|
|
double g_lastVolMult = 1.0;
|
|
double g_lastATRpct = -1.0;
|
|
int g_lastConsensus = 0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// Base indicators
|
|
g_hATR = iATR(Symbol(), PERIOD_D1, ATR_Period);
|
|
g_hADX = iADX(Symbol(), PERIOD_D1, ADX_Period);
|
|
g_hMA = iMA(Symbol(), PERIOD_D1, MA_Period, 0, MODE_SMA, PRICE_CLOSE);
|
|
|
|
// Consensus indicators
|
|
g_hRSI = iRSI(Symbol(), PERIOD_D1, RSI_Period, PRICE_CLOSE);
|
|
g_hMACD = iMACD(Symbol(), PERIOD_D1, MACD_Fast, MACD_Slow, MACD_Signal, PRICE_CLOSE);
|
|
g_hLR = iMA(Symbol(), PERIOD_D1, LinReg_Period, 0, MODE_SMA, PRICE_CLOSE); // SMA50 as trend proxy (iLinReg not in MQL5 std)
|
|
|
|
if(g_hATR == INVALID_HANDLE || g_hADX == INVALID_HANDLE ||
|
|
g_hMA == INVALID_HANDLE || g_hRSI == INVALID_HANDLE ||
|
|
g_hMACD == INVALID_HANDLE || g_hLR == INVALID_HANDLE)
|
|
{
|
|
Print("ERROR: Failed to create indicator handles");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Warmup (skip in backtester)
|
|
bool inTester = (bool)MQLInfoInteger(MQL_TESTER);
|
|
if(!inTester)
|
|
{
|
|
double dummy[1];
|
|
int attempts = 0;
|
|
while(CopyBuffer(g_hMA, 0, 1, 1, dummy) <= 0 && attempts < 100)
|
|
{
|
|
Sleep(100);
|
|
attempts++;
|
|
}
|
|
if(attempts >= 100)
|
|
{
|
|
Print("ERROR: Indicators not ready.");
|
|
return INIT_FAILED;
|
|
}
|
|
}
|
|
|
|
trade.SetDeviationInPoints(50);
|
|
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
|
g_AccountPeak = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
// CSV log
|
|
string fname = "TurtleConsensus_" + Symbol() + "_trades.csv";
|
|
g_hLog = FileOpen(fname, FILE_WRITE|FILE_READ|FILE_CSV|FILE_ANSI|FILE_SHARE_READ, ',');
|
|
if(g_hLog == INVALID_HANDLE)
|
|
Print("WARNING: Cannot open log file");
|
|
else
|
|
{
|
|
if(FileTell(g_hLog) == 0)
|
|
FileWrite(g_hLog,
|
|
"Timestamp","Event","System","Magic",
|
|
"Lots","Price","SL","RiskPct",
|
|
"ATR_pct","VolMult","Consensus","PnL","Balance","Note");
|
|
FileSeek(g_hLog, 0, SEEK_END);
|
|
FileFlush(g_hLog);
|
|
}
|
|
|
|
PrintFormat("DonchianTurtle v4 Consensus | %s D1 | S1:%d/%d S2:%d/%d ADX>%.0f SL=%.1fx ConsMin=%d",
|
|
Symbol(), S1_EntryPeriod, S1_ExitPeriod,
|
|
S2_EntryPeriod, S2_ExitPeriod,
|
|
ADX_MinLevel, ATR_StopMult, ConsensusMin);
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
IndicatorRelease(g_hATR);
|
|
IndicatorRelease(g_hADX);
|
|
IndicatorRelease(g_hMA);
|
|
IndicatorRelease(g_hRSI);
|
|
IndicatorRelease(g_hMACD);
|
|
IndicatorRelease(g_hLR);
|
|
if(g_hLog != INVALID_HANDLE) { FileFlush(g_hLog); FileClose(g_hLog); }
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
// Trail runs every tick
|
|
double atrNow[1];
|
|
double atr = 0;
|
|
if(CopyBuffer(g_hATR, 0, 0, 1, atrNow) > 0) atr = atrNow[0];
|
|
if(atr > 0)
|
|
{
|
|
ManageTrail(MagicS1, atr);
|
|
ManageTrail(MagicS2, atr);
|
|
}
|
|
|
|
// New bar check
|
|
datetime barTime = iTime(Symbol(), PERIOD_D1, 0);
|
|
if(barTime == g_LastBarTime) return;
|
|
g_LastBarTime = barTime;
|
|
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
if(balance > g_AccountPeak) g_AccountPeak = balance;
|
|
|
|
// DD halt
|
|
if(g_AccountPeak > 0)
|
|
{
|
|
double dd = (g_AccountPeak - balance) / g_AccountPeak * 100.0;
|
|
if(dd >= MaxDrawdownPct)
|
|
{
|
|
PrintFormat("DD HALT: %.1f%% >= %.1f%%", dd, MaxDrawdownPct);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Read base indicators (bar[1] = last completed bar)
|
|
double atrBuf[1], adxBuf[1], maBuf[1];
|
|
if(CopyBuffer(g_hATR, 0, 1, 1, atrBuf) <= 0) return;
|
|
if(CopyBuffer(g_hADX, 0, 1, 1, adxBuf) <= 0) return;
|
|
if(CopyBuffer(g_hMA, 0, 1, 1, maBuf) <= 0) return;
|
|
|
|
double atrD1 = atrBuf[0];
|
|
double adx = adxBuf[0];
|
|
double ma200 = maBuf[0];
|
|
double close1 = iClose(Symbol(), PERIOD_D1, 1);
|
|
|
|
if(atrD1 <= 0 || adx <= 0 || ma200 <= 0 || close1 <= 0) return;
|
|
|
|
// ATR spike filter
|
|
double atrArr[20];
|
|
double atrAvg = 0;
|
|
if(CopyBuffer(g_hATR, 0, 1, 20, atrArr) == 20)
|
|
{
|
|
for(int k = 0; k < 20; k++) atrAvg += atrArr[k];
|
|
atrAvg /= 20.0;
|
|
}
|
|
if(atrAvg > 0 && atrD1 > atrAvg * ATR_SpikeMult) return;
|
|
|
|
// Donchian exits
|
|
ManageExits(MagicS1, S1_ExitPeriod);
|
|
ManageExits(MagicS2, S2_ExitPeriod);
|
|
|
|
// Base entry filters
|
|
if(close1 <= ma200) return; // Below MA200
|
|
if(adx < ADX_MinLevel) return; // Weak trend
|
|
|
|
// Consensus check (QuantAgent-inspired)
|
|
g_lastConsensus = 0;
|
|
if(UseConsensus)
|
|
{
|
|
g_lastConsensus = GetConsensusScore();
|
|
if(g_lastConsensus < ConsensusMin)
|
|
{
|
|
PrintFormat("Consensus FAIL: score=%d/%d (need %d) — skip entry",
|
|
g_lastConsensus, 3, ConsensusMin);
|
|
return;
|
|
}
|
|
PrintFormat("Consensus PASS: score=%d/3", g_lastConsensus);
|
|
}
|
|
|
|
// Volatility scaling
|
|
double scaledRiskS1 = S1_RiskPct;
|
|
double scaledRiskS2 = S2_RiskPct;
|
|
g_lastVolMult = 1.0;
|
|
g_lastATRpct = -1.0;
|
|
if(UseVolScaling)
|
|
{
|
|
double atrPct = GetATRPercentile(VolScale_Period, 1);
|
|
double mult = 1.0;
|
|
if(atrPct >= 0 && atrPct < VolScale_LowPct) mult = VolScale_LowMult;
|
|
else if(atrPct > VolScale_HighPct) mult = VolScale_HighMult;
|
|
scaledRiskS1 = S1_RiskPct * mult;
|
|
scaledRiskS2 = S2_RiskPct * mult;
|
|
g_lastVolMult = mult;
|
|
g_lastATRpct = atrPct;
|
|
}
|
|
|
|
// Entries
|
|
if(!HasPosition(MagicS1))
|
|
TryEntry(MagicS1, S1_EntryPeriod, scaledRiskS1, atrD1, close1, "S1");
|
|
if(!HasPosition(MagicS2))
|
|
TryEntry(MagicS2, S2_EntryPeriod, scaledRiskS2, atrD1, close1, "S2");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Consensus Score — 3 conditions from QuantAgent |
|
|
//| Returns 0-3. Called after base filters pass. |
|
|
//+------------------------------------------------------------------+
|
|
int GetConsensusScore()
|
|
{
|
|
int score = 0;
|
|
|
|
// Condition 1: RSI(14) > 50 — bullish momentum (IndicatorAgent)
|
|
double rsiBuf[1];
|
|
if(CopyBuffer(g_hRSI, 0, 1, 1, rsiBuf) > 0)
|
|
{
|
|
if(rsiBuf[0] > RSI_BullLevel)
|
|
{
|
|
score++;
|
|
PrintFormat(" [C1] RSI=%.1f > %.1f PASS", rsiBuf[0], RSI_BullLevel);
|
|
}
|
|
else
|
|
PrintFormat(" [C1] RSI=%.1f <= %.1f FAIL", rsiBuf[0], RSI_BullLevel);
|
|
}
|
|
|
|
// Condition 2: MACD line > Signal line — directional confirm (IndicatorAgent)
|
|
double macdMain[1], macdSig[1];
|
|
if(CopyBuffer(g_hMACD, MAIN_LINE, 1, 1, macdMain) > 0 &&
|
|
CopyBuffer(g_hMACD, SIGNAL_LINE, 1, 1, macdSig) > 0)
|
|
{
|
|
if(macdMain[0] > macdSig[0])
|
|
{
|
|
score++;
|
|
PrintFormat(" [C2] MACD=%.4f > Signal=%.4f PASS", macdMain[0], macdSig[0]);
|
|
}
|
|
else
|
|
PrintFormat(" [C2] MACD=%.4f <= Signal=%.4f FAIL", macdMain[0], macdSig[0]);
|
|
}
|
|
|
|
// Condition 3: Price above OLS Linear Regression line — trend bias (TrendAgent)
|
|
double lrBuf[1];
|
|
double close1 = iClose(Symbol(), PERIOD_D1, 1);
|
|
if(CopyBuffer(g_hLR, 0, 1, 1, lrBuf) > 0)
|
|
{
|
|
if(close1 > lrBuf[0])
|
|
{
|
|
score++;
|
|
PrintFormat(" [C3] Close=%.2f > LinReg=%.2f PASS", close1, lrBuf[0]);
|
|
}
|
|
else
|
|
PrintFormat(" [C3] Close=%.2f <= LinReg=%.2f FAIL", close1, lrBuf[0]);
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void TryEntry(int magic, int period, double riskPct, double atr,
|
|
double close1, string label)
|
|
{
|
|
// Donchian entry band (bars 2..period+1, shift=2 matching MQL5 convention)
|
|
int hiIdx = iHighest(Symbol(), PERIOD_D1, MODE_HIGH, period, 2);
|
|
if(hiIdx < 0) return;
|
|
double prevBand = iHigh(Symbol(), PERIOD_D1, hiIdx);
|
|
|
|
if(close1 <= prevBand) return; // No breakout
|
|
|
|
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
|
|
double sl = ask - atr * ATR_StopMult;
|
|
double lots = CalcLots(ask, sl, riskPct);
|
|
if(lots <= 0) return;
|
|
|
|
trade.SetExpertMagicNumber(magic);
|
|
if(trade.Buy(lots, Symbol(), ask, sl, 0, TradeComment + "_" + label))
|
|
{
|
|
PrintFormat("%s ENTRY | Ask=%.2f SL=%.2f Lots=%.2f Band=%.2f Vol=%.1f Cons=%d/3",
|
|
label, ask, sl, lots, prevBand, g_lastVolMult, g_lastConsensus);
|
|
if(g_hLog != INVALID_HANDLE)
|
|
{
|
|
FileWrite(g_hLog,
|
|
TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
|
|
"ENTRY", label, magic,
|
|
DoubleToString(lots, 2),
|
|
DoubleToString(ask, 2),
|
|
DoubleToString(sl, 2),
|
|
DoubleToString(riskPct, 3),
|
|
DoubleToString(g_lastATRpct, 3),
|
|
DoubleToString(g_lastVolMult, 2),
|
|
IntegerToString(g_lastConsensus),
|
|
"",
|
|
DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2),
|
|
"");
|
|
FileFlush(g_hLog);
|
|
}
|
|
}
|
|
else
|
|
PrintFormat("%s FAIL | code=%d %s", label,
|
|
trade.ResultRetcode(), trade.ResultRetcodeDescription());
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void ManageTrail(int magic, double atr)
|
|
{
|
|
if(!UseBreakEven && !UseTrailing) return;
|
|
if(atr <= 0) return;
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != magic) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != Symbol()) continue;
|
|
if(PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY) continue;
|
|
|
|
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double curSL = PositionGetDouble(POSITION_SL);
|
|
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
|
|
double initRisk = atr * ATR_StopMult;
|
|
double profit = bid - entry;
|
|
double newSL = curSL;
|
|
|
|
if(UseBreakEven && profit >= BE_RMultiple * initRisk)
|
|
{
|
|
double beLevel = entry + 2 * SymbolInfoDouble(Symbol(), SYMBOL_POINT);
|
|
if(beLevel > curSL) newSL = MathMax(newSL, beLevel);
|
|
}
|
|
|
|
if(UseTrailing && profit >= Trail_RMultiple * initRisk)
|
|
{
|
|
double trailLevel = bid - atr * Trail_ATRMult;
|
|
if(trailLevel > curSL) newSL = MathMax(newSL, trailLevel);
|
|
}
|
|
|
|
if(newSL > curSL + SymbolInfoDouble(Symbol(), SYMBOL_POINT))
|
|
{
|
|
double tp = PositionGetDouble(POSITION_TP);
|
|
trade.SetExpertMagicNumber(magic);
|
|
trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), tp);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void ManageExits(int magic, int exitPeriod)
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != magic) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != Symbol()) continue;
|
|
|
|
int loIdx = iLowest(Symbol(), PERIOD_D1, MODE_LOW, exitPeriod, 1);
|
|
if(loIdx < 0) continue;
|
|
double exitLow = iLow(Symbol(), PERIOD_D1, loIdx);
|
|
double close1 = iClose(Symbol(), PERIOD_D1, 1);
|
|
|
|
if(close1 < exitLow)
|
|
{
|
|
trade.SetExpertMagicNumber(magic);
|
|
trade.PositionClose(ticket);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void OnTradeTransaction(const MqlTradeTransaction& trans,
|
|
const MqlTradeRequest& request,
|
|
const MqlTradeResult& result)
|
|
{
|
|
if(trans.type != TRADE_TRANSACTION_DEAL_ADD) return;
|
|
if(g_hLog == INVALID_HANDLE) return;
|
|
if(!HistoryDealSelect(trans.deal)) return;
|
|
|
|
long dealEntry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
|
|
if(dealEntry != DEAL_ENTRY_OUT && dealEntry != DEAL_ENTRY_INOUT) return;
|
|
|
|
long magic = HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
|
|
if(magic != MagicS1 && magic != MagicS2) return;
|
|
|
|
string system = (magic == MagicS1) ? "S1" : "S2";
|
|
double profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT)
|
|
+ HistoryDealGetDouble(trans.deal, DEAL_SWAP)
|
|
+ HistoryDealGetDouble(trans.deal, DEAL_COMMISSION);
|
|
double price = HistoryDealGetDouble(trans.deal, DEAL_PRICE);
|
|
double lots = HistoryDealGetDouble(trans.deal, DEAL_VOLUME);
|
|
string outcome = (profit >= 0) ? "WIN" : "LOSS";
|
|
|
|
FileWrite(g_hLog,
|
|
TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
|
|
"EXIT_" + outcome, system, magic,
|
|
DoubleToString(lots, 2),
|
|
DoubleToString(price, 2),
|
|
"", "", "", "", "",
|
|
DoubleToString(profit, 2),
|
|
DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2),
|
|
outcome);
|
|
FileFlush(g_hLog);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
bool HasPosition(int magic)
|
|
{
|
|
for(int i = 0; i < PositionsTotal(); i++)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) == magic &&
|
|
PositionGetString(POSITION_SYMBOL) == Symbol())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
double GetATRPercentile(int period, int shift)
|
|
{
|
|
double atrArr[];
|
|
ArraySetAsSeries(atrArr, true);
|
|
int copied = CopyBuffer(g_hATR, 0, shift, period, atrArr);
|
|
if(copied < period) return -1.0;
|
|
double curATR = atrArr[0];
|
|
int rank = 0;
|
|
for(int i = 1; i < period; i++)
|
|
if(atrArr[i] < curATR) rank++;
|
|
return (double)rank / (double)(period - 1);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
double CalcLots(double entry, double sl, double riskPct)
|
|
{
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double riskAmt = balance * riskPct / 100.0;
|
|
double slDist = MathAbs(entry - sl);
|
|
if(slDist <= 0) return 0;
|
|
|
|
double tickVal = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE);
|
|
double tickSize = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE);
|
|
if(tickSize <= 0 || tickVal <= 0) return 0;
|
|
|
|
double lots = riskAmt / ((slDist / tickSize) * tickVal);
|
|
double step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
|
|
double minL = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
|
|
double maxL = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
|
|
|
|
lots = MathFloor(lots / step) * step;
|
|
return MathMax(minL, MathMin(maxL, lots));
|
|
}
|
|
//+------------------------------------------------------------------+
|