1752 lines
62 KiB
Plaintext
1752 lines
62 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| TrendlineIndicator.mq5 |
|
|
//| Copyright 2025, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
|
#property link "https://www.mql5.com"
|
|
#property version "1.00"
|
|
#property indicator_chart_window
|
|
#property indicator_buffers 0
|
|
#property indicator_plots 0
|
|
#property description "Automatically draws trendlines connecting swing highs and lows"
|
|
#property description "Adjustable parameters for swing detection and line appearance"
|
|
#property description "Can execute actual trades when enabled"
|
|
#property description "NOTE: For Strategy Tester, use as Expert Advisor (EA) instead"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
//--- Input Parameters
|
|
input group "=== Swing Point Detection ==="
|
|
input int InpSwingPeriod = 5; // Swing Period (bars to look back/forward)
|
|
input int InpMinBarsBetween = 10; // Minimum Bars Between Swing Points
|
|
input int InpMaxSwingPoints = 20; // Maximum Swing Points to Track
|
|
input int InpLookbackBars = 500; // Lookback Bars (0 = all available)
|
|
|
|
input group "=== Trendline Appearance ==="
|
|
input color InpResistanceColor = clrRed; // Resistance Line Color
|
|
input color InpSupportColor = clrBlue; // Support Line Color
|
|
input int InpLineWidth = 1; // Line Width
|
|
input ENUM_LINE_STYLE InpLineStyle = STYLE_SOLID; // Line Style
|
|
input bool InpExtendLines = true; // Extend Lines to Right Edge
|
|
input int InpExtensionBars = 50; // Extension Bars (if ExtendLines = false)
|
|
|
|
input group "=== Display Options ==="
|
|
input bool InpShowSupportLines = true; // Show Support Lines
|
|
input bool InpShowResistanceLines = true; // Show Resistance Lines
|
|
input bool InpShowRay = false; // Show Ray (infinite extension)
|
|
input bool InpShowLabels = false; // Show Price Labels
|
|
|
|
input group "=== Trading Strategy ==="
|
|
input bool InpEnableTrading = true; // Enable Trading Signals
|
|
input bool InpExecuteRealTrades = false; // Execute Real Trades (WARNING: Uses Real Money!)
|
|
input double InpLotSize = 0.01; // Lot Size for Real Trades
|
|
input int InpMagicNumber = 123456; // Magic Number for Trades
|
|
input int InpSlippage = 10; // Slippage in Points
|
|
input double InpMinRiskRewardRatio = 2.0; // Minimum Risk/Reward Ratio Required
|
|
input double InpMaxRiskRewardRatio = 10.0; // Maximum Risk/Reward Ratio (sanity check)
|
|
input bool InpIgnoreRRRejection = false; // Ignore R/R Ratio Rejection (Accept All Signals)
|
|
input bool InpUseNearestLevels = true; // Use Nearest Support/Resistance for SL/TP
|
|
input double InpLevelTolerancePips = 5.0; // Tolerance for finding levels (pips)
|
|
input bool InpShowTradeLevels = true; // Show Entry/SL/TP on Chart
|
|
input color InpBuyColor = clrLime; // Buy Signal Color
|
|
input color InpSellColor = clrOrange; // Sell Signal Color
|
|
|
|
input group "=== Debug & Feedback ==="
|
|
input bool InpShowDebugInfo = true; // Show Debug Information
|
|
input bool InpShowDetailedStats = false; // Show Detailed Statistics
|
|
|
|
//--- Global Variables
|
|
struct SwingPoint
|
|
{
|
|
datetime time;
|
|
double price;
|
|
bool isHigh;
|
|
int barIndex;
|
|
};
|
|
|
|
SwingPoint swingPoints[];
|
|
string trendlineNames[];
|
|
int trendlineCount = 0;
|
|
|
|
//--- Trading structures
|
|
struct TrendlineInfo
|
|
{
|
|
string name;
|
|
double point1Price;
|
|
double point2Price;
|
|
datetime point1Time;
|
|
datetime point2Time;
|
|
bool isResistance;
|
|
double slope;
|
|
double intercept;
|
|
};
|
|
|
|
struct TradeSignal
|
|
{
|
|
bool active;
|
|
ENUM_ORDER_TYPE type;
|
|
double entryPrice;
|
|
double stopLoss;
|
|
double takeProfit;
|
|
datetime entryTime;
|
|
string trendlineName;
|
|
string entryObjectName;
|
|
string slObjectName;
|
|
string tpObjectName;
|
|
};
|
|
|
|
TrendlineInfo trendlines[];
|
|
TradeSignal currentSignal;
|
|
int atrHandle = INVALID_HANDLE;
|
|
CTrade trade; // Trade object for real trading
|
|
ulong currentTradeTicket = 0; // Current trade ticket
|
|
static int uniqueObjectCounter = 0; // Unique counter for object names
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Custom indicator initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
//--- Set indicator short name
|
|
IndicatorSetString(INDICATOR_SHORTNAME, "Trendline Indicator");
|
|
IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
|
|
|
|
//--- Initialize arrays
|
|
ArrayResize(swingPoints, 0);
|
|
ArrayResize(trendlineNames, 0);
|
|
|
|
//--- Initialize trade signal
|
|
currentSignal.active = false;
|
|
currentTradeTicket = 0;
|
|
|
|
//--- Initialize trade object if real trading is enabled
|
|
if(InpExecuteRealTrades)
|
|
{
|
|
trade.SetExpertMagicNumber(InpMagicNumber);
|
|
trade.SetDeviationInPoints(InpSlippage);
|
|
trade.SetAsyncMode(false);
|
|
|
|
//--- Set filling mode based on broker capabilities
|
|
ENUM_ORDER_TYPE_FILLING filling = (ENUM_ORDER_TYPE_FILLING)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
|
|
if(filling == 0)
|
|
filling = ORDER_FILLING_FOK; // Default if not specified
|
|
trade.SetTypeFilling(filling);
|
|
}
|
|
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("=== TRENDLINE INDICATOR INITIALIZED ===");
|
|
Print("Symbol: ", _Symbol, " | Period: ", EnumToString(_Period));
|
|
Print("Settings: SwingPeriod=", InpSwingPeriod, " MinBars=", InpMinBarsBetween, " MaxPoints=", InpMaxSwingPoints, " Lookback=", InpLookbackBars);
|
|
Print("Colors: Resistance=", ColorToString(InpResistanceColor), " Support=", ColorToString(InpSupportColor));
|
|
Print("Display: Resistance=", InpShowResistanceLines, " Support=", InpShowSupportLines, " Ray=", InpShowRay);
|
|
if(InpEnableTrading)
|
|
{
|
|
Print("Trading: ENABLED | Min R/R=", InpMinRiskRewardRatio, " | Max R/R=", InpMaxRiskRewardRatio, " | Use Nearest Levels=", InpUseNearestLevels);
|
|
Print("Ignore R/R Rejection: ", InpIgnoreRRRejection, " (", (InpIgnoreRRRejection ? "All signals accepted" : "R/R validation active"), ")");
|
|
if(InpExecuteRealTrades)
|
|
{
|
|
Print("*** REAL TRADING ENABLED *** - Trades will be executed with real money!");
|
|
Print("Lot Size: ", InpLotSize, " | Magic: ", InpMagicNumber);
|
|
}
|
|
else
|
|
{
|
|
Print("Real Trading: DISABLED (Simulation only)");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("Trendline Indicator initialized");
|
|
}
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Custom indicator deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
//--- Delete all trendline objects
|
|
DeleteAllTrendlines();
|
|
|
|
//--- Delete trade signal objects
|
|
DeleteTradeSignalObjects();
|
|
|
|
Print("Trendline Indicator deinitialized");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Custom indicator iteration function |
|
|
//+------------------------------------------------------------------+
|
|
int OnCalculate(const int rates_total,
|
|
const int prev_calculated,
|
|
const datetime &time[],
|
|
const double &open[],
|
|
const double &high[],
|
|
const double &low[],
|
|
const double &close[],
|
|
const long &tick_volume[],
|
|
const long &volume[],
|
|
const int &spread[])
|
|
{
|
|
if(rates_total < InpSwingPeriod * 2 + 1)
|
|
return(0);
|
|
|
|
//--- Always recalculate to update trendlines as new data comes in
|
|
//--- Only skip if we're on the exact same bar (same rates_total and same last bar time)
|
|
static int lastRatesTotal = 0;
|
|
static datetime lastBarTime = 0;
|
|
|
|
if(prev_calculated > 0)
|
|
{
|
|
datetime currentBarTime = time[rates_total - 1];
|
|
// Only skip if it's the exact same calculation (same bar count and same time)
|
|
if(rates_total == lastRatesTotal && currentBarTime == lastBarTime)
|
|
{
|
|
return(rates_total); // Same bar, skip recalculation
|
|
}
|
|
lastBarTime = currentBarTime;
|
|
}
|
|
lastRatesTotal = rates_total;
|
|
|
|
//--- Don't set arrays as series - we'll work with normal indexing
|
|
//--- Copy arrays to work with them
|
|
datetime timeArray[];
|
|
double highArray[];
|
|
double lowArray[];
|
|
double closeArray[];
|
|
|
|
ArraySetAsSeries(timeArray, false);
|
|
ArraySetAsSeries(highArray, false);
|
|
ArraySetAsSeries(lowArray, false);
|
|
ArraySetAsSeries(closeArray, false);
|
|
|
|
int timeCopied = CopyTime(_Symbol, _Period, 0, rates_total, timeArray);
|
|
int highCopied = CopyHigh(_Symbol, _Period, 0, rates_total, highArray);
|
|
int lowCopied = CopyLow(_Symbol, _Period, 0, rates_total, lowArray);
|
|
int closeCopied = CopyClose(_Symbol, _Period, 0, rates_total, closeArray);
|
|
|
|
//--- Data validation
|
|
if(timeCopied <= 0 || highCopied <= 0 || lowCopied <= 0 || closeCopied <= 0)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("ERROR: Failed to copy data - Time: ", timeCopied, " High: ", highCopied, " Low: ", lowCopied, " Close: ", closeCopied);
|
|
return(0);
|
|
}
|
|
|
|
//--- Validate data sanity
|
|
if(InpShowDebugInfo && prev_calculated == 0)
|
|
{
|
|
Print("=== DATA VALIDATION ===");
|
|
Print("Symbol: ", _Symbol, " Period: ", EnumToString(_Period));
|
|
Print("Total bars: ", rates_total, " | Time copied: ", timeCopied, " | High copied: ", highCopied, " | Low copied: ", lowCopied);
|
|
Print("Data range: ", TimeToString(timeArray[0]), " to ", TimeToString(timeArray[ArraySize(timeArray)-1]));
|
|
Print("Price range: High=", highArray[ArrayMaximum(highArray, 0, rates_total)], " Low=", lowArray[ArrayMinimum(lowArray, 0, rates_total)]);
|
|
Print("Swing Period: ", InpSwingPeriod, " | Min Bars Between: ", InpMinBarsBetween, " | Max Points: ", InpMaxSwingPoints);
|
|
}
|
|
|
|
//--- Find swing points
|
|
FindSwingPoints(rates_total, timeArray, highArray, lowArray);
|
|
|
|
//--- Draw trendlines
|
|
DrawTrendlines(rates_total, timeArray, prev_calculated);
|
|
|
|
//--- Check for trading signals if enabled
|
|
if(InpEnableTrading)
|
|
{
|
|
CheckTradingSignals(rates_total, timeArray, highArray, lowArray, closeArray);
|
|
}
|
|
|
|
return(rates_total);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find swing highs and lows |
|
|
//+------------------------------------------------------------------+
|
|
void FindSwingPoints(const int rates_total,
|
|
const datetime &time[],
|
|
const double &high[],
|
|
const double &low[])
|
|
{
|
|
ArrayResize(swingPoints, 0);
|
|
|
|
//--- Arrays are in normal order: index 0 = oldest, index (rates_total-1) = newest
|
|
//--- Focus on recent data if lookback is specified
|
|
int lookbackStart = 0;
|
|
if(InpLookbackBars > 0 && rates_total > InpLookbackBars)
|
|
{
|
|
lookbackStart = rates_total - InpLookbackBars;
|
|
}
|
|
|
|
int start = MathMax(InpSwingPeriod, lookbackStart); // Start from older bars (but respect lookback)
|
|
int end = rates_total - InpSwingPeriod - 1; // End at newer bars
|
|
|
|
//--- Find swing highs
|
|
if(InpShowResistanceLines)
|
|
{
|
|
SwingPoint highPoints[];
|
|
ArrayResize(highPoints, 0);
|
|
|
|
for(int i = start; i <= end; i++)
|
|
{
|
|
bool isSwingHigh = true;
|
|
double currentHigh = high[i];
|
|
|
|
//--- Check if current high is higher than surrounding bars
|
|
for(int j = 1; j <= InpSwingPeriod; j++)
|
|
{
|
|
if(high[i - j] >= currentHigh || high[i + j] >= currentHigh)
|
|
{
|
|
isSwingHigh = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(isSwingHigh)
|
|
{
|
|
SwingPoint point;
|
|
point.time = time[i];
|
|
point.price = currentHigh;
|
|
point.isHigh = true;
|
|
point.barIndex = i;
|
|
|
|
//--- Check minimum distance from previous swing high
|
|
bool canAdd = true;
|
|
if(ArraySize(highPoints) > 0)
|
|
{
|
|
int lastIndex = ArraySize(highPoints) - 1;
|
|
int barsBetween = MathAbs(highPoints[lastIndex].barIndex - point.barIndex);
|
|
|
|
if(barsBetween < InpMinBarsBetween)
|
|
canAdd = false;
|
|
}
|
|
|
|
if(canAdd)
|
|
{
|
|
ArrayResize(highPoints, ArraySize(highPoints) + 1);
|
|
highPoints[ArraySize(highPoints) - 1] = point;
|
|
}
|
|
|
|
//--- Limit number of swing points
|
|
if(ArraySize(highPoints) >= InpMaxSwingPoints / 2)
|
|
break;
|
|
}
|
|
}
|
|
|
|
//--- Add high points to main array
|
|
for(int i = 0; i < ArraySize(highPoints); i++)
|
|
{
|
|
ArrayResize(swingPoints, ArraySize(swingPoints) + 1);
|
|
swingPoints[ArraySize(swingPoints) - 1] = highPoints[i];
|
|
}
|
|
|
|
if(InpShowDebugInfo && InpShowDetailedStats)
|
|
{
|
|
Print("Swing Highs Found: ", ArraySize(highPoints));
|
|
for(int i = 0; i < ArraySize(highPoints) && i < 5; i++)
|
|
{
|
|
Print(" High[", i, "]: Bar=", highPoints[i].barIndex, " Time=", TimeToString(highPoints[i].time), " Price=", DoubleToString(highPoints[i].price, _Digits));
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- Find swing lows
|
|
if(InpShowSupportLines)
|
|
{
|
|
SwingPoint lowPoints[];
|
|
ArrayResize(lowPoints, 0);
|
|
|
|
for(int i = start; i <= end; i++)
|
|
{
|
|
bool isSwingLow = true;
|
|
double currentLow = low[i];
|
|
|
|
//--- Check if current low is lower than surrounding bars
|
|
for(int j = 1; j <= InpSwingPeriod; j++)
|
|
{
|
|
if(low[i - j] <= currentLow || low[i + j] <= currentLow)
|
|
{
|
|
isSwingLow = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(isSwingLow)
|
|
{
|
|
SwingPoint point;
|
|
point.time = time[i];
|
|
point.price = currentLow;
|
|
point.isHigh = false;
|
|
point.barIndex = i;
|
|
|
|
//--- Check minimum distance from previous swing low
|
|
bool canAdd = true;
|
|
if(ArraySize(lowPoints) > 0)
|
|
{
|
|
int lastIndex = ArraySize(lowPoints) - 1;
|
|
int barsBetween = MathAbs(lowPoints[lastIndex].barIndex - point.barIndex);
|
|
|
|
if(barsBetween < InpMinBarsBetween)
|
|
canAdd = false;
|
|
}
|
|
|
|
if(canAdd)
|
|
{
|
|
ArrayResize(lowPoints, ArraySize(lowPoints) + 1);
|
|
lowPoints[ArraySize(lowPoints) - 1] = point;
|
|
}
|
|
|
|
//--- Limit number of swing points
|
|
if(ArraySize(lowPoints) >= InpMaxSwingPoints / 2)
|
|
break;
|
|
}
|
|
}
|
|
|
|
//--- Add low points to main array
|
|
for(int i = 0; i < ArraySize(lowPoints); i++)
|
|
{
|
|
ArrayResize(swingPoints, ArraySize(swingPoints) + 1);
|
|
swingPoints[ArraySize(swingPoints) - 1] = lowPoints[i];
|
|
}
|
|
|
|
if(InpShowDebugInfo && InpShowDetailedStats)
|
|
{
|
|
Print("Swing Lows Found: ", ArraySize(lowPoints));
|
|
for(int i = 0; i < ArraySize(lowPoints) && i < 5; i++)
|
|
{
|
|
Print(" Low[", i, "]: Bar=", lowPoints[i].barIndex, " Time=", TimeToString(lowPoints[i].time), " Price=", DoubleToString(lowPoints[i].price, _Digits));
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- Sort swing points by bar index (oldest first)
|
|
SortSwingPoints();
|
|
|
|
//--- Summary feedback
|
|
if(InpShowDebugInfo)
|
|
{
|
|
int totalHighs = 0, totalLows = 0;
|
|
for(int i = 0; i < ArraySize(swingPoints); i++)
|
|
{
|
|
if(swingPoints[i].isHigh) totalHighs++;
|
|
else totalLows++;
|
|
}
|
|
static int lastTotal = -1;
|
|
static datetime lastSummaryTime = 0;
|
|
datetime currentTime = TimeCurrent();
|
|
|
|
// Show summary when swing points change OR every 100 bars to show it's updating
|
|
if(ArraySize(swingPoints) != lastTotal || (currentTime - lastSummaryTime) > 3600)
|
|
{
|
|
Print("=== SWING POINTS SUMMARY ===");
|
|
Print("Current bar time: ", TimeToString(time[rates_total - 1]));
|
|
Print("Total swing points: ", ArraySize(swingPoints), " (Highs: ", totalHighs, " | Lows: ", totalLows, ")");
|
|
lastTotal = ArraySize(swingPoints);
|
|
lastSummaryTime = currentTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Sort swing points by bar index |
|
|
//+------------------------------------------------------------------+
|
|
void SortSwingPoints()
|
|
{
|
|
int size = ArraySize(swingPoints);
|
|
for(int i = 0; i < size - 1; i++)
|
|
{
|
|
for(int j = i + 1; j < size; j++)
|
|
{
|
|
if(swingPoints[i].barIndex > swingPoints[j].barIndex)
|
|
{
|
|
SwingPoint temp = swingPoints[i];
|
|
swingPoints[i] = swingPoints[j];
|
|
swingPoints[j] = temp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw trendlines connecting swing points |
|
|
//+------------------------------------------------------------------+
|
|
void DrawTrendlines(const int rates_total, const datetime &time[], const int prev_calculated)
|
|
{
|
|
//--- Delete existing trendlines first
|
|
DeleteAllTrendlines();
|
|
|
|
int swingCount = ArraySize(swingPoints);
|
|
if(swingCount < 2)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("WARNING: Not enough swing points to draw trendlines: ", swingCount, " (need at least 2)");
|
|
return;
|
|
}
|
|
|
|
trendlineCount = 0;
|
|
ArrayResize(trendlineNames, 0);
|
|
|
|
int resistanceLines = 0;
|
|
int supportLines = 0;
|
|
|
|
//--- Focus on most recent swing points (prioritize recent trendlines)
|
|
//--- Only use swing points from the most recent portion of data
|
|
int recentStartIndex = 0;
|
|
if(InpLookbackBars > 0)
|
|
{
|
|
// Find the first swing point that's within our lookback window
|
|
int lookbackBarIndex = (rates_total > InpLookbackBars) ? (rates_total - InpLookbackBars) : 0;
|
|
for(int i = 0; i < swingCount; i++)
|
|
{
|
|
if(swingPoints[i].barIndex >= lookbackBarIndex)
|
|
{
|
|
recentStartIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- Draw trendlines for swing highs (resistance) - connect recent highs
|
|
if(InpShowResistanceLines)
|
|
{
|
|
for(int i = recentStartIndex; i < swingCount; i++)
|
|
{
|
|
if(!swingPoints[i].isHigh)
|
|
continue;
|
|
|
|
//--- Find next high
|
|
for(int j = i + 1; j < swingCount; j++)
|
|
{
|
|
if(!swingPoints[j].isHigh)
|
|
continue;
|
|
|
|
//--- Check if both points are valid
|
|
if(swingPoints[i].barIndex >= rates_total || swingPoints[j].barIndex >= rates_total)
|
|
continue;
|
|
|
|
DrawTrendline(swingPoints[i], swingPoints[j], rates_total, time, true);
|
|
resistanceLines++;
|
|
break; // Only connect to the next high
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- Draw trendlines for swing lows (support) - connect recent lows
|
|
if(InpShowSupportLines)
|
|
{
|
|
for(int i = recentStartIndex; i < swingCount; i++)
|
|
{
|
|
if(swingPoints[i].isHigh)
|
|
continue;
|
|
|
|
//--- Find next low
|
|
for(int j = i + 1; j < swingCount; j++)
|
|
{
|
|
if(swingPoints[j].isHigh)
|
|
continue;
|
|
|
|
//--- Check if both points are valid
|
|
if(swingPoints[i].barIndex >= rates_total || swingPoints[j].barIndex >= rates_total)
|
|
continue;
|
|
|
|
DrawTrendline(swingPoints[i], swingPoints[j], rates_total, time, false);
|
|
supportLines++;
|
|
break; // Only connect to the next low
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- Feedback summary
|
|
static int lastResistance = -1, lastSupport = -1;
|
|
static datetime lastSummaryTime = 0;
|
|
datetime currentTime = TimeCurrent();
|
|
|
|
// Show summary when counts change OR periodically to show updates
|
|
bool shouldShow = (resistanceLines != lastResistance || supportLines != lastSupport || prev_calculated == 0);
|
|
bool periodicUpdate = (currentTime - lastSummaryTime) > 3600; // Every hour
|
|
|
|
if(InpShowDebugInfo && (shouldShow || periodicUpdate))
|
|
{
|
|
Print("=== TRENDLINE SUMMARY ===");
|
|
Print("Current time: ", TimeToString(time[rates_total - 1]), " | Bars: ", rates_total);
|
|
Print("Resistance lines: ", resistanceLines, " | Support lines: ", supportLines, " | Total: ", (resistanceLines + supportLines));
|
|
Print("Objects created: ", trendlineCount);
|
|
|
|
//--- Verify objects exist
|
|
int actualObjects = 0;
|
|
int total = ObjectsTotal(0, 0, -1);
|
|
string prefix = "TrendlineIndicator_TL_";
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
string name = ObjectName(0, i, 0, -1);
|
|
if(StringFind(name, prefix) == 0)
|
|
actualObjects++;
|
|
}
|
|
Print("Objects on chart: ", actualObjects, " (expected: ", trendlineCount, ")");
|
|
|
|
if(actualObjects != trendlineCount && actualObjects > 0)
|
|
Print("WARNING: Object count mismatch - some objects may not be visible");
|
|
|
|
if(shouldShow)
|
|
{
|
|
Print("TRENDLINES UPDATED - New swing points detected!");
|
|
}
|
|
|
|
lastResistance = resistanceLines;
|
|
lastSupport = supportLines;
|
|
lastSummaryTime = currentTime;
|
|
}
|
|
|
|
//--- Redraw chart to show objects
|
|
ChartRedraw(0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw a single trendline |
|
|
//+------------------------------------------------------------------+
|
|
void DrawTrendline(SwingPoint &point1, SwingPoint &point2,
|
|
const int rates_total, const datetime &time[],
|
|
const bool isResistance)
|
|
{
|
|
//--- Create unique name with indicator prefix
|
|
uniqueObjectCounter++;
|
|
string prefix = "TrendlineIndicator_TL_";
|
|
//--- Use point times and counter for better uniqueness
|
|
long timeHash = (long)point1.time + (long)point2.time;
|
|
string name = prefix + IntegerToString(trendlineCount) + "_" + IntegerToString(uniqueObjectCounter) + "_" + IntegerToString(timeHash) + "_" + IntegerToString(GetTickCount64());
|
|
|
|
//--- Delete object if it already exists (with retry)
|
|
int attempts = 0;
|
|
while(ObjectFind(0, name) >= 0 && attempts < 10)
|
|
{
|
|
ObjectDelete(0, name);
|
|
Sleep(50);
|
|
ChartRedraw(0);
|
|
attempts++;
|
|
//--- Generate new name if still exists
|
|
uniqueObjectCounter++;
|
|
timeHash = (long)point1.time + (long)point2.time + uniqueObjectCounter;
|
|
name = prefix + IntegerToString(trendlineCount) + "_" + IntegerToString(uniqueObjectCounter) + "_" + IntegerToString(timeHash) + "_" + IntegerToString(GetTickCount64());
|
|
}
|
|
|
|
if(attempts >= 10)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("WARNING: Could not create unique name after ", attempts, " attempts. Using: ", name);
|
|
}
|
|
|
|
//--- Calculate end time
|
|
datetime endTime;
|
|
if(InpExtendLines)
|
|
{
|
|
//--- Extend to right edge of chart (use most recent bar time)
|
|
datetime latestTime = time[rates_total - 1];
|
|
endTime = latestTime + PeriodSeconds(_Period) * InpExtensionBars;
|
|
}
|
|
else
|
|
{
|
|
//--- Use extension bars
|
|
int extensionBars = MathMax(InpExtensionBars, rates_total - point2.barIndex);
|
|
endTime = point2.time + PeriodSeconds(_Period) * extensionBars;
|
|
}
|
|
|
|
//--- Calculate end price using linear extrapolation
|
|
double priceDiff = point2.price - point1.price;
|
|
datetime timeDiff = point2.time - point1.time;
|
|
double slope = 0.0;
|
|
|
|
if(timeDiff > 0)
|
|
{
|
|
slope = priceDiff / (double)timeDiff;
|
|
}
|
|
|
|
double endPrice = point2.price + slope * (endTime - point2.time);
|
|
|
|
//--- Create trendline object (use 0 for current chart)
|
|
bool created = false;
|
|
if(InpShowRay)
|
|
{
|
|
//--- Create ray (infinite extension)
|
|
created = ObjectCreate(0, name, OBJ_TREND, 0, point1.time, point1.price, point2.time, point2.price);
|
|
if(created)
|
|
{
|
|
ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, isResistance ? InpResistanceColor : InpSupportColor);
|
|
ObjectSetInteger(0, name, OBJPROP_STYLE, InpLineStyle);
|
|
ObjectSetInteger(0, name, OBJPROP_WIDTH, InpLineWidth);
|
|
ObjectSetInteger(0, name, OBJPROP_BACK, false);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Create regular trendline
|
|
created = ObjectCreate(0, name, OBJ_TREND, 0, point1.time, point1.price, endTime, endPrice);
|
|
if(created)
|
|
{
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, isResistance ? InpResistanceColor : InpSupportColor);
|
|
ObjectSetInteger(0, name, OBJPROP_STYLE, InpLineStyle);
|
|
ObjectSetInteger(0, name, OBJPROP_WIDTH, InpLineWidth);
|
|
ObjectSetInteger(0, name, OBJPROP_BACK, false);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
|
|
}
|
|
}
|
|
|
|
if(!created)
|
|
{
|
|
int error = GetLastError();
|
|
ResetLastError();
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("ERROR: Failed to create trendline: ", name);
|
|
Print(" Error code: ", error);
|
|
Print(" Point1: Bar=", point1.barIndex, " Time=", TimeToString(point1.time), " Price=", DoubleToString(point1.price, _Digits));
|
|
Print(" Point2: Bar=", point2.barIndex, " Time=", TimeToString(point2.time), " Price=", DoubleToString(point2.price, _Digits));
|
|
Print(" EndTime: ", TimeToString(endTime), " EndPrice: ", DoubleToString(endPrice, _Digits));
|
|
|
|
//--- Data validation
|
|
if(point1.time <= 0 || point2.time <= 0)
|
|
Print(" VALIDATION ERROR: Invalid time values");
|
|
if(point1.price <= 0 || point2.price <= 0)
|
|
Print(" VALIDATION ERROR: Invalid price values");
|
|
if(endTime <= point2.time)
|
|
Print(" VALIDATION ERROR: End time must be after point2 time");
|
|
}
|
|
}
|
|
else if(InpShowDebugInfo && InpShowDetailedStats)
|
|
{
|
|
Print("Created: ", name, " | Type: ", (isResistance ? "Resistance" : "Support"), " | Points: ", TimeToString(point1.time), " -> ", TimeToString(point2.time));
|
|
}
|
|
|
|
//--- Add price label if enabled
|
|
if(InpShowLabels && created)
|
|
{
|
|
string labelName = name + "_Label";
|
|
if(ObjectFind(0, labelName) >= 0)
|
|
{
|
|
ObjectDelete(0, labelName);
|
|
}
|
|
|
|
string labelText = DoubleToString(point2.price, _Digits);
|
|
|
|
if(ObjectCreate(0, labelName, OBJ_TEXT, 0, point2.time, point2.price))
|
|
{
|
|
ObjectSetString(0, labelName, OBJPROP_TEXT, labelText);
|
|
ObjectSetInteger(0, labelName, OBJPROP_COLOR, isResistance ? InpResistanceColor : InpSupportColor);
|
|
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
|
|
ObjectSetString(0, labelName, OBJPROP_FONT, "Arial");
|
|
}
|
|
}
|
|
|
|
//--- Store trendline name and info only if created successfully
|
|
if(created)
|
|
{
|
|
//--- Store trendline info for trading signals
|
|
if(InpEnableTrading)
|
|
{
|
|
ArrayResize(trendlines, trendlineCount + 1);
|
|
trendlines[trendlineCount].name = name;
|
|
trendlines[trendlineCount].point1Price = point1.price;
|
|
trendlines[trendlineCount].point2Price = point2.price;
|
|
trendlines[trendlineCount].point1Time = point1.time;
|
|
trendlines[trendlineCount].point2Time = point2.time;
|
|
trendlines[trendlineCount].isResistance = isResistance;
|
|
trendlines[trendlineCount].slope = slope;
|
|
// Calculate intercept: price = slope * time + intercept
|
|
trendlines[trendlineCount].intercept = point1.price - slope * (double)point1.time;
|
|
}
|
|
|
|
ArrayResize(trendlineNames, trendlineCount + 1);
|
|
trendlineNames[trendlineCount] = name;
|
|
if(InpShowLabels)
|
|
{
|
|
ArrayResize(trendlineNames, trendlineCount + 2);
|
|
trendlineNames[trendlineCount + 1] = name + "_Label";
|
|
}
|
|
trendlineCount++;
|
|
if(InpShowLabels) trendlineCount++;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Delete all trendline objects |
|
|
//+------------------------------------------------------------------+
|
|
void DeleteAllTrendlines()
|
|
{
|
|
//--- Delete stored trendlines
|
|
for(int i = 0; i < ArraySize(trendlineNames); i++)
|
|
{
|
|
if(ObjectFind(0, trendlineNames[i]) >= 0)
|
|
{
|
|
ObjectDelete(0, trendlineNames[i]);
|
|
}
|
|
}
|
|
|
|
//--- Also delete any remaining trendlines with our prefix
|
|
string prefix = "TrendlineIndicator_TL_";
|
|
int total = ObjectsTotal(0, 0, -1);
|
|
int deleted = 0;
|
|
for(int i = total - 1; i >= 0; i--)
|
|
{
|
|
string name = ObjectName(0, i, 0, -1);
|
|
if(StringFind(name, prefix) == 0)
|
|
{
|
|
if(ObjectDelete(0, name))
|
|
deleted++;
|
|
}
|
|
}
|
|
|
|
if(deleted > 0)
|
|
{
|
|
ChartRedraw(0);
|
|
Sleep(100); // Give time for deletion to complete
|
|
}
|
|
|
|
ArrayResize(trendlineNames, 0);
|
|
trendlineCount = 0;
|
|
ArrayResize(trendlines, 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for trading signals based on trendline breaks |
|
|
//+------------------------------------------------------------------+
|
|
void CheckTradingSignals(const int rates_total,
|
|
datetime &time[],
|
|
double &high[],
|
|
double &low[],
|
|
double &close[])
|
|
{
|
|
if(ArraySize(trendlines) == 0)
|
|
return;
|
|
|
|
//--- Get current price data
|
|
double currentClose = close[rates_total - 1];
|
|
double currentHigh = high[rates_total - 1];
|
|
double currentLow = low[rates_total - 1];
|
|
datetime currentTime = time[rates_total - 1];
|
|
|
|
//--- Check if we already have an active signal
|
|
if(currentSignal.active)
|
|
{
|
|
CheckTradeStatus(currentTime, currentClose, currentHigh, currentLow);
|
|
|
|
//--- Check real trade status if enabled
|
|
if(InpExecuteRealTrades && currentTradeTicket > 0)
|
|
{
|
|
CheckRealTradeStatus();
|
|
}
|
|
return;
|
|
}
|
|
|
|
//--- Check each trendline for breaks
|
|
for(int i = 0; i < ArraySize(trendlines); i++)
|
|
{
|
|
double trendlinePrice = CalculateTrendlinePrice(trendlines[i], currentTime);
|
|
|
|
if(trendlinePrice <= 0)
|
|
continue;
|
|
|
|
//--- Check for resistance break (bullish signal)
|
|
if(trendlines[i].isResistance && currentClose > trendlinePrice && currentHigh > trendlinePrice)
|
|
{
|
|
// Price broke above resistance - BUY signal
|
|
if(CreateBuySignal(trendlines[i], currentTime, currentClose, rates_total, time))
|
|
break;
|
|
}
|
|
//--- Check for support break (bearish signal)
|
|
else if(!trendlines[i].isResistance && currentClose < trendlinePrice && currentLow < trendlinePrice)
|
|
{
|
|
// Price broke below support - SELL signal
|
|
if(CreateSellSignal(trendlines[i], currentTime, currentClose, rates_total, time))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate price on trendline at given time |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateTrendlinePrice(TrendlineInfo &tl, datetime time)
|
|
{
|
|
// Price = slope * time + intercept
|
|
double price = tl.slope * (double)time + tl.intercept;
|
|
|
|
//--- Validate: price should be between point1 and point2 prices (or extended)
|
|
if(time >= tl.point1Time && time <= tl.point2Time)
|
|
{
|
|
return price;
|
|
}
|
|
//--- Allow extension beyond point2 for future prediction
|
|
else if(time > tl.point2Time)
|
|
{
|
|
return price; // Extended forward
|
|
}
|
|
|
|
return 0; // Invalid
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Create buy signal |
|
|
//+------------------------------------------------------------------+
|
|
bool CreateBuySignal(TrendlineInfo &tl, datetime entryTime, double entryPrice, const int rates_total, datetime &time[])
|
|
{
|
|
//--- Find nearest support level (for SL) and next resistance (for TP)
|
|
double stopLoss = 0;
|
|
double takeProfit = 0;
|
|
|
|
if(InpUseNearestLevels)
|
|
{
|
|
stopLoss = FindNearestSupport(entryPrice, rates_total, time);
|
|
takeProfit = FindNextResistance(entryPrice, rates_total, time);
|
|
}
|
|
|
|
//--- Validate levels found
|
|
if(stopLoss <= 0 || takeProfit <= 0 || stopLoss >= entryPrice || takeProfit <= entryPrice)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Signal rejected: Invalid support/resistance levels - SL: ", stopLoss, " TP: ", takeProfit);
|
|
return false;
|
|
}
|
|
|
|
//--- Calculate Risk/Reward ratio
|
|
double risk = entryPrice - stopLoss;
|
|
double reward = takeProfit - entryPrice;
|
|
|
|
if(risk <= 0 || reward <= 0)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Signal rejected: Invalid risk/reward calculation");
|
|
return false;
|
|
}
|
|
|
|
double riskRewardRatio = reward / risk;
|
|
|
|
//--- Check if R/R meets minimum requirement
|
|
if(!InpIgnoreRRRejection && riskRewardRatio < InpMinRiskRewardRatio)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Signal rejected: R/R ratio ", DoubleToString(riskRewardRatio, 2), " below minimum ", InpMinRiskRewardRatio);
|
|
return false;
|
|
}
|
|
|
|
//--- Sanity check for maximum R/R
|
|
if(!InpIgnoreRRRejection && riskRewardRatio > InpMaxRiskRewardRatio)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Signal rejected: R/R ratio ", DoubleToString(riskRewardRatio, 2), " exceeds maximum ", InpMaxRiskRewardRatio);
|
|
return false;
|
|
}
|
|
|
|
//--- Warn if R/R is outside recommended range but still allow if ignore is enabled
|
|
if(InpIgnoreRRRejection)
|
|
{
|
|
if(riskRewardRatio < InpMinRiskRewardRatio)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Signal WARNING: R/R ratio ", DoubleToString(riskRewardRatio, 2), " below recommended minimum ", InpMinRiskRewardRatio, " (Accepted due to IgnoreRRRejection)");
|
|
}
|
|
else if(riskRewardRatio > InpMaxRiskRewardRatio)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Signal WARNING: R/R ratio ", DoubleToString(riskRewardRatio, 2), " exceeds recommended maximum ", InpMaxRiskRewardRatio, " (Accepted due to IgnoreRRRejection)");
|
|
}
|
|
}
|
|
|
|
//--- Create the signal
|
|
currentSignal.active = true;
|
|
currentSignal.type = ORDER_TYPE_BUY;
|
|
currentSignal.entryPrice = entryPrice;
|
|
currentSignal.entryTime = entryTime;
|
|
currentSignal.trendlineName = tl.name;
|
|
currentSignal.stopLoss = stopLoss;
|
|
currentSignal.takeProfit = takeProfit;
|
|
|
|
//--- Execute real trade if enabled
|
|
if(InpExecuteRealTrades)
|
|
{
|
|
//--- Check if we already have an open position with our magic number
|
|
bool hasPosition = false;
|
|
for(int pos = PositionsTotal() - 1; pos >= 0; pos--)
|
|
{
|
|
ulong ticket = PositionGetTicket(pos);
|
|
if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol)
|
|
{
|
|
if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
|
|
{
|
|
hasPosition = true;
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Signal: Position already exists (Ticket: ", ticket, "), skipping trade execution");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!hasPosition)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("Attempting to execute BUY trade - Entry: ", entryPrice, " SL: ", stopLoss, " TP: ", takeProfit);
|
|
|
|
if(ExecuteBuyTrade(entryPrice, stopLoss, takeProfit))
|
|
{
|
|
currentTradeTicket = trade.ResultOrder();
|
|
if(InpShowDebugInfo)
|
|
Print("Real BUY trade executed - Ticket: ", currentTradeTicket);
|
|
}
|
|
else
|
|
{
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("Failed to execute real BUY trade - Error: ", trade.ResultRetcode(), " - ", trade.ResultRetcodeDescription());
|
|
Print(" Symbol: ", _Symbol, " Lot: ", InpLotSize, " Magic: ", InpMagicNumber);
|
|
}
|
|
// Still create signal for visualization even if trade fails
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Real trading is disabled - only showing signal for visualization
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Signal created (Visualization only) - Real Trading is DISABLED. Set InpExecuteRealTrades=true to execute trades.");
|
|
}
|
|
|
|
//--- Draw trade levels on chart
|
|
if(InpShowTradeLevels)
|
|
{
|
|
DrawTradeLevels();
|
|
}
|
|
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("=== BUY SIGNAL GENERATED ===");
|
|
Print("Entry: ", DoubleToString(entryPrice, _Digits));
|
|
Print("SL (Support): ", DoubleToString(stopLoss, _Digits), " | Risk: ", DoubleToString(risk, _Digits));
|
|
Print("TP (Resistance): ", DoubleToString(takeProfit, _Digits), " | Reward: ", DoubleToString(reward, _Digits));
|
|
Print("Risk/Reward Ratio: ", DoubleToString(riskRewardRatio, 2), ":1");
|
|
Print("Trendline: ", tl.name);
|
|
if(InpExecuteRealTrades && currentTradeTicket > 0)
|
|
Print("Real Trade Ticket: ", currentTradeTicket);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Create sell signal |
|
|
//+------------------------------------------------------------------+
|
|
bool CreateSellSignal(TrendlineInfo &tl, datetime entryTime, double entryPrice, const int rates_total, datetime &time[])
|
|
{
|
|
//--- Find nearest resistance level (for SL) and next support (for TP)
|
|
double stopLoss = 0;
|
|
double takeProfit = 0;
|
|
|
|
if(InpUseNearestLevels)
|
|
{
|
|
stopLoss = FindNearestResistance(entryPrice, rates_total, time);
|
|
takeProfit = FindNextSupport(entryPrice, rates_total, time);
|
|
}
|
|
|
|
//--- Validate levels found
|
|
if(stopLoss <= 0 || takeProfit <= 0 || stopLoss <= entryPrice || takeProfit >= entryPrice)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Signal rejected: Invalid support/resistance levels - SL: ", stopLoss, " TP: ", takeProfit);
|
|
return false;
|
|
}
|
|
|
|
//--- Calculate Risk/Reward ratio
|
|
double risk = stopLoss - entryPrice;
|
|
double reward = entryPrice - takeProfit;
|
|
|
|
if(risk <= 0 || reward <= 0)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Signal rejected: Invalid risk/reward calculation");
|
|
return false;
|
|
}
|
|
|
|
double riskRewardRatio = reward / risk;
|
|
|
|
//--- Check if R/R meets minimum requirement
|
|
if(!InpIgnoreRRRejection && riskRewardRatio < InpMinRiskRewardRatio)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Signal rejected: R/R ratio ", DoubleToString(riskRewardRatio, 2), " below minimum ", InpMinRiskRewardRatio);
|
|
return false;
|
|
}
|
|
|
|
//--- Sanity check for maximum R/R
|
|
if(!InpIgnoreRRRejection && riskRewardRatio > InpMaxRiskRewardRatio)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Signal rejected: R/R ratio ", DoubleToString(riskRewardRatio, 2), " exceeds maximum ", InpMaxRiskRewardRatio);
|
|
return false;
|
|
}
|
|
|
|
//--- Warn if R/R is outside recommended range but still allow if ignore is enabled
|
|
if(InpIgnoreRRRejection)
|
|
{
|
|
if(riskRewardRatio < InpMinRiskRewardRatio)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Signal WARNING: R/R ratio ", DoubleToString(riskRewardRatio, 2), " below recommended minimum ", InpMinRiskRewardRatio, " (Accepted due to IgnoreRRRejection)");
|
|
}
|
|
else if(riskRewardRatio > InpMaxRiskRewardRatio)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Signal WARNING: R/R ratio ", DoubleToString(riskRewardRatio, 2), " exceeds recommended maximum ", InpMaxRiskRewardRatio, " (Accepted due to IgnoreRRRejection)");
|
|
}
|
|
}
|
|
|
|
//--- Create the signal
|
|
currentSignal.active = true;
|
|
currentSignal.type = ORDER_TYPE_SELL;
|
|
currentSignal.entryPrice = entryPrice;
|
|
currentSignal.entryTime = entryTime;
|
|
currentSignal.trendlineName = tl.name;
|
|
currentSignal.stopLoss = stopLoss;
|
|
currentSignal.takeProfit = takeProfit;
|
|
|
|
//--- Execute real trade if enabled
|
|
if(InpExecuteRealTrades)
|
|
{
|
|
//--- Check if we already have an open position with our magic number
|
|
bool hasPosition = false;
|
|
for(int pos = PositionsTotal() - 1; pos >= 0; pos--)
|
|
{
|
|
ulong ticket = PositionGetTicket(pos);
|
|
if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol)
|
|
{
|
|
if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
|
|
{
|
|
hasPosition = true;
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Signal: Position already exists (Ticket: ", ticket, "), skipping trade execution");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!hasPosition)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("Attempting to execute SELL trade - Entry: ", entryPrice, " SL: ", stopLoss, " TP: ", takeProfit);
|
|
|
|
if(ExecuteSellTrade(entryPrice, stopLoss, takeProfit))
|
|
{
|
|
currentTradeTicket = trade.ResultOrder();
|
|
if(InpShowDebugInfo)
|
|
Print("Real SELL trade executed - Ticket: ", currentTradeTicket);
|
|
}
|
|
else
|
|
{
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("Failed to execute real SELL trade - Error: ", trade.ResultRetcode(), " - ", trade.ResultRetcodeDescription());
|
|
Print(" Symbol: ", _Symbol, " Lot: ", InpLotSize, " Magic: ", InpMagicNumber);
|
|
}
|
|
// Still create signal for visualization even if trade fails
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Real trading is disabled - only showing signal for visualization
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Signal created (Visualization only) - Real Trading is DISABLED. Set InpExecuteRealTrades=true to execute trades.");
|
|
}
|
|
|
|
//--- Draw trade levels on chart
|
|
if(InpShowTradeLevels)
|
|
{
|
|
DrawTradeLevels();
|
|
}
|
|
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("=== SELL SIGNAL GENERATED ===");
|
|
Print("Entry: ", DoubleToString(entryPrice, _Digits));
|
|
Print("SL (Resistance): ", DoubleToString(stopLoss, _Digits), " | Risk: ", DoubleToString(risk, _Digits));
|
|
Print("TP (Support): ", DoubleToString(takeProfit, _Digits), " | Reward: ", DoubleToString(reward, _Digits));
|
|
Print("Risk/Reward Ratio: ", DoubleToString(riskRewardRatio, 2), ":1");
|
|
Print("Trendline: ", tl.name);
|
|
if(InpExecuteRealTrades && currentTradeTicket > 0)
|
|
Print("Real Trade Ticket: ", currentTradeTicket);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw trade levels (Entry, SL, TP) on chart |
|
|
//+------------------------------------------------------------------+
|
|
void DrawTradeLevels()
|
|
{
|
|
string prefix = "TrendlineIndicator_Trade_";
|
|
|
|
//--- Entry line
|
|
currentSignal.entryObjectName = prefix + "Entry";
|
|
if(ObjectFind(0, currentSignal.entryObjectName) >= 0)
|
|
ObjectDelete(0, currentSignal.entryObjectName);
|
|
|
|
if(ObjectCreate(0, currentSignal.entryObjectName, OBJ_HLINE, 0, 0, currentSignal.entryPrice))
|
|
{
|
|
ObjectSetInteger(0, currentSignal.entryObjectName, OBJPROP_COLOR, (currentSignal.type == ORDER_TYPE_BUY) ? InpBuyColor : InpSellColor);
|
|
ObjectSetInteger(0, currentSignal.entryObjectName, OBJPROP_STYLE, STYLE_DASH);
|
|
ObjectSetInteger(0, currentSignal.entryObjectName, OBJPROP_WIDTH, 2);
|
|
ObjectSetString(0, currentSignal.entryObjectName, OBJPROP_TEXT, "Entry: " + DoubleToString(currentSignal.entryPrice, _Digits));
|
|
}
|
|
|
|
//--- Stop Loss line
|
|
currentSignal.slObjectName = prefix + "SL";
|
|
if(ObjectFind(0, currentSignal.slObjectName) >= 0)
|
|
ObjectDelete(0, currentSignal.slObjectName);
|
|
|
|
if(ObjectCreate(0, currentSignal.slObjectName, OBJ_HLINE, 0, 0, currentSignal.stopLoss))
|
|
{
|
|
ObjectSetInteger(0, currentSignal.slObjectName, OBJPROP_COLOR, clrRed);
|
|
ObjectSetInteger(0, currentSignal.slObjectName, OBJPROP_STYLE, STYLE_DOT);
|
|
ObjectSetInteger(0, currentSignal.slObjectName, OBJPROP_WIDTH, 2);
|
|
ObjectSetString(0, currentSignal.slObjectName, OBJPROP_TEXT, "SL: " + DoubleToString(currentSignal.stopLoss, _Digits));
|
|
}
|
|
|
|
//--- Take Profit line
|
|
currentSignal.tpObjectName = prefix + "TP";
|
|
if(ObjectFind(0, currentSignal.tpObjectName) >= 0)
|
|
ObjectDelete(0, currentSignal.tpObjectName);
|
|
|
|
if(ObjectCreate(0, currentSignal.tpObjectName, OBJ_HLINE, 0, 0, currentSignal.takeProfit))
|
|
{
|
|
ObjectSetInteger(0, currentSignal.tpObjectName, OBJPROP_COLOR, clrGreen);
|
|
ObjectSetInteger(0, currentSignal.tpObjectName, OBJPROP_STYLE, STYLE_DOT);
|
|
ObjectSetInteger(0, currentSignal.tpObjectName, OBJPROP_WIDTH, 2);
|
|
ObjectSetString(0, currentSignal.tpObjectName, OBJPROP_TEXT, "TP: " + DoubleToString(currentSignal.takeProfit, _Digits));
|
|
}
|
|
|
|
ChartRedraw(0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if trade hit SL or TP |
|
|
//+------------------------------------------------------------------+
|
|
void CheckTradeStatus(datetime currentTime, double currentClose, double currentHigh, double currentLow)
|
|
{
|
|
bool tradeClosed = false;
|
|
string reason = "";
|
|
|
|
if(currentSignal.type == ORDER_TYPE_BUY)
|
|
{
|
|
//--- Check for TP hit
|
|
if(currentHigh >= currentSignal.takeProfit)
|
|
{
|
|
tradeClosed = true;
|
|
reason = "TP HIT";
|
|
}
|
|
//--- Check for SL hit
|
|
else if(currentLow <= currentSignal.stopLoss)
|
|
{
|
|
tradeClosed = true;
|
|
reason = "SL HIT";
|
|
}
|
|
}
|
|
else // SELL
|
|
{
|
|
//--- Check for TP hit
|
|
if(currentLow <= currentSignal.takeProfit)
|
|
{
|
|
tradeClosed = true;
|
|
reason = "TP HIT";
|
|
}
|
|
//--- Check for SL hit
|
|
else if(currentHigh >= currentSignal.stopLoss)
|
|
{
|
|
tradeClosed = true;
|
|
reason = "SL HIT";
|
|
}
|
|
}
|
|
|
|
if(tradeClosed)
|
|
{
|
|
double profit = 0;
|
|
if(currentSignal.type == ORDER_TYPE_BUY)
|
|
{
|
|
if(reason == "TP HIT")
|
|
profit = currentSignal.takeProfit - currentSignal.entryPrice;
|
|
else
|
|
profit = currentSignal.stopLoss - currentSignal.entryPrice;
|
|
}
|
|
else
|
|
{
|
|
if(reason == "TP HIT")
|
|
profit = currentSignal.entryPrice - currentSignal.takeProfit;
|
|
else
|
|
profit = currentSignal.entryPrice - currentSignal.stopLoss;
|
|
}
|
|
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("=== TRADE CLOSED: ", reason, " ===");
|
|
Print("Entry: ", DoubleToString(currentSignal.entryPrice, _Digits), " | Close: ", DoubleToString(currentClose, _Digits));
|
|
Print("Profit/Loss: ", DoubleToString(profit, _Digits), " (", (profit > 0 ? "WIN" : "LOSS"), ")");
|
|
}
|
|
|
|
//--- Close real trade if enabled
|
|
if(InpExecuteRealTrades && currentTradeTicket > 0)
|
|
{
|
|
CloseRealTrade();
|
|
}
|
|
|
|
//--- Delete trade level objects
|
|
DeleteTradeSignalObjects();
|
|
|
|
//--- Reset signal
|
|
currentSignal.active = false;
|
|
currentTradeTicket = 0;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Delete trade signal objects |
|
|
//+------------------------------------------------------------------+
|
|
void DeleteTradeSignalObjects()
|
|
{
|
|
string prefix = "TrendlineIndicator_Trade_";
|
|
int total = ObjectsTotal(0, 0, -1);
|
|
for(int i = total - 1; i >= 0; i--)
|
|
{
|
|
string name = ObjectName(0, i, 0, -1);
|
|
if(StringFind(name, prefix) == 0)
|
|
{
|
|
ObjectDelete(0, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find nearest support level below entry price |
|
|
//+------------------------------------------------------------------+
|
|
double FindNearestSupport(double entryPrice, const int rates_total, datetime &time[])
|
|
{
|
|
double nearestSupport = 0;
|
|
double minDistance = DBL_MAX;
|
|
double pipsMultiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
|
|
double tolerance = InpLevelTolerancePips * _Point * pipsMultiplier;
|
|
|
|
datetime currentTime = time[rates_total - 1];
|
|
int supportLinesChecked = 0;
|
|
int validSupportLines = 0;
|
|
|
|
for(int i = 0; i < ArraySize(trendlines); i++)
|
|
{
|
|
if(trendlines[i].isResistance)
|
|
continue; // Skip resistance lines
|
|
|
|
supportLinesChecked++;
|
|
|
|
// Get the price level of this support trendline at current time
|
|
double supportPrice = CalculateTrendlinePrice(trendlines[i], currentTime);
|
|
|
|
if(supportPrice <= 0)
|
|
{
|
|
if(InpShowDebugInfo && supportLinesChecked <= 3) // Only log first few to avoid spam
|
|
Print("FindNearestSupport: Invalid price for trendline ", i, " - Price: ", supportPrice);
|
|
continue;
|
|
}
|
|
|
|
// Support must be below entry price
|
|
if(supportPrice >= entryPrice - tolerance)
|
|
{
|
|
if(InpShowDebugInfo && supportLinesChecked <= 3)
|
|
Print("FindNearestSupport: Support too close/above entry - Support: ", supportPrice, " Entry: ", entryPrice);
|
|
continue;
|
|
}
|
|
|
|
validSupportLines++;
|
|
double distance = entryPrice - supportPrice;
|
|
|
|
// Find the closest support below entry
|
|
if(distance < minDistance && distance > 0)
|
|
{
|
|
minDistance = distance;
|
|
nearestSupport = supportPrice;
|
|
}
|
|
}
|
|
|
|
if(InpShowDebugInfo && nearestSupport == 0)
|
|
Print("FindNearestSupport: No valid support found - Checked: ", supportLinesChecked, " Valid: ", validSupportLines, " Entry: ", entryPrice);
|
|
|
|
return nearestSupport;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find next resistance level above entry price |
|
|
//+------------------------------------------------------------------+
|
|
double FindNextResistance(double entryPrice, const int rates_total, datetime &time[])
|
|
{
|
|
double nextResistance = 0;
|
|
double minDistance = DBL_MAX;
|
|
double pipsMultiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
|
|
double tolerance = InpLevelTolerancePips * _Point * pipsMultiplier;
|
|
|
|
datetime currentTime = time[rates_total - 1];
|
|
int resistanceLinesChecked = 0;
|
|
int validResistanceLines = 0;
|
|
|
|
for(int i = 0; i < ArraySize(trendlines); i++)
|
|
{
|
|
if(!trendlines[i].isResistance)
|
|
continue; // Skip support lines
|
|
|
|
resistanceLinesChecked++;
|
|
|
|
// Get the price level of this resistance trendline at current time
|
|
double resistancePrice = CalculateTrendlinePrice(trendlines[i], currentTime);
|
|
|
|
if(resistancePrice <= 0)
|
|
{
|
|
if(InpShowDebugInfo && resistanceLinesChecked <= 3) // Only log first few to avoid spam
|
|
Print("FindNextResistance: Invalid price for trendline ", i, " - Price: ", resistancePrice);
|
|
continue;
|
|
}
|
|
|
|
// Resistance must be above entry price
|
|
if(resistancePrice <= entryPrice + tolerance)
|
|
{
|
|
if(InpShowDebugInfo && resistanceLinesChecked <= 3)
|
|
Print("FindNextResistance: Resistance too close/below entry - Resistance: ", resistancePrice, " Entry: ", entryPrice);
|
|
continue;
|
|
}
|
|
|
|
validResistanceLines++;
|
|
double distance = resistancePrice - entryPrice;
|
|
|
|
// Find the closest resistance above entry
|
|
if(distance < minDistance && distance > 0)
|
|
{
|
|
minDistance = distance;
|
|
nextResistance = resistancePrice;
|
|
}
|
|
}
|
|
|
|
if(InpShowDebugInfo && nextResistance == 0)
|
|
Print("FindNextResistance: No valid resistance found - Checked: ", resistanceLinesChecked, " Valid: ", validResistanceLines, " Entry: ", entryPrice);
|
|
|
|
return nextResistance;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find nearest resistance level above entry price |
|
|
//+------------------------------------------------------------------+
|
|
double FindNearestResistance(double entryPrice, const int rates_total, datetime &time[])
|
|
{
|
|
double nearestResistance = 0;
|
|
double minDistance = DBL_MAX;
|
|
double pipsMultiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
|
|
double tolerance = InpLevelTolerancePips * _Point * pipsMultiplier;
|
|
|
|
for(int i = 0; i < ArraySize(trendlines); i++)
|
|
{
|
|
if(!trendlines[i].isResistance)
|
|
continue; // Skip support lines
|
|
|
|
// Get the price level of this resistance trendline at current time
|
|
datetime currentTime = time[rates_total - 1];
|
|
double resistancePrice = CalculateTrendlinePrice(trendlines[i], currentTime);
|
|
|
|
if(resistancePrice <= 0)
|
|
continue;
|
|
|
|
// Resistance must be above entry price
|
|
if(resistancePrice <= entryPrice + tolerance)
|
|
continue;
|
|
|
|
double distance = resistancePrice - entryPrice;
|
|
|
|
// Find the closest resistance above entry
|
|
if(distance < minDistance && distance > 0)
|
|
{
|
|
minDistance = distance;
|
|
nearestResistance = resistancePrice;
|
|
}
|
|
}
|
|
|
|
return nearestResistance;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find next support level below entry price |
|
|
//+------------------------------------------------------------------+
|
|
double FindNextSupport(double entryPrice, const int rates_total, datetime &time[])
|
|
{
|
|
double nextSupport = 0;
|
|
double minDistance = DBL_MAX;
|
|
double pipsMultiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
|
|
double tolerance = InpLevelTolerancePips * _Point * pipsMultiplier;
|
|
|
|
for(int i = 0; i < ArraySize(trendlines); i++)
|
|
{
|
|
if(trendlines[i].isResistance)
|
|
continue; // Skip resistance lines
|
|
|
|
// Get the price level of this support trendline at current time
|
|
datetime currentTime = time[rates_total - 1];
|
|
double supportPrice = CalculateTrendlinePrice(trendlines[i], currentTime);
|
|
|
|
if(supportPrice <= 0)
|
|
continue;
|
|
|
|
// Support must be below entry price
|
|
if(supportPrice >= entryPrice - tolerance)
|
|
continue;
|
|
|
|
double distance = entryPrice - supportPrice;
|
|
|
|
// Find the closest support below entry
|
|
if(distance < minDistance && distance > 0)
|
|
{
|
|
minDistance = distance;
|
|
nextSupport = supportPrice;
|
|
}
|
|
}
|
|
|
|
return nextSupport;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Execute real BUY trade |
|
|
//+------------------------------------------------------------------+
|
|
bool ExecuteBuyTrade(double entryPrice, double stopLoss, double takeProfit)
|
|
{
|
|
//--- Get symbol info
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
double minStopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
|
|
|
|
//--- Normalize prices properly
|
|
double normalizedSL = NormalizeDouble(stopLoss, digits);
|
|
double normalizedTP = NormalizeDouble(takeProfit, digits);
|
|
|
|
//--- Validate SL/TP distances from current price
|
|
if(normalizedSL >= ask - minStopLevel)
|
|
{
|
|
normalizedSL = NormalizeDouble(ask - minStopLevel - point, digits);
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Trade: Adjusted SL to minimum stop level: ", normalizedSL);
|
|
}
|
|
|
|
if(normalizedTP <= ask + minStopLevel)
|
|
{
|
|
normalizedTP = NormalizeDouble(ask + minStopLevel + point, digits);
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Trade: Adjusted TP to minimum stop level: ", normalizedTP);
|
|
}
|
|
|
|
//--- Final validation
|
|
if(normalizedSL >= ask || normalizedTP <= ask)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Trade: Invalid SL/TP after normalization - Ask: ", ask, " SL: ", normalizedSL, " TP: ", normalizedTP);
|
|
return false;
|
|
}
|
|
|
|
//--- Execute buy order
|
|
bool result = trade.Buy(InpLotSize, _Symbol, 0, normalizedSL, normalizedTP, "Trendline Breakout BUY");
|
|
|
|
//--- Wait a bit for order processing
|
|
Sleep(100);
|
|
|
|
//--- Check result
|
|
if(!result)
|
|
{
|
|
uint retcode = trade.ResultRetcode();
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("BUY Trade Error: ", retcode, " - ", trade.ResultRetcodeDescription());
|
|
Print(" Entry: ", DoubleToString(entryPrice, digits), " SL: ", DoubleToString(normalizedSL, digits), " TP: ", DoubleToString(normalizedTP, digits), " Lot: ", InpLotSize);
|
|
Print(" Ask: ", DoubleToString(ask, digits), " Symbol: ", _Symbol, " Digits: ", digits);
|
|
Print(" Min Stop Level: ", minStopLevel, " points");
|
|
}
|
|
|
|
//--- Retcode 0 might mean pending - check if order was actually placed
|
|
if(retcode == 0)
|
|
{
|
|
// Check if we have a pending order
|
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = OrderGetTicket(i);
|
|
if(ticket > 0 && OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == InpMagicNumber)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("BUY Order found as pending - Ticket: ", ticket);
|
|
return true; // Order exists, consider it successful
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(InpShowDebugInfo)
|
|
{
|
|
ulong orderTicket = trade.ResultOrder();
|
|
ulong dealTicket = trade.ResultDeal();
|
|
Print("BUY Trade SUCCESS - Order: ", orderTicket, " Deal: ", dealTicket);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Execute real SELL trade |
|
|
//+------------------------------------------------------------------+
|
|
bool ExecuteSellTrade(double entryPrice, double stopLoss, double takeProfit)
|
|
{
|
|
//--- Get symbol info
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
double minStopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
|
|
|
|
//--- Normalize prices properly
|
|
double normalizedSL = NormalizeDouble(stopLoss, digits);
|
|
double normalizedTP = NormalizeDouble(takeProfit, digits);
|
|
|
|
//--- Validate SL/TP distances from current price
|
|
if(normalizedSL <= bid + minStopLevel)
|
|
{
|
|
normalizedSL = NormalizeDouble(bid + minStopLevel + point, digits);
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Trade: Adjusted SL to minimum stop level: ", normalizedSL);
|
|
}
|
|
|
|
if(normalizedTP >= bid - minStopLevel)
|
|
{
|
|
normalizedTP = NormalizeDouble(bid - minStopLevel - point, digits);
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Trade: Adjusted TP to minimum stop level: ", normalizedTP);
|
|
}
|
|
|
|
//--- Final validation
|
|
if(normalizedSL <= bid || normalizedTP >= bid)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Trade: Invalid SL/TP after normalization - Bid: ", bid, " SL: ", normalizedSL, " TP: ", normalizedTP);
|
|
return false;
|
|
}
|
|
|
|
//--- Execute sell order
|
|
bool result = trade.Sell(InpLotSize, _Symbol, 0, normalizedSL, normalizedTP, "Trendline Breakout SELL");
|
|
|
|
//--- Wait a bit for order processing
|
|
Sleep(100);
|
|
|
|
//--- Check result
|
|
if(!result)
|
|
{
|
|
uint retcode = trade.ResultRetcode();
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("SELL Trade Error: ", retcode, " - ", trade.ResultRetcodeDescription());
|
|
Print(" Entry: ", DoubleToString(entryPrice, digits), " SL: ", DoubleToString(normalizedSL, digits), " TP: ", DoubleToString(normalizedTP, digits), " Lot: ", InpLotSize);
|
|
Print(" Bid: ", DoubleToString(bid, digits), " Symbol: ", _Symbol, " Digits: ", digits);
|
|
Print(" Min Stop Level: ", minStopLevel, " points");
|
|
}
|
|
|
|
//--- Retcode 0 might mean pending - check if order was actually placed
|
|
if(retcode == 0)
|
|
{
|
|
// Check if we have a pending order
|
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = OrderGetTicket(i);
|
|
if(ticket > 0 && OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == InpMagicNumber)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
Print("SELL Order found as pending - Ticket: ", ticket);
|
|
return true; // Order exists, consider it successful
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(InpShowDebugInfo)
|
|
{
|
|
ulong orderTicket = trade.ResultOrder();
|
|
ulong dealTicket = trade.ResultDeal();
|
|
Print("SELL Trade SUCCESS - Order: ", orderTicket, " Deal: ", dealTicket);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check real trade status |
|
|
//+------------------------------------------------------------------+
|
|
void CheckRealTradeStatus()
|
|
{
|
|
if(currentTradeTicket == 0)
|
|
return;
|
|
|
|
//--- Check if position still exists
|
|
if(!PositionSelectByTicket(currentTradeTicket))
|
|
{
|
|
//--- Position was closed (SL/TP hit or manually closed)
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("Real trade closed - Ticket: ", currentTradeTicket);
|
|
}
|
|
|
|
currentTradeTicket = 0;
|
|
currentSignal.active = false;
|
|
DeleteTradeSignalObjects();
|
|
return;
|
|
}
|
|
|
|
//--- Position still open - check if we need to update SL/TP
|
|
double currentSL = PositionGetDouble(POSITION_SL);
|
|
double currentTP = PositionGetDouble(POSITION_TP);
|
|
|
|
//--- Update SL/TP if they differ from signal levels (trailing stop logic could go here)
|
|
// For now, we just monitor - could add trailing stop logic later
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close real trade |
|
|
//+------------------------------------------------------------------+
|
|
void CloseRealTrade()
|
|
{
|
|
if(currentTradeTicket == 0)
|
|
return;
|
|
|
|
if(PositionSelectByTicket(currentTradeTicket))
|
|
{
|
|
bool result = trade.PositionClose(currentTradeTicket);
|
|
|
|
if(result)
|
|
{
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("Real trade closed successfully - Ticket: ", currentTradeTicket);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(InpShowDebugInfo)
|
|
{
|
|
Print("Failed to close real trade - Ticket: ", currentTradeTicket, " Error: ", trade.ResultRetcode());
|
|
}
|
|
}
|
|
}
|
|
|
|
currentTradeTicket = 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+ |