Files
zhutoutoutousan 98a87a69ca Update
2026-02-13 08:03:25 +01:00

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;
}
//+------------------------------------------------------------------+