Update
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SimpleTrendlineStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
#define SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
|
||||
struct SimpleTrendlineModel
|
||||
{
|
||||
datetime t1;
|
||||
datetime t2;
|
||||
datetime t3;
|
||||
double a;
|
||||
double b;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct SimpleTrendlineData
|
||||
{
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
CTrade trade;
|
||||
ENUM_TIMEFRAMES signalTF;
|
||||
ENUM_TIMEFRAMES higherTF;
|
||||
int maPeriod;
|
||||
ENUM_MA_METHOD maMethod;
|
||||
ENUM_APPLIED_PRICE appliedPrice;
|
||||
int htfBarsToScan;
|
||||
double touchTolerancePoints;
|
||||
double breakBufferPoints;
|
||||
ulong magic;
|
||||
bool drawTrendline;
|
||||
int maHandle;
|
||||
datetime lastSignalBarTime;
|
||||
string lineName;
|
||||
};
|
||||
|
||||
double ST_NormalizeVolume(const string sym, double vol)
|
||||
{
|
||||
double minLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
|
||||
double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
|
||||
if(step > 0.0)
|
||||
vol = MathFloor(vol / step) * step;
|
||||
if(vol < minLot)
|
||||
vol = minLot;
|
||||
if(vol > maxLot)
|
||||
vol = maxLot;
|
||||
return vol;
|
||||
}
|
||||
|
||||
bool ST_GetPosition(const string sym, const ulong magic, ENUM_POSITION_TYPE &type, double &volume)
|
||||
{
|
||||
if(!PositionSelectByMagic(sym, magic))
|
||||
return false;
|
||||
type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
volume = PositionGetDouble(POSITION_VOLUME);
|
||||
return true;
|
||||
}
|
||||
|
||||
int ST_FindRecentCrossPoints(SimpleTrendlineData &d, datetime ×[], double &prices[])
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(prices, 0);
|
||||
if(d.maHandle == INVALID_HANDLE)
|
||||
return 0;
|
||||
|
||||
int needBars = MathMax(d.htfBarsToScan, d.maPeriod + 20);
|
||||
MqlRates rates[];
|
||||
double maBuf[];
|
||||
ArraySetAsSeries(rates, true);
|
||||
ArraySetAsSeries(maBuf, true);
|
||||
|
||||
int copiedRates = CopyRates(d.symbol, d.higherTF, 0, needBars, rates);
|
||||
int copiedMa = CopyBuffer(d.maHandle, 0, 0, needBars, maBuf);
|
||||
if(copiedRates <= 5 || copiedMa <= 5)
|
||||
return 0;
|
||||
|
||||
int bars = MathMin(copiedRates, copiedMa);
|
||||
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 ST_BuildTrendline(SimpleTrendlineData &d, SimpleTrendlineModel &m)
|
||||
{
|
||||
m.valid = false;
|
||||
datetime ts[];
|
||||
double ps[];
|
||||
if(ST_FindRecentCrossPoints(d, ts, ps) < 3)
|
||||
return false;
|
||||
|
||||
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.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
double ST_LinePriceAt(const SimpleTrendlineModel &m, const datetime t)
|
||||
{
|
||||
if(!m.valid)
|
||||
return 0.0;
|
||||
double x = (double)((long)t - (long)m.t1);
|
||||
return m.a * x + m.b;
|
||||
}
|
||||
|
||||
void ST_DrawTrendline(SimpleTrendlineData &d, const SimpleTrendlineModel &m)
|
||||
{
|
||||
if(!d.drawTrendline || !m.valid || d.symbol != _Symbol)
|
||||
return;
|
||||
|
||||
datetime tStart = m.t1;
|
||||
datetime tEnd = iTime(d.symbol, d.signalTF, 0);
|
||||
if(tEnd <= tStart)
|
||||
tEnd = m.t3 + PeriodSeconds(d.signalTF) * 20;
|
||||
|
||||
double pStart = ST_LinePriceAt(m, tStart);
|
||||
double pEnd = ST_LinePriceAt(m, tEnd);
|
||||
|
||||
if(ObjectFind(0, d.lineName) < 0)
|
||||
ObjectCreate(0, d.lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd);
|
||||
else
|
||||
{
|
||||
ObjectMove(0, d.lineName, 0, tStart, pStart);
|
||||
ObjectMove(0, d.lineName, 1, tEnd, pEnd);
|
||||
}
|
||||
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_COLOR, clrGold);
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_WIDTH, 2);
|
||||
}
|
||||
|
||||
void ST_TryExitOnBreak(SimpleTrendlineData &d, const SimpleTrendlineModel &m)
|
||||
{
|
||||
ENUM_POSITION_TYPE posType;
|
||||
double vol;
|
||||
if(!ST_GetPosition(d.symbol, d.magic, posType, vol))
|
||||
return;
|
||||
|
||||
double close1 = iClose(d.symbol, d.signalTF, 1);
|
||||
datetime t1 = iTime(d.symbol, d.signalTF, 1);
|
||||
double line1 = ST_LinePriceAt(m, t1);
|
||||
double buf = d.breakBufferPoints * SymbolInfoDouble(d.symbol, SYMBOL_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)
|
||||
ClosePositionByMagic(d.trade, d.symbol, d.magic);
|
||||
}
|
||||
|
||||
void ST_TryPullbackEntry(SimpleTrendlineData &d, const SimpleTrendlineModel &m, const double lots)
|
||||
{
|
||||
if(PositionExistsByMagic(d.symbol, d.magic))
|
||||
return;
|
||||
|
||||
MqlRates b1[], b2[];
|
||||
ArraySetAsSeries(b1, true);
|
||||
ArraySetAsSeries(b2, true);
|
||||
if(CopyRates(d.symbol, d.signalTF, 1, 1, b1) != 1)
|
||||
return;
|
||||
if(CopyRates(d.symbol, d.signalTF, 2, 1, b2) != 1)
|
||||
return;
|
||||
if(ArraySize(b1) < 1 || ArraySize(b2) < 1)
|
||||
return;
|
||||
|
||||
double line1 = ST_LinePriceAt(m, b1[0].time);
|
||||
double tol = d.touchTolerancePoints * SymbolInfoDouble(d.symbol, SYMBOL_POINT);
|
||||
bool upTrend = (m.a > 0.0);
|
||||
bool downTrend = (m.a < 0.0);
|
||||
double vol = ST_NormalizeVolume(d.symbol, lots);
|
||||
|
||||
if(upTrend)
|
||||
{
|
||||
bool touched = (b1[0].low <= (line1 + tol));
|
||||
bool reclaim = (b1[0].close > line1);
|
||||
bool bullish = (b1[0].close > b1[0].open);
|
||||
bool stillHealthy = (b2[0].close >= ST_LinePriceAt(m, b2[0].time) - tol);
|
||||
if(touched && reclaim && bullish && stillHealthy)
|
||||
{
|
||||
if(!d.trade.Buy(vol, d.symbol, 0.0, 0.0, 0.0, "SimpleTrendline BUY"))
|
||||
Print("SimpleTrendline BUY failed [", d.symbol, "] retcode=", d.trade.ResultRetcode(), " ", d.trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
else if(downTrend)
|
||||
{
|
||||
bool touched = (b1[0].high >= (line1 - tol));
|
||||
bool reject = (b1[0].close < line1);
|
||||
bool bearish = (b1[0].close < b1[0].open);
|
||||
bool stillWeak = (b2[0].close <= ST_LinePriceAt(m, b2[0].time) + tol);
|
||||
if(touched && reject && bearish && stillWeak)
|
||||
{
|
||||
if(!d.trade.Sell(vol, d.symbol, 0.0, 0.0, 0.0, "SimpleTrendline SELL"))
|
||||
Print("SimpleTrendline SELL failed [", d.symbol, "] retcode=", d.trade.ResultRetcode(), " ", d.trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool InitSimpleTrendline(SimpleTrendlineData &d,
|
||||
const string symbol,
|
||||
const ENUM_TIMEFRAMES signalTF,
|
||||
const ENUM_TIMEFRAMES higherTF,
|
||||
const int maPeriod,
|
||||
const ENUM_MA_METHOD maMethod,
|
||||
const ENUM_APPLIED_PRICE appliedPrice,
|
||||
const int htfBarsToScan,
|
||||
const double touchTolerancePoints,
|
||||
const double breakBufferPoints,
|
||||
const ulong magic,
|
||||
const bool drawTrendline)
|
||||
{
|
||||
d.isInitialized = false;
|
||||
d.symbol = symbol;
|
||||
StringTrimLeft(d.symbol);
|
||||
StringTrimRight(d.symbol);
|
||||
if(StringLen(d.symbol) == 0)
|
||||
d.symbol = _Symbol;
|
||||
|
||||
if(!SymbolSelect(d.symbol, true))
|
||||
return false;
|
||||
|
||||
d.signalTF = signalTF;
|
||||
d.higherTF = higherTF;
|
||||
d.maPeriod = maPeriod;
|
||||
d.maMethod = maMethod;
|
||||
d.appliedPrice = appliedPrice;
|
||||
d.htfBarsToScan = htfBarsToScan;
|
||||
d.touchTolerancePoints = touchTolerancePoints;
|
||||
d.breakBufferPoints = breakBufferPoints;
|
||||
d.magic = magic;
|
||||
d.drawTrendline = drawTrendline;
|
||||
d.lastSignalBarTime = 0;
|
||||
d.lineName = "SimpleTrendline_" + d.symbol + "_" + IntegerToString((int)d.magic);
|
||||
|
||||
d.trade.SetExpertMagicNumber((long)d.magic);
|
||||
d.trade.SetTypeFillingBySymbol(d.symbol);
|
||||
d.trade.SetDeviationInPoints(20);
|
||||
|
||||
d.maHandle = iMA(d.symbol, d.higherTF, d.maPeriod, 0, d.maMethod, d.appliedPrice);
|
||||
if(d.maHandle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
d.isInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitSimpleTrendline(SimpleTrendlineData &d)
|
||||
{
|
||||
if(d.maHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(d.maHandle);
|
||||
d.maHandle = INVALID_HANDLE;
|
||||
if(ObjectFind(0, d.lineName) >= 0)
|
||||
ObjectDelete(0, d.lineName);
|
||||
d.isInitialized = false;
|
||||
}
|
||||
|
||||
void ProcessSimpleTrendline(SimpleTrendlineData &d, const double lots)
|
||||
{
|
||||
if(!d.isInitialized)
|
||||
return;
|
||||
|
||||
datetime bar0 = iTime(d.symbol, d.signalTF, 0);
|
||||
if(bar0 == 0 || bar0 == d.lastSignalBarTime)
|
||||
return;
|
||||
d.lastSignalBarTime = bar0;
|
||||
|
||||
SimpleTrendlineModel m;
|
||||
if(!ST_BuildTrendline(d, m))
|
||||
return;
|
||||
|
||||
ST_DrawTrendline(d, m);
|
||||
ST_TryExitOnBreak(d, m);
|
||||
ST_TryPullbackEntry(d, m, lots);
|
||||
}
|
||||
|
||||
#endif // SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Strategies/SuperEMAStrategy.mqh"
|
||||
#include "Strategies/RSIReversalAsianStrategy.mqh"
|
||||
#include "Strategies/RSIConsolidationStrategy.mqh"
|
||||
#include "Strategies/SimpleTrendlineStrategy.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Lot Size Variables (for dynamic lot sizing) |
|
||||
@@ -54,6 +55,8 @@ input bool EnableSuperEMA = true;
|
||||
input bool EnableRSIConsolidation = true;
|
||||
input bool EnableRSIReversalAsianEURUSD = true;
|
||||
input bool EnableRSIReversalAsianAUDUSD = true;
|
||||
input bool EnableSimpleTrendlineBTCUSD = true;
|
||||
input bool EnableSimpleTrendlineXAUUSD = true;
|
||||
|
||||
input group "=== Centralized Lot Size (Granular Per Robot) ==="
|
||||
input double LOT_ES_EMASlopeDistance = 0.05;
|
||||
@@ -68,6 +71,8 @@ input double LOT_RRA_EURUSD = 0.01;
|
||||
input double LOT_RRA_AUDUSD = 0.10;
|
||||
input double LOT_SE_SuperEMA = 0.01;
|
||||
input double LOT_RCO_RSIConsolidation = 0.04;
|
||||
input double LOT_ST_BTCUSD = 0.19;
|
||||
input double LOT_ST_XAUUSD = 0.02;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 1: DarvasBoxXAUUSD |
|
||||
@@ -385,6 +390,32 @@ input ulong RCO_MagicNumber = 20250420;
|
||||
input int RCO_Slippage = 10;
|
||||
input int RCO_MaxSpreadPoints = 28;
|
||||
|
||||
input group "=== SimpleTrendline BTCUSD ==="
|
||||
input string ST_BTC_Symbol = "BTCUSD";
|
||||
input ENUM_TIMEFRAMES ST_BTC_SignalTF = PERIOD_H1;
|
||||
input ENUM_TIMEFRAMES ST_BTC_HigherTF = PERIOD_H4;
|
||||
input int ST_BTC_MAPeriod = 150;
|
||||
input ENUM_MA_METHOD ST_BTC_MAMethod = MODE_SMMA;
|
||||
input ENUM_APPLIED_PRICE ST_BTC_AppliedPrice = PRICE_OPEN;
|
||||
input int ST_BTC_HTFBarsToScan = 1200;
|
||||
input double ST_BTC_LineTouchTolerance = 170.0;
|
||||
input double ST_BTC_BreakBuffer = 90.0;
|
||||
input ulong ST_BTC_MagicNumber = 26042501;
|
||||
input bool ST_BTC_DrawTrendline = true;
|
||||
|
||||
input group "=== SimpleTrendline XAUUSD ==="
|
||||
input string ST_XAU_Symbol = "XAUUSD";
|
||||
input ENUM_TIMEFRAMES ST_XAU_SignalTF = PERIOD_H1;
|
||||
input ENUM_TIMEFRAMES ST_XAU_HigherTF = PERIOD_M10;
|
||||
input int ST_XAU_MAPeriod = 65;
|
||||
input ENUM_MA_METHOD ST_XAU_MAMethod = MODE_EMA;
|
||||
input ENUM_APPLIED_PRICE ST_XAU_AppliedPrice = PRICE_OPEN;
|
||||
input int ST_XAU_HTFBarsToScan = 500;
|
||||
input double ST_XAU_LineTouchTolerance = 220.0;
|
||||
input double ST_XAU_BreakBuffer = 110.0;
|
||||
input ulong ST_XAU_MagicNumber = 26042503;
|
||||
input bool ST_XAU_DrawTrendline = true;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - DarvasBox |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -483,6 +514,8 @@ RSIScalpingData rsTSLAData;
|
||||
RSIScalpingData rsXAUUSDData;
|
||||
SuperEMAData seData;
|
||||
RSIConsolidationData rcoData;
|
||||
SimpleTrendlineData stBTCData;
|
||||
SimpleTrendlineData stXAUData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - RSI Reversal Asian |
|
||||
@@ -570,6 +603,18 @@ int OnInit()
|
||||
RRA_AUDUSD_UseTakeProfit, RRA_AUDUSD_UseRSIExit, RRA_AUDUSD_RSIExitLevel,
|
||||
RRA_AUDUSD_CloseOutsideSession, RRA_AUDUSD_TimeFrame, RRA_AUDUSD_MagicNumber, RRA_AUDUSD_Slippage))
|
||||
Print("Warning: RSIReversalAsianAUDUSD strategy failed to initialize for symbol '", RRA_AUDUSD_Symbol, "'");
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
if(!InitSimpleTrendline(stBTCData, ST_BTC_Symbol, ST_BTC_SignalTF, ST_BTC_HigherTF, ST_BTC_MAPeriod,
|
||||
ST_BTC_MAMethod, ST_BTC_AppliedPrice, ST_BTC_HTFBarsToScan,
|
||||
ST_BTC_LineTouchTolerance, ST_BTC_BreakBuffer, ST_BTC_MagicNumber, ST_BTC_DrawTrendline))
|
||||
Print("Warning: SimpleTrendlineBTCUSD failed to initialize for symbol '", ST_BTC_Symbol, "'");
|
||||
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
if(!InitSimpleTrendline(stXAUData, ST_XAU_Symbol, ST_XAU_SignalTF, ST_XAU_HigherTF, ST_XAU_MAPeriod,
|
||||
ST_XAU_MAMethod, ST_XAU_AppliedPrice, ST_XAU_HTFBarsToScan,
|
||||
ST_XAU_LineTouchTolerance, ST_XAU_BreakBuffer, ST_XAU_MagicNumber, ST_XAU_DrawTrendline))
|
||||
Print("Warning: SimpleTrendlineXAUUSD failed to initialize for symbol '", ST_XAU_Symbol, "'");
|
||||
|
||||
Print("United EA initialized. Active strategies: ",
|
||||
(EnableDarvasBox ? "DarvasBox " : ""),
|
||||
@@ -584,7 +629,9 @@ int OnInit()
|
||||
(EnableSuperEMA ? "SuperEMA " : ""),
|
||||
(EnableRSIConsolidation ? "RSIConsolidation " : ""),
|
||||
(EnableRSIReversalAsianEURUSD ? "RSIReversalAsianEURUSD " : ""),
|
||||
(EnableRSIReversalAsianAUDUSD ? "RSIReversalAsianAUDUSD " : ""));
|
||||
(EnableRSIReversalAsianAUDUSD ? "RSIReversalAsianAUDUSD " : ""),
|
||||
(EnableSimpleTrendlineBTCUSD ? "SimpleTrendlineBTCUSD " : ""),
|
||||
(EnableSimpleTrendlineXAUUSD ? "SimpleTrendlineXAUUSD " : ""));
|
||||
|
||||
return initResult;
|
||||
}
|
||||
@@ -632,6 +679,11 @@ void OnDeinit(const int reason)
|
||||
|
||||
if(EnableRSIReversalAsianAUDUSD)
|
||||
DeinitRSIReversalAsian(rraAUDUSDData);
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
DeinitSimpleTrendline(stBTCData);
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
DeinitSimpleTrendline(stXAUData);
|
||||
|
||||
Print("United EA deinitialized. Reason: ", reason);
|
||||
}
|
||||
@@ -699,6 +751,11 @@ void OnTick()
|
||||
|
||||
if(EnableRSIConsolidation)
|
||||
ProcessRSIConsolidation(rcoData, LOT_RCO_RSIConsolidation);
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
ProcessSimpleTrendline(stBTCData, LOT_ST_BTCUSD);
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
ProcessSimpleTrendline(stXAUData, LOT_ST_XAUUSD);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_H4; // Higher timeframe for MA/cross points
|
||||
input int InpMAPeriod = 150; // MA period
|
||||
input ENUM_MA_METHOD InpMAMethod = MODE_SMMA; // MA method
|
||||
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_OPEN; // MA applied price
|
||||
input int InpHTFBarsToScan = 1200; // HTF bars to scan for crossings
|
||||
input double InpLineTouchTolerance = 170; // Pullback touch tolerance (points)
|
||||
input double InpBreakBuffer = 90; // 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 ×[], 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
|
||||
; Strategy Tester -> Inputs -> Load
|
||||
; Focus: trendline pullback entries + break exits (no broker SL/TP)
|
||||
;
|
||||
InpHigherTF=16385||16385||0||16388||Y
|
||||
InpMAPeriod=50||20||5||200||Y
|
||||
InpMAMethod=1||0||1||3||Y
|
||||
InpAppliedPrice=0||0||1||6||Y
|
||||
InpHTFBarsToScan=400||200||100||1200||Y
|
||||
InpLineTouchTolerance=100.0||30.0||10.0||300.0||Y
|
||||
InpBreakBuffer=30.0||5.0||5.0||120.0||Y
|
||||
InpLots=0.10||0.10||0.01||0.10||N
|
||||
InpMagic=26042501||26042501||1||26042501||N
|
||||
InpDrawTrendline=false||false||0||true||N
|
||||
@@ -0,0 +1,284 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_M15; // Higher timeframe for MA/cross points
|
||||
input int InpMAPeriod = 65; // MA period
|
||||
input ENUM_MA_METHOD InpMAMethod = MODE_LWMA; // MA method
|
||||
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_OPEN; // MA applied price
|
||||
input int InpHTFBarsToScan = 1200; // HTF bars to scan for crossings
|
||||
input double InpLineTouchTolerance = 100; // Pullback touch tolerance (points)
|
||||
input double InpBreakBuffer = 80; // 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 ×[], 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
|
||||
; Strategy Tester -> Inputs -> Load
|
||||
; Focus: trendline pullback entries + break exits (no broker SL/TP)
|
||||
;
|
||||
InpHigherTF=16385||16385||0||16388||Y
|
||||
InpMAPeriod=50||20||5||200||Y
|
||||
InpMAMethod=1||0||1||3||Y
|
||||
InpAppliedPrice=0||0||1||6||Y
|
||||
InpHTFBarsToScan=400||200||100||1200||Y
|
||||
InpLineTouchTolerance=100.0||30.0||10.0||300.0||Y
|
||||
InpBreakBuffer=30.0||5.0||5.0||120.0||Y
|
||||
InpLots=0.10||0.10||0.01||0.10||N
|
||||
InpMagic=26042501||26042501||1||26042501||N
|
||||
InpDrawTrendline=false||false||0||true||N
|
||||
@@ -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 ×[], 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
|
||||
; Strategy Tester -> Inputs -> Load
|
||||
; Focus: trendline pullback entries + break exits (no broker SL/TP)
|
||||
;
|
||||
InpHigherTF=16385||16385||0||16388||Y
|
||||
InpMAPeriod=50||20||5||200||Y
|
||||
InpMAMethod=1||0||1||3||Y
|
||||
InpAppliedPrice=0||0||1||6||Y
|
||||
InpHTFBarsToScan=400||200||100||1200||Y
|
||||
InpLineTouchTolerance=100.0||30.0||10.0||300.0||Y
|
||||
InpBreakBuffer=30.0||5.0||5.0||120.0||Y
|
||||
InpLots=0.10||0.10||0.01||0.10||N
|
||||
InpMagic=26042501||26042501||1||26042501||N
|
||||
InpDrawTrendline=false||false||0||true||N
|
||||
Reference in New Issue
Block a user