This commit is contained in:
zhutoutoutousan
2026-05-02 15:55:04 +02:00
parent 0b4a70b843
commit b5acd37754
198 changed files with 18739 additions and 11364 deletions
+437
View File
@@ -0,0 +1,437 @@
//+------------------------------------------------------------------+
//| RSIScalping.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.03"
#include <Trade\Trade.mqh>
#include "../_united/MagicNumberHelpers.mqh"
//--- Input parameters
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // Timeframe for Analysis
input int RSI_Period = 14; // RSI Period
input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE; // RSI Applied Price
input double RSI_Overbought = 71; // RSI Overbought Level
input double RSI_Oversold = 57; // RSI Oversold Level
input bool UseEntrySlopeFilter = false; // require RSI momentum on entry bars
input double EntryMinSlopePerBar = 1.0; // minimum RSI delta per bar for entry
input double RSI_Target_Buy = 80; // RSI Target for Buy Exit
input double RSI_Target_Sell = 57; // RSI Target for Sell Exit
input int BarsToWait = 4; // Bars to wait when RSI goes against position
input bool ExitOnAdverseRsiBarStep = true; // new bar: exit if last closed RSI vs prior closed is against trade
input double LotSize = 0.1; // Lot Size
input int MagicNumber = 129102315; // Magic Number
input int Slippage = 3; // Slippage in points
input group "=== Reversal escape (intrabar, multi-signal) ==="
input bool UseReversalEscape = true; // run while in position every tick
input int ReversalATRPeriod = 14; // ATR lookback on signal timeframe
input double ReversalAdverseAtrMult = 5.25; // close if price vs entry >= this * ATR
input int ReversalSignsRequired = 2; // how many independent signs must align
input double ReversalRsiVelocity = 16.0; // RSI points drop (long) / rise (short) vs prior buffer
input double ReversalBodyAtrMult = 5.1; // last closed bar body >= this * ATR counts as one sign
//--- Global variables
CTrade trade;
int rsi_handle;
double rsi_buffer[];
double rsi_prev, rsi_current, rsi_two_bars_ago;
bool position_open = false;
int position_ticket = 0;
ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY;
datetime last_bar_time = 0;
bool rsi_against_position = false;
int bars_against_count = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Initialize RSI indicator
rsi_handle = iRSI(_Symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
if(rsi_handle == INVALID_HANDLE)
{
return(INIT_FAILED);
}
// Initialize trade object
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
trade.SetTypeFilling(ORDER_FILLING_FOK);
// Allocate arrays
ArraySetAsSeries(rsi_buffer, true);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(rsi_handle != INVALID_HANDLE)
IndicatorRelease(rsi_handle);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(Bars(_Symbol, TimeFrame) < RSI_Period + 2)
return;
const datetime current_bar_time = iTime(_Symbol, TimeFrame, 0);
const bool new_bar = (current_bar_time != last_bar_time);
const bool in_pos = position_open || PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
if(!in_pos && !new_bar)
return;
if(!UpdateRSI())
return;
if(in_pos && UseReversalEscape)
TryReversalEscape();
if(!new_bar)
return;
last_bar_time = current_bar_time;
ResyncPositionFromMarket();
CheckExistingPosition();
if(!position_open && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
CheckEntrySignals();
}
//+------------------------------------------------------------------+
//| Update RSI values |
//+------------------------------------------------------------------+
bool UpdateRSI()
{
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3)
{
return false;
}
rsi_current = rsi_buffer[0]; // Current bar
rsi_prev = rsi_buffer[1]; // Previous bar
rsi_two_bars_ago = rsi_buffer[2]; // Two bars ago
return true;
}
//+------------------------------------------------------------------+
//| Wilder ATR in price units (signal timeframe) |
//+------------------------------------------------------------------+
double ATRPriceOnTF(const int period)
{
if(period < 1)
return 0.0;
MqlRates rates[];
const int need = period + 2;
if(CopyRates(_Symbol, TimeFrame, 0, need, rates) < need)
return 0.0;
ArraySetAsSeries(rates, true);
double sum = 0.0;
for(int i = 1; i <= period; i++)
{
const double hl = rates[i].high - rates[i].low;
const double hc = MathAbs(rates[i].high - rates[i + 1].close);
const double lc = MathAbs(rates[i].low - rates[i + 1].close);
sum += MathMax(hl, MathMax(hc, lc));
}
return sum / (double)period;
}
//+------------------------------------------------------------------+
//| Independent adverse signs (need ReversalSignsRequired to exit) |
//+------------------------------------------------------------------+
int CountReversalEscapeSigns(const ENUM_POSITION_TYPE ptype, const double atr)
{
if(atr <= 0.0)
return 0;
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
int signs = 0;
if(ptype == POSITION_TYPE_BUY)
{
if(entry - bid >= ReversalAdverseAtrMult * atr)
signs++;
if(rsi_prev - rsi_current >= ReversalRsiVelocity)
signs++;
}
else if(ptype == POSITION_TYPE_SELL)
{
if(ask - entry >= ReversalAdverseAtrMult * atr)
signs++;
if(rsi_current - rsi_prev >= ReversalRsiVelocity)
signs++;
}
else
return 0;
MqlRates r[];
if(CopyRates(_Symbol, TimeFrame, 0, 4, r) >= 4)
{
ArraySetAsSeries(r, true);
const double body = MathAbs(r[1].close - r[1].open);
if(body >= ReversalBodyAtrMult * atr)
{
if(ptype == POSITION_TYPE_BUY && r[1].close < r[1].open)
signs++;
else if(ptype == POSITION_TYPE_SELL && r[1].close > r[1].open)
signs++;
}
if(ptype == POSITION_TYPE_BUY)
{
if(r[1].close < r[2].close && r[2].close < r[3].close)
signs++;
}
else
{
if(r[1].close > r[2].close && r[2].close > r[3].close)
signs++;
}
}
return signs;
}
//+------------------------------------------------------------------+
//| Cut losers fast on violent reversals (evaluated every tick) |
//+------------------------------------------------------------------+
void TryReversalEscape()
{
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
return;
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
const double atr = ATRPriceOnTF(ReversalATRPeriod);
if(atr <= 0.0)
return;
const int n = CountReversalEscapeSigns(ptype, atr);
if(n < ReversalSignsRequired)
return;
ClosePosition();
Print("RSIScalpingXAUUSD: reversal escape signs=", n, " need=", ReversalSignsRequired,
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
}
void ResyncPositionFromMarket()
{
if(position_open)
return;
ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
if(t == 0 || !PositionSelectByTicket(t))
return;
position_ticket = (int)t;
position_open = true;
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
}
//+------------------------------------------------------------------+
//| Check existing position for exit conditions |
//+------------------------------------------------------------------+
void CheckExistingPosition()
{
if(!position_open)
{
return;
}
// Check if position still exists with correct magic number
if(!PositionSelectByTicketAndMagic(position_ticket, MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
// On each new bar: last completed RSI vs the bar before — exit if that step is adverse to the position
if(ExitOnAdverseRsiBarStep)
{
if(current_position_type == POSITION_TYPE_BUY && rsi_prev < rsi_two_bars_ago)
{
ClosePosition();
return;
}
if(current_position_type == POSITION_TYPE_SELL && rsi_prev > rsi_two_bars_ago)
{
ClosePosition();
return;
}
}
// Exit conditions based on RSI target
if(current_position_type == POSITION_TYPE_BUY)
{
// Check if RSI is against the position (below oversold)
if(rsi_current < RSI_Oversold)
{
if(!rsi_against_position)
{
rsi_against_position = true;
bars_against_count = 1;
}
else
{
bars_against_count++;
}
// Close position if RSI has been against for Y bars
if(bars_against_count >= BarsToWait)
{
ClosePosition();
return;
}
}
else
{
// RSI is no longer against the position, reset counter
if(rsi_against_position)
{
rsi_against_position = false;
bars_against_count = 0;
}
// Exit long position when RSI reaches buy target
if(rsi_current >= RSI_Target_Buy)
{
ClosePosition();
}
}
}
else if(current_position_type == POSITION_TYPE_SELL)
{
// Check if RSI is against the position (above overbought)
if(rsi_current > RSI_Overbought)
{
if(!rsi_against_position)
{
rsi_against_position = true;
bars_against_count = 1;
}
else
{
bars_against_count++;
}
// Close position if RSI has been against for Y bars
if(bars_against_count >= BarsToWait)
{
ClosePosition();
return;
}
}
else
{
// RSI is no longer against the position, reset counter
if(rsi_against_position)
{
rsi_against_position = false;
bars_against_count = 0;
}
// Exit short position when RSI reaches sell target
if(rsi_current <= RSI_Target_Sell)
{
ClosePosition();
}
}
}
}
//+------------------------------------------------------------------+
//| Check for entry signals |
//+------------------------------------------------------------------+
void CheckEntrySignals()
{
const double upSlope1 = rsi_prev - rsi_two_bars_ago; // older->prev
const double upSlope2 = rsi_current - rsi_prev; // prev->current
const double dnSlope1 = rsi_two_bars_ago - rsi_prev; // older->prev
const double dnSlope2 = rsi_prev - rsi_current; // prev->current
const bool buySlopeOk = (!UseEntrySlopeFilter) || (upSlope1 >= EntryMinSlopePerBar && upSlope2 >= EntryMinSlopePerBar);
const bool sellSlopeOk = (!UseEntrySlopeFilter) || (dnSlope1 >= EntryMinSlopePerBar && dnSlope2 >= EntryMinSlopePerBar);
// Buy signal: RSI crosses from oversold to above oversold (checking the actual crossover)
if(rsi_two_bars_ago <= RSI_Oversold && rsi_prev > RSI_Oversold && buySlopeOk)
{
OpenBuyPosition();
}
// Sell signal: RSI crosses from overbought to below overbought (checking the actual crossover)
if(rsi_two_bars_ago >= RSI_Overbought && rsi_prev < RSI_Overbought && sellSlopeOk)
{
OpenSellPosition();
}
}
//+------------------------------------------------------------------+
//| Open buy position |
//+------------------------------------------------------------------+
void OpenBuyPosition()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Scalping Buy"))
{
position_ticket = trade.ResultOrder();
position_open = true;
current_position_type = POSITION_TYPE_BUY;
}
}
//+------------------------------------------------------------------+
//| Open sell position |
//+------------------------------------------------------------------+
void OpenSellPosition()
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Scalping Sell"))
{
position_ticket = trade.ResultOrder();
position_open = true;
current_position_type = POSITION_TYPE_SELL;
}
}
//+------------------------------------------------------------------+
//| Close current position |
//+------------------------------------------------------------------+
void ClosePosition()
{
if(ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
if(!PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
Print("RSIScalpingXAUUSD: close failed (will retry on next bar). retcode=",
trade.ResultRetcode(), " lastError=", GetLastError());
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

@@ -0,0 +1,284 @@
#property strict
#property version "1.00"
#include <Trade/Trade.mqh>
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_M10; // Higher timeframe for MA/cross points
input int InpMAPeriod = 65; // MA period
input ENUM_MA_METHOD InpMAMethod = MODE_EMA; // MA method
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_OPEN; // MA applied price
input int InpHTFBarsToScan = 500; // HTF bars to scan for crossings
input double InpLineTouchTolerance = 220; // Pullback touch tolerance (points)
input double InpBreakBuffer = 110; // Break confirmation buffer (points)
input double InpLots = 0.10; // Position size
input long InpMagic = 26042501; // Magic number
input bool InpDrawTrendline = true; // Draw detected trendline
CTrade trade;
int g_maHandle = INVALID_HANDLE;
datetime g_lastBarTime = 0;
string g_lineName = "SimpleTrendline_Basis";
struct TrendlineModel
{
datetime t1;
datetime t2;
datetime t3;
double p1;
double p2;
double p3;
double a;
double b;
bool valid;
};
bool IsNewBar()
{
datetime t = iTime(_Symbol, _Period, 0);
if(t == 0)
return false;
if(t != g_lastBarTime)
{
g_lastBarTime = t;
return true;
}
return false;
}
int FindRecentCrossPoints(datetime &times[], double &prices[])
{
ArrayResize(times, 0);
ArrayResize(prices, 0);
if(g_maHandle == INVALID_HANDLE)
return 0;
int needBars = MathMax(InpHTFBarsToScan, InpMAPeriod + 20);
MqlRates rates[];
double maBuf[];
int copiedRates = CopyRates(_Symbol, InpHigherTF, 0, needBars, rates);
int copiedMa = CopyBuffer(g_maHandle, 0, 0, needBars, maBuf);
if(copiedRates <= 5 || copiedMa <= 5)
return 0;
int bars = MathMin(copiedRates, copiedMa);
ArraySetAsSeries(rates, true);
ArraySetAsSeries(maBuf, true);
for(int i = 2; i < bars - 1; i++)
{
double d0 = rates[i].close - maBuf[i];
double d1 = rates[i + 1].close - maBuf[i + 1];
if(d0 == 0.0 || d1 == 0.0 || (d0 * d1 < 0.0))
{
int n = ArraySize(times);
ArrayResize(times, n + 1);
ArrayResize(prices, n + 1);
times[n] = rates[i].time;
prices[n] = rates[i].close;
if(ArraySize(times) >= 3)
break;
}
}
return ArraySize(times);
}
bool BuildTrendlineFrom3Points(TrendlineModel &m)
{
m.valid = false;
datetime ts[];
double ps[];
int n = FindRecentCrossPoints(ts, ps);
if(n < 3)
return false;
// We collected from recent to older in series order.
// Re-map as oldest -> newest to stabilize slope direction.
datetime tOld[3];
double pOld[3];
for(int i = 0; i < 3; i++)
{
tOld[i] = ts[2 - i];
pOld[i] = ps[2 - i];
}
long t0 = (long)tOld[0];
double x1 = 0.0;
double x2 = (double)((long)tOld[1] - t0);
double x3 = (double)((long)tOld[2] - t0);
double y1 = pOld[0];
double y2 = pOld[1];
double y3 = pOld[2];
double sx = x1 + x2 + x3;
double sy = y1 + y2 + y3;
double sxx = x1 * x1 + x2 * x2 + x3 * x3;
double sxy = x1 * y1 + x2 * y2 + x3 * y3;
double den = 3.0 * sxx - sx * sx;
if(MathAbs(den) < 1e-10)
return false;
m.a = (3.0 * sxy - sx * sy) / den;
m.b = (sy - m.a * sx) / 3.0;
m.t1 = tOld[0];
m.t2 = tOld[1];
m.t3 = tOld[2];
m.p1 = pOld[0];
m.p2 = pOld[1];
m.p3 = pOld[2];
m.valid = true;
return true;
}
double TrendlinePriceAtTime(const TrendlineModel &m, datetime t)
{
if(!m.valid)
return 0.0;
double x = (double)((long)t - (long)m.t1);
return m.a * x + m.b;
}
void DrawTrendline(const TrendlineModel &m)
{
if(!InpDrawTrendline || !m.valid)
return;
datetime tStart = m.t1;
datetime tEnd = iTime(_Symbol, _Period, 0);
if(tEnd <= tStart)
tEnd = m.t3 + PeriodSeconds(_Period) * 20;
double pStart = TrendlinePriceAtTime(m, tStart);
double pEnd = TrendlinePriceAtTime(m, tEnd);
if(ObjectFind(0, g_lineName) < 0)
ObjectCreate(0, g_lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd);
else
{
ObjectMove(0, g_lineName, 0, tStart, pStart);
ObjectMove(0, g_lineName, 1, tEnd, pEnd);
}
ObjectSetInteger(0, g_lineName, OBJPROP_RAY_RIGHT, true);
ObjectSetInteger(0, g_lineName, OBJPROP_COLOR, clrGold);
ObjectSetInteger(0, g_lineName, OBJPROP_WIDTH, 2);
}
bool GetCurrentPosition(long &type, double &volume)
{
if(!PositionSelect(_Symbol))
return false;
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
return false;
type = PositionGetInteger(POSITION_TYPE);
volume = PositionGetDouble(POSITION_VOLUME);
return true;
}
void TryExitOnBreak(const TrendlineModel &m)
{
long posType;
double vol;
if(!GetCurrentPosition(posType, vol))
return;
double close1 = iClose(_Symbol, _Period, 1);
datetime t1 = iTime(_Symbol, _Period, 1);
double line1 = TrendlinePriceAtTime(m, t1);
double buf = InpBreakBuffer * _Point;
bool closePos = false;
if(posType == POSITION_TYPE_BUY && close1 < (line1 - buf))
closePos = true;
if(posType == POSITION_TYPE_SELL && close1 > (line1 + buf))
closePos = true;
if(closePos)
trade.PositionClose(_Symbol);
}
void TryPullbackEntry(const TrendlineModel &m)
{
long posType;
double vol;
if(GetCurrentPosition(posType, vol))
return;
MqlRates bars1[], bars2[];
if(CopyRates(_Symbol, _Period, 1, 1, bars1) != 1)
return;
if(CopyRates(_Symbol, _Period, 2, 1, bars2) != 1)
return;
if(ArraySize(bars1) < 1 || ArraySize(bars2) < 1)
return;
MqlRates b1 = bars1[0];
MqlRates b2 = bars2[0];
double line1 = TrendlinePriceAtTime(m, b1.time);
double tol = InpLineTouchTolerance * _Point;
bool upTrend = (m.a > 0.0);
bool downTrend = (m.a < 0.0);
if(upTrend)
{
bool touched = (b1.low <= (line1 + tol));
bool reclaim = (b1.close > line1);
bool bullish = (b1.close > b1.open);
bool stillHealthy = (b2.close >= TrendlinePriceAtTime(m, b2.time) - tol);
if(touched && reclaim && bullish && stillHealthy)
{
trade.Buy(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback buy");
}
}
else if(downTrend)
{
bool touched = (b1.high >= (line1 - tol));
bool reject = (b1.close < line1);
bool bearish = (b1.close < b1.open);
bool stillWeak = (b2.close <= TrendlinePriceAtTime(m, b2.time) + tol);
if(touched && reject && bearish && stillWeak)
{
trade.Sell(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback sell");
}
}
}
int OnInit()
{
g_maHandle = iMA(_Symbol, InpHigherTF, InpMAPeriod, 0, InpMAMethod, InpAppliedPrice);
if(g_maHandle == INVALID_HANDLE)
return INIT_FAILED;
trade.SetExpertMagicNumber(InpMagic);
g_lastBarTime = 0;
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(g_maHandle != INVALID_HANDLE)
IndicatorRelease(g_maHandle);
if(ObjectFind(0, g_lineName) >= 0)
ObjectDelete(0, g_lineName);
}
void OnTick()
{
if(!IsNewBar())
return;
TrendlineModel m;
if(!BuildTrendlineFrom3Points(m))
return;
DrawTrendline(m);
TryExitOnBreak(m);
TryPullbackEntry(m);
}
@@ -0,0 +1,14 @@
; SimpleTrendline.mq5 optimization preset (TSLA-focused)
; Strategy Tester -> Inputs -> Load
; Focus: trendline pullback + break exits on TSLA volatility (no broker SL/TP)
;
InpHigherTF=16385||16385||1||16387||Y
InpMAPeriod=55||30||5||160||Y
InpMAMethod=1||0||1||3||Y
InpAppliedPrice=0||0||1||6||Y
InpHTFBarsToScan=500||250||50||1500||Y
InpLineTouchTolerance=65.0||20.0||5.0||180.0||Y
InpBreakBuffer=22.0||8.0||2.0||70.0||Y
InpLots=0.10||0.10||0.01||0.10||N
InpMagic=26042501||26042501||1||26042501||N
InpDrawTrendline=false||false||0||true||N
+21
View File
@@ -0,0 +1,21 @@
; saved on 2026.04.25
; optimization profile for double-top-bottom-catcher.mq5
; load this in Strategy Tester > Inputs tab > Load
;
InpTf=1||0||0||49153||N
InpHtf=5||0||0||49153||N
InpUseHtfFilter=true||false||0||true||Y
InpEmaFast=9||7||1||14||Y
InpEmaSlow=21||18||1||34||Y
InpPivotLeft=2||1||1||4||Y
InpPivotRight=2||1||1||4||Y
InpPatternLookbackBars=180||100||20||300||Y
InpMinPatternSeparation=6||4||1||12||Y
InpMaxPatternSeparation=50||20||5||80||Y
InpTopBottomTolPts=120.0||50.0||10.0||250.0||Y
InpMinEmaPriceDistPts=80.0||20.0||10.0||180.0||Y
InpPrevLevelLookback=120||50||10||250||Y
InpLots=0.01||0.01||0.0||0.01||N
InpSlBufferPts=25||10||5||60||Y
InpMagic=930101||930101||1||9301010||N
InpSlippagePts=30||10||5||50||Y
+350
View File
@@ -0,0 +1,350 @@
//+------------------------------------------------------------------+
//| double-top-bottom-catcher.mq5 |
//| Lab EA: Double top/bottom catcher with EMA distance + HTF trend |
//+------------------------------------------------------------------+
#property copyright "Lab"
#property version "1.00"
#include <Trade\Trade.mqh>
input ENUM_TIMEFRAMES InpTf = PERIOD_M1; // Signal timeframe
input ENUM_TIMEFRAMES InpHtf = PERIOD_M5; // Higher timeframe
input bool InpUseHtfFilter = true; // Require HTF trend alignment
input int InpEmaFast = 9; // Fast EMA
input int InpEmaSlow = 21; // Slow EMA
input int InpPivotLeft = 2; // Pivot bars left
input int InpPivotRight = 2; // Pivot bars right
input int InpPatternLookbackBars = 180; // Search range for patterns
input int InpMinPatternSeparation = 6; // Min bars between tops/bottoms
input int InpMaxPatternSeparation = 50; // Max bars between tops/bottoms
input double InpTopBottomTolPts = 120; // Max diff between top/top or bottom/bottom
input double InpMinEmaPriceDistPts = 80; // Min stretch from EMA at 2nd touch
input int InpPrevLevelLookback = 120; // Lookback to find previous support/resistance
input double InpLots = 0.01;
input int InpSlBufferPts = 25; // SL buffer beyond pattern extreme
input ulong InpMagic = 20260425;
input int InpSlippagePts = 30;
CTrade g_trade;
int g_hEmaFast = INVALID_HANDLE;
int g_hEmaSlow = INVALID_HANDLE;
int g_hEmaFastHtf = INVALID_HANDLE;
int g_hEmaSlowHtf = INVALID_HANDLE;
double g_emaFast[];
double g_emaSlow[];
double g_emaFastHtf[];
double g_emaSlowHtf[];
int OnInit()
{
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePts);
SetTradeFillingBySymbol();
g_hEmaFast = iMA(_Symbol, InpTf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
g_hEmaSlow = iMA(_Symbol, InpTf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
g_hEmaFastHtf = iMA(_Symbol, InpHtf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
g_hEmaSlowHtf = iMA(_Symbol, InpHtf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
if(g_hEmaFast == INVALID_HANDLE || g_hEmaSlow == INVALID_HANDLE ||
g_hEmaFastHtf == INVALID_HANDLE || g_hEmaSlowHtf == INVALID_HANDLE)
return INIT_FAILED;
ArraySetAsSeries(g_emaFast, true);
ArraySetAsSeries(g_emaSlow, true);
ArraySetAsSeries(g_emaFastHtf, true);
ArraySetAsSeries(g_emaSlowHtf, true);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(g_hEmaFast != INVALID_HANDLE) IndicatorRelease(g_hEmaFast);
if(g_hEmaSlow != INVALID_HANDLE) IndicatorRelease(g_hEmaSlow);
if(g_hEmaFastHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaFastHtf);
if(g_hEmaSlowHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaSlowHtf);
}
void OnTick()
{
static datetime lastBar = 0;
datetime barTime = iTime(_Symbol, InpTf, 0);
if(barTime == lastBar)
return;
lastBar = barTime;
const int need = MathMax(260, InpPatternLookbackBars + InpPrevLevelLookback + 20);
if(CopyBuffer(g_hEmaFast, 0, 0, need, g_emaFast) < need) return;
if(CopyBuffer(g_hEmaSlow, 0, 0, need, g_emaSlow) < need) return;
if(CopyBuffer(g_hEmaFastHtf, 0, 0, 5, g_emaFastHtf) < 5) return;
if(CopyBuffer(g_hEmaSlowHtf, 0, 0, 5, g_emaSlowHtf) < 5) return;
if(PositionExistsForMagic())
return;
TryEnterLongDoubleBottom();
if(!PositionExistsForMagic())
TryEnterShortDoubleTop();
}
void TryEnterLongDoubleBottom()
{
int firstBottom = -1; // older
int secondBottom = -1; // newer
double neckline = 0.0;
double lowA = 0.0;
double lowB = 0.0;
if(!FindDoubleBottom(firstBottom, secondBottom, neckline, lowA, lowB))
return;
const int c = 1;
double close1 = iClose(_Symbol, InpTf, c);
if(close1 <= neckline)
return; // wait for neckline break confirmation
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double stretched = g_emaFast[secondBottom] - lowB;
if(stretched < InpMinEmaPriceDistPts * pt)
return; // no enough EMA/price displacement for reversal
if(close1 <= g_emaFast[c])
return; // keep confirmation strict: close above EMA fast
if(InpUseHtfFilter && !(g_emaFastHtf[c] > g_emaSlowHtf[c]))
return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double sl = MathMin(lowA, lowB) - InpSlBufferPts * pt;
double tp = FindPreviousResistance(firstBottom);
if(tp <= ask + pt)
return;
if(sl >= ask - pt)
return;
sl = NormalizeDouble(sl, digits);
tp = NormalizeDouble(tp, digits);
g_trade.Buy(InpLots, _Symbol, ask, sl, tp, "Double bottom");
}
void TryEnterShortDoubleTop()
{
int firstTop = -1; // older
int secondTop = -1; // newer
double neckline = 0.0;
double hiA = 0.0;
double hiB = 0.0;
if(!FindDoubleTop(firstTop, secondTop, neckline, hiA, hiB))
return;
const int c = 1;
double close1 = iClose(_Symbol, InpTf, c);
if(close1 >= neckline)
return; // wait for neckline break confirmation
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double stretched = hiB - g_emaFast[secondTop];
if(stretched < InpMinEmaPriceDistPts * pt)
return; // no enough EMA/price displacement for reversal
if(close1 >= g_emaFast[c])
return; // keep confirmation strict: close below EMA fast
if(InpUseHtfFilter && !(g_emaFastHtf[c] < g_emaSlowHtf[c]))
return;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double sl = MathMax(hiA, hiB) + InpSlBufferPts * pt;
double tp = FindPreviousSupport(firstTop);
if(tp >= bid - pt)
return;
if(sl <= bid + pt)
return;
sl = NormalizeDouble(sl, digits);
tp = NormalizeDouble(tp, digits);
g_trade.Sell(InpLots, _Symbol, bid, sl, tp, "Double top");
}
bool FindDoubleBottom(int &firstBottom, int &secondBottom, double &neckline, double &lowA, double &lowB)
{
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int minShift = InpPivotRight + 1;
int maxShift = MathMin(InpPatternLookbackBars, Bars(_Symbol, InpTf) - InpPivotLeft - 2);
if(maxShift <= minShift + InpPivotLeft + InpPivotRight + 2)
return false;
for(int newer = minShift; newer <= maxShift; newer++)
{
if(!IsPivotLow(newer))
continue;
for(int older = newer + InpMinPatternSeparation; older <= maxShift; older++)
{
int sep = older - newer;
if(sep > InpMaxPatternSeparation)
break;
if(!IsPivotLow(older))
continue;
double lNew = iLow(_Symbol, InpTf, newer);
double lOld = iLow(_Symbol, InpTf, older);
if(MathAbs(lNew - lOld) > InpTopBottomTolPts * pt)
continue;
double neck = HighestHighBetween(newer, older);
if(neck <= 0.0)
continue;
firstBottom = older;
secondBottom = newer;
lowA = lOld;
lowB = lNew;
neckline = neck;
return true;
}
}
return false;
}
bool FindDoubleTop(int &firstTop, int &secondTop, double &neckline, double &hiA, double &hiB)
{
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int minShift = InpPivotRight + 1;
int maxShift = MathMin(InpPatternLookbackBars, Bars(_Symbol, InpTf) - InpPivotLeft - 2);
if(maxShift <= minShift + InpPivotLeft + InpPivotRight + 2)
return false;
for(int newer = minShift; newer <= maxShift; newer++)
{
if(!IsPivotHigh(newer))
continue;
for(int older = newer + InpMinPatternSeparation; older <= maxShift; older++)
{
int sep = older - newer;
if(sep > InpMaxPatternSeparation)
break;
if(!IsPivotHigh(older))
continue;
double hNew = iHigh(_Symbol, InpTf, newer);
double hOld = iHigh(_Symbol, InpTf, older);
if(MathAbs(hNew - hOld) > InpTopBottomTolPts * pt)
continue;
double neck = LowestLowBetween(newer, older);
if(neck <= 0.0)
continue;
firstTop = older;
secondTop = newer;
hiA = hOld;
hiB = hNew;
neckline = neck;
return true;
}
}
return false;
}
bool IsPivotLow(const int shift)
{
double v = iLow(_Symbol, InpTf, shift);
for(int i = 1; i <= InpPivotLeft; i++)
if(iLow(_Symbol, InpTf, shift + i) <= v) return false;
for(int i = 1; i <= InpPivotRight; i++)
if(iLow(_Symbol, InpTf, shift - i) < v) return false;
return true;
}
bool IsPivotHigh(const int shift)
{
double v = iHigh(_Symbol, InpTf, shift);
for(int i = 1; i <= InpPivotLeft; i++)
if(iHigh(_Symbol, InpTf, shift + i) >= v) return false;
for(int i = 1; i <= InpPivotRight; i++)
if(iHigh(_Symbol, InpTf, shift - i) > v) return false;
return true;
}
double HighestHighBetween(const int shiftA, const int shiftB)
{
int from = MathMin(shiftA, shiftB);
int to = MathMax(shiftA, shiftB);
double v = -DBL_MAX;
for(int i = from; i <= to; i++)
v = MathMax(v, iHigh(_Symbol, InpTf, i));
return v;
}
double LowestLowBetween(const int shiftA, const int shiftB)
{
int from = MathMin(shiftA, shiftB);
int to = MathMax(shiftA, shiftB);
double v = DBL_MAX;
for(int i = from; i <= to; i++)
v = MathMin(v, iLow(_Symbol, InpTf, i));
return v;
}
double FindPreviousResistance(const int firstBottomShift)
{
int start = firstBottomShift + 1;
int end = firstBottomShift + InpPrevLevelLookback;
int bars = Bars(_Symbol, InpTf);
end = MathMin(end, bars - 2);
if(start > end)
return 0.0;
double r = -DBL_MAX;
for(int i = start; i <= end; i++)
r = MathMax(r, iHigh(_Symbol, InpTf, i));
return r;
}
double FindPreviousSupport(const int firstTopShift)
{
int start = firstTopShift + 1;
int end = firstTopShift + InpPrevLevelLookback;
int bars = Bars(_Symbol, InpTf);
end = MathMin(end, bars - 2);
if(start > end)
return 0.0;
double s = DBL_MAX;
for(int i = start; i <= end; i++)
s = MathMin(s, iLow(_Symbol, InpTf, i));
return s;
}
bool PositionExistsForMagic()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic)
return true;
}
return false;
}
void SetTradeFillingBySymbol()
{
long mask = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if((mask & SYMBOL_FILLING_IOC) != 0)
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
else if((mask & SYMBOL_FILLING_FOK) != 0)
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
else
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
}
+404
View File
@@ -0,0 +1,404 @@
//+------------------------------------------------------------------+
//| rsi-dual-martingale-hybrid.mq5 |
//| Two robots: RSI reversal martingale + RSI midpoint trend helper |
//+------------------------------------------------------------------+
#property copyright "Lab"
#property version "1.00"
#include <Trade\Trade.mqh>
//--- core
input ENUM_TIMEFRAMES InpTf = PERIOD_M5;
input int InpRsiLen = 14;
input double InpRsiOverbought = 70.0;
input double InpRsiOversold = 30.0;
input double InpRsiMid = 50.0;
//--- money
input double InpBaseLot = 0.01;
input int InpSlippagePts = 30;
input ulong InpMagicBase = 2026042501;
//--- robot A: RSI reversal martingale
input bool InpEnableReversalMartingale = true;
input double InpMartingaleMult = 1.7;
input int InpMartingaleStepPts = 300;
input int InpMartingaleMaxLevels = 6;
//--- robot B: reverse martingale trend follow (RSI cross midpoint)
input bool InpEnableTrendReverseMartingale = true;
input double InpTrendPyramidMult = 1.5;
input int InpTrendPyramidStepPts = 250;
input int InpTrendMaxLevels = 5;
//--- rescue / coordination
input bool InpEnableRescue = true;
input double InpTroubleLossMoney = -8.0; // martingale basket in trouble below this
input double InpRescueLotMult = 2.0; // base lot multiplier for rescue trade
input int InpRescueCooldownBars = 3;
CTrade g_trade;
int g_hRsi = INVALID_HANDLE;
double g_rsi[];
datetime g_lastBar = 0;
int g_lastRescueBarIndex = -1000000;
enum RobotDirection
{
DIR_NONE = 0,
DIR_BUY = 1,
DIR_SELL = -1
};
// Magic map:
// base + 1 : reversal martingale basket
// base + 2 : trend reverse-martingale basket
// base + 3 : rescue positions
ulong MagicRev() { return InpMagicBase + 1; }
ulong MagicTrend() { return InpMagicBase + 2; }
ulong MagicRescue() { return InpMagicBase + 3; }
int OnInit()
{
g_trade.SetDeviationInPoints(InpSlippagePts);
SetTradeFillingBySymbol();
g_hRsi = iRSI(_Symbol, InpTf, InpRsiLen, PRICE_CLOSE);
if(g_hRsi == INVALID_HANDLE)
return INIT_FAILED;
ArraySetAsSeries(g_rsi, true);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(g_hRsi != INVALID_HANDLE)
IndicatorRelease(g_hRsi);
}
void OnTick()
{
if(CopyBuffer(g_hRsi, 0, 0, 10, g_rsi) < 10)
return;
// Rescue management can run every tick.
ManageRescueCoordination();
datetime t = iTime(_Symbol, InpTf, 0);
if(t == g_lastBar)
return;
g_lastBar = t;
if(InpEnableReversalMartingale)
RunReversalMartingale();
if(InpEnableTrendReverseMartingale)
RunTrendReverseMartingale();
}
void RunReversalMartingale()
{
ulong magic = MagicRev();
int count = BasketCountByMagic(magic);
double rsi1 = g_rsi[1];
// No fixed TP/SL: close reversal basket when mean-reversion reaches RSI midpoint.
RobotDirection dir = BasketDirectionByMagic(magic);
if(count > 0 &&
((dir == DIR_BUY && rsi1 >= InpRsiMid) ||
(dir == DIR_SELL && rsi1 <= InpRsiMid)))
{
CloseBasketByMagic(magic);
return;
}
double rsi2 = g_rsi[2];
if(count == 0)
{
if(rsi2 < InpRsiOversold && rsi1 > InpRsiOversold)
{
OpenMarketByDirection(magic, DIR_BUY, NormalizeVolume(InpBaseLot), "REV start");
return;
}
if(rsi2 > InpRsiOverbought && rsi1 < InpRsiOverbought)
{
OpenMarketByDirection(magic, DIR_SELL, NormalizeVolume(InpBaseLot), "REV start");
return;
}
return;
}
if(dir == DIR_NONE || count >= InpMartingaleMaxLevels)
return;
double lastEntry = LastEntryPriceByMagic(magic);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
bool adverseEnough = false;
if(dir == DIR_BUY)
adverseEnough = (lastEntry - bid) >= (InpMartingaleStepPts * pt);
else if(dir == DIR_SELL)
adverseEnough = (ask - lastEntry) >= (InpMartingaleStepPts * pt);
if(!adverseEnough)
return;
double lot = NormalizeVolume(InpBaseLot * MathPow(InpMartingaleMult, count));
OpenMarketByDirection(magic, dir, lot, "REV scale");
}
void RunTrendReverseMartingale()
{
ulong magic = MagicTrend();
int count = BasketCountByMagic(magic);
RobotDirection dir = BasketDirectionByMagic(magic);
double basketProfit = BasketProfitByMagic(magic);
double rsi1 = g_rsi[1];
double rsi2 = g_rsi[2];
// No fixed TP/SL: close trend basket when RSI crosses back through midpoint.
if(count > 0 &&
((dir == DIR_BUY && rsi2 > InpRsiMid && rsi1 < InpRsiMid) ||
(dir == DIR_SELL && rsi2 < InpRsiMid && rsi1 > InpRsiMid)))
{
CloseBasketByMagic(magic);
return;
}
if(count == 0)
{
if(rsi2 < InpRsiMid && rsi1 > InpRsiMid)
{
OpenMarketByDirection(magic, DIR_BUY, NormalizeVolume(InpBaseLot), "TREND cross");
return;
}
if(rsi2 > InpRsiMid && rsi1 < InpRsiMid)
{
OpenMarketByDirection(magic, DIR_SELL, NormalizeVolume(InpBaseLot), "TREND cross");
return;
}
return;
}
if(dir == DIR_NONE || count >= InpTrendMaxLevels)
return;
if(basketProfit <= 0.0)
return; // reverse martingale: only add into winners
double lastEntry = LastEntryPriceByMagic(magic);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
bool favorableEnough = false;
if(dir == DIR_BUY)
favorableEnough = (bid - lastEntry) >= (InpTrendPyramidStepPts * pt);
else if(dir == DIR_SELL)
favorableEnough = (lastEntry - ask) >= (InpTrendPyramidStepPts * pt);
if(!favorableEnough)
return;
double lot = NormalizeVolume(InpBaseLot * MathPow(InpTrendPyramidMult, count));
OpenMarketByDirection(magic, dir, lot, "TREND add");
}
void ManageRescueCoordination()
{
if(!InpEnableRescue)
return;
ulong mRev = MagicRev();
ulong mRes = MagicRescue();
double revProfit = BasketProfitByMagic(mRev);
int revCount = BasketCountByMagic(mRev);
if(revCount == 0)
{
CloseBasketByMagic(mRes);
return;
}
// Phase 1: no fixed rescue TP/SL; close rescue on RSI midpoint recross against rescue direction.
RobotDirection rescueDir = BasketDirectionByMagic(mRes);
if(BasketCountByMagic(mRes) > 0 &&
((rescueDir == DIR_BUY && g_rsi[2] > InpRsiMid && g_rsi[1] < InpRsiMid) ||
(rescueDir == DIR_SELL && g_rsi[2] < InpRsiMid && g_rsi[1] > InpRsiMid)))
{
ulong worstTicket = WorstTicketByMagic(mRev);
CloseBasketByMagic(mRes);
if(worstTicket != 0)
g_trade.PositionClose(worstTicket);
return;
}
// Phase 2: if martingale basket is in trouble, launch one trend-aligned rescue trade.
if(revProfit > InpTroubleLossMoney)
return;
if(BasketCountByMagic(mRes) > 0)
return;
int barsNow = iBars(_Symbol, InpTf);
if((barsNow - g_lastRescueBarIndex) < InpRescueCooldownBars)
return;
RobotDirection helperDir = (g_rsi[1] >= InpRsiMid ? DIR_BUY : DIR_SELL);
// avoid adding rescue in same direction as losing reversal basket when RSI trend disagrees
RobotDirection revDir = BasketDirectionByMagic(mRev);
if(revDir == helperDir)
helperDir = (helperDir == DIR_BUY ? DIR_SELL : DIR_BUY);
double lot = NormalizeVolume(InpBaseLot * InpRescueLotMult);
if(OpenMarketByDirection(mRes, helperDir, lot, "RESCUE"))
g_lastRescueBarIndex = barsNow;
}
bool OpenMarketByDirection(const ulong magic, const RobotDirection dir, const double lot, const string comment)
{
if(dir == DIR_NONE || lot <= 0.0)
return false;
g_trade.SetExpertMagicNumber(magic);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(dir == DIR_BUY)
return g_trade.Buy(lot, _Symbol, ask, 0.0, 0.0, comment);
return g_trade.Sell(lot, _Symbol, bid, 0.0, 0.0, comment);
}
int BasketCountByMagic(const ulong magic)
{
int n = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
n++;
}
return n;
}
double BasketProfitByMagic(const ulong magic)
{
double sum = 0.0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
sum += PositionGetDouble(POSITION_PROFIT);
}
return sum;
}
RobotDirection BasketDirectionByMagic(const ulong magic)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
long type = PositionGetInteger(POSITION_TYPE);
return (type == POSITION_TYPE_BUY ? DIR_BUY : DIR_SELL);
}
return DIR_NONE;
}
double LastEntryPriceByMagic(const ulong magic)
{
datetime newest = 0;
double price = 0.0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
datetime t = (datetime)PositionGetInteger(POSITION_TIME);
if(t >= newest)
{
newest = t;
price = PositionGetDouble(POSITION_PRICE_OPEN);
}
}
return price;
}
ulong WorstTicketByMagic(const ulong magic)
{
double worstProfit = DBL_MAX;
ulong worstTicket = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
double p = PositionGetDouble(POSITION_PROFIT);
if(p < worstProfit)
{
worstProfit = p;
worstTicket = ticket;
}
}
return worstTicket;
}
void CloseBasketByMagic(const ulong magic)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
g_trade.PositionClose(ticket);
}
}
double NormalizeVolume(const double volRaw)
{
double vMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double vMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double vStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(vStep <= 0.0)
vStep = 0.01;
double v = MathMax(vMin, MathMin(vMax, volRaw));
v = MathFloor(v / vStep) * vStep;
int vd = 2;
if(vStep < 0.01) vd = 3;
if(vStep < 0.001) vd = 4;
return NormalizeDouble(v, vd);
}
void SetTradeFillingBySymbol()
{
long mask = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if((mask & SYMBOL_FILLING_IOC) != 0)
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
else if((mask & SYMBOL_FILLING_FOK) != 0)
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
else
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
}