938 lines
30 KiB
MQL5
938 lines
30 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| BFS-EA.mq5 |
|
|
//| GIT under Copyright 2025, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com/en/users/johnhlomohang/ |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
|
|
#property link "https://www.mql5.com/en/users/johnhlomohang/"
|
|
#property version "1.00"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Arrays\List.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Input Parameters |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_HISTORY_MODE
|
|
{
|
|
MODE_PREVIOUS_BARS, // Previous Bars
|
|
MODE_PREVIOUS_DAYS, // Previous Days
|
|
MODE_YESTERDAY_ONLY // Yesterday Only
|
|
};
|
|
|
|
input ENUM_HISTORY_MODE HistoryMode = MODE_PREVIOUS_DAYS; // History Mode
|
|
input int BarsLookback = 300; // Bars Lookback
|
|
input int DaysLookback = 5; // Days Lookback
|
|
input int SwingPeriod = 3; // Swing Period (lookback bars)
|
|
input double BiasThreshold = 0.3; // Bias Threshold
|
|
input bool EnableTrading = true; // Enable Trading
|
|
input double LotSize = 0.1; // Lot Size
|
|
input int TakeProfit = 100; // Take Profit (points)
|
|
input int StopLoss = 50; // Stop Loss (points)
|
|
input bool VisualizeStructure = true; // Visualize Structure
|
|
input color BullishColor = clrGreen; // Bullish Color
|
|
input color BearishColor = clrRed; // Bearish Color
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Swing Node Structure |
|
|
//+------------------------------------------------------------------+
|
|
struct SwingNode
|
|
{
|
|
int index; // bar index
|
|
double price; // high or low price
|
|
bool isHigh; // true = swing high, false = swing low
|
|
bool bullish; // inferred direction
|
|
int level; // BFS level
|
|
datetime time; // bar time
|
|
|
|
// Default constructor
|
|
SwingNode(): index(0), price(0.0), isHigh(false), bullish(false), level(-1), time(0) {}
|
|
|
|
// Parameterized constructor
|
|
SwingNode(int idx, double prc, bool high, bool bull, int lvl, datetime t):
|
|
index(idx), price(prc), isHigh(high), bullish(bull), level(lvl), time(t) {}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| BFS Queue Item |
|
|
//+------------------------------------------------------------------+
|
|
struct BFSItem
|
|
{
|
|
int nodeIndex; // index in nodes array
|
|
int level; // BFS level
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Edge Structure for Graph |
|
|
//+------------------------------------------------------------------+
|
|
struct Edge
|
|
{
|
|
int from; // Source node index
|
|
int to; // Destination node index
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global Variables |
|
|
//+------------------------------------------------------------------+
|
|
SwingNode nodes[]; // Array of swing nodes
|
|
Edge edges[]; // Array of edges
|
|
int edgeCount = 0; // Number of edges
|
|
int nodeCount = 0; // Number of nodes
|
|
double currentBias = 0.0; // Current market bias
|
|
bool allowBuy = false; // Allow buy trades
|
|
bool allowSell = false; // Allow sell trades
|
|
datetime lastRecalcTime = 0; // Last recalculation time
|
|
int lastBarCount = 0; // Last bar count
|
|
CTrade trade; // Trading object for position management
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// Validate inputs
|
|
if(BarsLookback <= 0)
|
|
{
|
|
Print("Error: BarsLookback must be positive");
|
|
return INIT_PARAMETERS_INCORRECT;
|
|
}
|
|
|
|
if(DaysLookback <= 0)
|
|
{
|
|
Print("Error: DaysLookback must be positive");
|
|
return INIT_PARAMETERS_INCORRECT;
|
|
}
|
|
|
|
if(SwingPeriod <= 0)
|
|
{
|
|
Print("Error: SwingPeriod must be positive");
|
|
return INIT_PARAMETERS_INCORRECT;
|
|
}
|
|
|
|
// Set timer for periodic updates (every 5 minutes)
|
|
EventSetTimer(300);
|
|
|
|
// Set trading parameters
|
|
trade.SetExpertMagicNumber(12345);
|
|
trade.SetDeviationInPoints(10);
|
|
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
|
|
|
// Initial calculation
|
|
CalculateBFSStructure();
|
|
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
// Remove timer
|
|
EventKillTimer();
|
|
|
|
// Clean up visualization objects
|
|
if(VisualizeStructure)
|
|
{
|
|
RemoveVisualization();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
// Check for new bar
|
|
if(IsNewBar())
|
|
{
|
|
CalculateBFSStructure();
|
|
|
|
// Check for trading signals if enabled
|
|
if(EnableTrading)
|
|
{
|
|
CheckTradingSignals();
|
|
}
|
|
}
|
|
|
|
// Update visualization if enabled
|
|
if(VisualizeStructure)
|
|
{
|
|
UpdateVisualization();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Timer function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer()
|
|
{
|
|
// Periodic recalculation (fallback)
|
|
CalculateBFSStructure();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate BFS Market Structure |
|
|
//+------------------------------------------------------------------+
|
|
void CalculateBFSStructure()
|
|
{
|
|
// Reset values
|
|
currentBias = 0.0;
|
|
allowBuy = false;
|
|
allowSell = false;
|
|
|
|
// Step 1: Detect swing points
|
|
if(!DetectSwingNodes())
|
|
{
|
|
Print("Failed to detect swing nodes");
|
|
return;
|
|
}
|
|
|
|
// Step 2: Build directed graph (edges)
|
|
BuildDirectedGraph();
|
|
|
|
// Step 3: Select BFS root based on mode
|
|
int rootIndex = SelectBFSRoot();
|
|
if(rootIndex < 0)
|
|
{
|
|
Print("No valid root found");
|
|
return;
|
|
}
|
|
|
|
// Step 4: Perform BFS traversal
|
|
PerformBFSTraversal(rootIndex);
|
|
|
|
// Step 5: Calculate market bias
|
|
CalculateMarketBias();
|
|
|
|
// Step 6: Update trading permissions
|
|
UpdateTradingPermissions();
|
|
|
|
// Update last calculation time
|
|
lastRecalcTime = TimeCurrent();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Detect Swing Nodes |
|
|
//+------------------------------------------------------------------+
|
|
bool DetectSwingNodes()
|
|
{
|
|
// Determine how many bars to analyze
|
|
int barsToAnalyze = 0;
|
|
datetime startTime = 0;
|
|
|
|
switch(HistoryMode)
|
|
{
|
|
case MODE_PREVIOUS_BARS:
|
|
barsToAnalyze = BarsLookback + SwingPeriod * 2;
|
|
break;
|
|
|
|
case MODE_PREVIOUS_DAYS:
|
|
startTime = TimeCurrent() - (DaysLookback * 86400);
|
|
barsToAnalyze = iBars(_Symbol, _Period);
|
|
break;
|
|
|
|
case MODE_YESTERDAY_ONLY:
|
|
{MqlDateTime yesterday;
|
|
TimeToStruct(TimeCurrent() - 86400, yesterday);
|
|
yesterday.hour = 0;
|
|
yesterday.min = 0;
|
|
yesterday.sec = 0;
|
|
startTime = StructToTime(yesterday);
|
|
barsToAnalyze = iBars(_Symbol, _Period);
|
|
break;}
|
|
}
|
|
|
|
// Get historical data
|
|
int totalBars = MathMin(barsToAnalyze, iBars(_Symbol, _Period));
|
|
if(totalBars < SwingPeriod * 2 + 10)
|
|
{
|
|
Print("Not enough bars for swing detection");
|
|
return false;
|
|
}
|
|
|
|
// Prepare arrays for highs and lows
|
|
double highs[], lows[];
|
|
datetime times[];
|
|
|
|
ArrayResize(highs, totalBars);
|
|
ArrayResize(lows, totalBars);
|
|
ArrayResize(times, totalBars);
|
|
|
|
// Copy data
|
|
if(CopyHigh(_Symbol, _Period, 0, totalBars, highs) != totalBars ||
|
|
CopyLow(_Symbol, _Period, 0, totalBars, lows) != totalBars ||
|
|
CopyTime(_Symbol, _Period, 0, totalBars, times) != totalBars)
|
|
{
|
|
Print("Failed to copy historical data");
|
|
return false;
|
|
}
|
|
|
|
// Detect swing highs and lows
|
|
ArrayResize(nodes, 0);
|
|
nodeCount = 0;
|
|
|
|
for(int i = SwingPeriod; i < totalBars - SwingPeriod; i++)
|
|
{
|
|
bool isSwingHigh = true;
|
|
bool isSwingLow = true;
|
|
|
|
// Check for swing high
|
|
for(int j = 1; j <= SwingPeriod; j++)
|
|
{
|
|
if(highs[i] <= highs[i-j] || highs[i] <= highs[i+j])
|
|
{
|
|
isSwingHigh = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check for swing low
|
|
for(int j = 1; j <= SwingPeriod; j++)
|
|
{
|
|
if(lows[i] >= lows[i-j] || lows[i] >= lows[i+j])
|
|
{
|
|
isSwingLow = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add swing node if found
|
|
if(isSwingHigh || isSwingLow)
|
|
{
|
|
SwingNode node;
|
|
node.index = i;
|
|
node.time = times[i];
|
|
node.isHigh = isSwingHigh;
|
|
|
|
if(isSwingHigh)
|
|
node.price = highs[i];
|
|
else
|
|
node.price = lows[i];
|
|
|
|
node.bullish = false; // Will be determined during BFS
|
|
node.level = -1; // Not assigned yet
|
|
|
|
ArrayResize(nodes, nodeCount + 1);
|
|
nodes[nodeCount] = node;
|
|
nodeCount++;
|
|
}
|
|
}
|
|
|
|
Print("Detected ", nodeCount, " swing nodes");
|
|
return nodeCount > 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Build Directed Graph |
|
|
//+------------------------------------------------------------------+
|
|
void BuildDirectedGraph()
|
|
{
|
|
// Clear existing edges
|
|
ArrayResize(edges, 0);
|
|
edgeCount = 0;
|
|
|
|
// Create edges from older to newer nodes (time progression)
|
|
for(int i = 0; i < nodeCount; i++)
|
|
{
|
|
// Find the next valid swing to connect to
|
|
int nextIndex = -1;
|
|
|
|
// Look for the next swing of alternating type
|
|
for(int j = i + 1; j < nodeCount; j++)
|
|
{
|
|
// Check if swing types alternate (high-low-high-low pattern)
|
|
if(nodes[j].isHigh != nodes[i].isHigh)
|
|
{
|
|
// Check if there are no other valid swings between i and j
|
|
bool validConnection = true;
|
|
|
|
// Skip checking intermediate nodes for simplicity
|
|
// In a more sophisticated version, we could check for
|
|
// price action patterns between swings
|
|
|
|
if(validConnection)
|
|
{
|
|
nextIndex = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add edge if found
|
|
if(nextIndex > i)
|
|
{
|
|
Edge edge;
|
|
edge.from = i;
|
|
edge.to = nextIndex;
|
|
|
|
ArrayResize(edges, edgeCount + 1);
|
|
edges[edgeCount] = edge;
|
|
edgeCount++;
|
|
}
|
|
}
|
|
|
|
Print("Created ", edgeCount, " edges in the graph");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Select BFS Root Based on Mode |
|
|
//+------------------------------------------------------------------+
|
|
int SelectBFSRoot()
|
|
{
|
|
int rootIndex = -1;
|
|
datetime currentTime = TimeCurrent();
|
|
|
|
switch(HistoryMode)
|
|
{
|
|
case MODE_PREVIOUS_BARS:
|
|
// Find the oldest swing within BarsLookback
|
|
for(int i = 0; i < nodeCount; i++)
|
|
{
|
|
if(nodes[i].index >= BarsLookback)
|
|
{
|
|
rootIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MODE_PREVIOUS_DAYS:
|
|
{
|
|
datetime startTime = currentTime - (DaysLookback * 86400);
|
|
// Find first swing after startTime
|
|
for(int i = 0; i < nodeCount; i++)
|
|
{
|
|
if(nodes[i].time >= startTime)
|
|
{
|
|
rootIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MODE_YESTERDAY_ONLY:
|
|
{
|
|
MqlDateTime yesterday;
|
|
TimeToStruct(currentTime - 86400, yesterday);
|
|
yesterday.hour = 0;
|
|
yesterday.min = 0;
|
|
yesterday.sec = 0;
|
|
datetime yesterdayStart = StructToTime(yesterday);
|
|
|
|
datetime todayStart = yesterdayStart + 86400;
|
|
|
|
// Find first swing of yesterday
|
|
for(int i = 0; i < nodeCount; i++)
|
|
{
|
|
if(nodes[i].time >= yesterdayStart && nodes[i].time < todayStart)
|
|
{
|
|
rootIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no root found with mode criteria, use the oldest node
|
|
if(rootIndex == -1 && nodeCount > 0)
|
|
{
|
|
rootIndex = 0;
|
|
}
|
|
|
|
return rootIndex;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Perform BFS Traversal |
|
|
//+------------------------------------------------------------------+
|
|
void PerformBFSTraversal(int rootIndex)
|
|
{
|
|
if(rootIndex < 0 || rootIndex >= nodeCount)
|
|
return;
|
|
|
|
// Reset node levels
|
|
for(int i = 0; i < nodeCount; i++)
|
|
{
|
|
nodes[i].level = -1;
|
|
nodes[i].bullish = false;
|
|
}
|
|
|
|
// Initialize queue for BFS
|
|
BFSItem queue[];
|
|
int queueSize = nodeCount;
|
|
ArrayResize(queue, queueSize);
|
|
|
|
// Enqueue root
|
|
queue[0].nodeIndex = rootIndex;
|
|
queue[0].level = 0;
|
|
nodes[rootIndex].level = 0;
|
|
int queueStart = 0;
|
|
int queueEnd = 1;
|
|
|
|
// BFS traversal
|
|
while(queueStart < queueEnd)
|
|
{
|
|
// Dequeue
|
|
BFSItem current = queue[queueStart];
|
|
queueStart++;
|
|
|
|
int currentIdx = current.nodeIndex;
|
|
int currentLevel = current.level;
|
|
|
|
// Classify node direction
|
|
ClassifyNodeDirection(currentIdx);
|
|
|
|
// Find and enqueue children (nodes reachable from current node)
|
|
for(int e = 0; e < edgeCount; e++)
|
|
{
|
|
if(edges[e].from == currentIdx)
|
|
{
|
|
int childIdx = edges[e].to;
|
|
if(nodes[childIdx].level == -1) // Not visited yet
|
|
{
|
|
nodes[childIdx].level = currentLevel + 1;
|
|
|
|
// Enqueue child
|
|
if(queueEnd < queueSize)
|
|
{
|
|
queue[queueEnd].nodeIndex = childIdx;
|
|
queue[queueEnd].level = currentLevel + 1;
|
|
queueEnd++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Classify Node Direction |
|
|
//+------------------------------------------------------------------+
|
|
void ClassifyNodeDirection(int nodeIndex)
|
|
{
|
|
if(nodeIndex <= 0)
|
|
{
|
|
// First node, classify based on recent price action
|
|
double currentPrice = iClose(_Symbol, _Period, 0);
|
|
if(nodes[nodeIndex].isHigh)
|
|
{
|
|
nodes[nodeIndex].bullish = (currentPrice > nodes[nodeIndex].price);
|
|
}
|
|
else
|
|
{
|
|
nodes[nodeIndex].bullish = (currentPrice < nodes[nodeIndex].price);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Find previous node of the same type
|
|
int prevSameType = -1;
|
|
for(int i = nodeIndex - 1; i >= 0; i--)
|
|
{
|
|
if(nodes[i].isHigh == nodes[nodeIndex].isHigh)
|
|
{
|
|
prevSameType = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(prevSameType >= 0)
|
|
{
|
|
// Compare with previous node of same type
|
|
if(nodes[nodeIndex].isHigh)
|
|
{
|
|
// Swing high: higher high = bullish, lower high = bearish
|
|
nodes[nodeIndex].bullish = (nodes[nodeIndex].price > nodes[prevSameType].price);
|
|
}
|
|
else
|
|
{
|
|
// Swing low: higher low = bullish, lower low = bearish
|
|
nodes[nodeIndex].bullish = (nodes[nodeIndex].price > nodes[prevSameType].price);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No previous node of same type, use simple logic
|
|
double currentPrice = iClose(_Symbol, _Period, 0);
|
|
if(nodes[nodeIndex].isHigh)
|
|
{
|
|
nodes[nodeIndex].bullish = (currentPrice > nodes[nodeIndex].price);
|
|
}
|
|
else
|
|
{
|
|
nodes[nodeIndex].bullish = (currentPrice < nodes[nodeIndex].price);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate Market Bias |
|
|
//+------------------------------------------------------------------+
|
|
void CalculateMarketBias()
|
|
{
|
|
double totalWeight = 0.0;
|
|
double weightedSum = 0.0;
|
|
|
|
for(int i = 0; i < nodeCount; i++)
|
|
{
|
|
if(nodes[i].level >= 0) // Only consider visited nodes
|
|
{
|
|
// Calculate weight based on level (higher weight for lower levels)
|
|
double weight = 1.0 / (1.0 + nodes[i].level * 0.2);
|
|
|
|
// Add to weighted sum
|
|
if(nodes[i].bullish)
|
|
weightedSum += weight;
|
|
else
|
|
weightedSum -= weight;
|
|
|
|
totalWeight += weight;
|
|
}
|
|
}
|
|
|
|
if(totalWeight > 0)
|
|
{
|
|
currentBias = weightedSum / totalWeight;
|
|
}
|
|
else
|
|
{
|
|
currentBias = 0.0;
|
|
}
|
|
|
|
Print("Market Bias: ", DoubleToString(currentBias, 3));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update Trading Permissions |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateTradingPermissions()
|
|
{
|
|
allowBuy = (currentBias > BiasThreshold);
|
|
allowSell = (currentBias < -BiasThreshold);
|
|
|
|
Print("Trading Permissions - Buy: ", allowBuy, ", Sell: ", allowSell);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check Trading Signals |
|
|
//+------------------------------------------------------------------+
|
|
void CheckTradingSignals()
|
|
{
|
|
// Check for existing positions
|
|
if(PositionsTotal() > 0)
|
|
{
|
|
// Manage existing positions
|
|
ManagePositions();
|
|
return;
|
|
}
|
|
|
|
// Check for new signals
|
|
if(allowBuy)
|
|
{
|
|
// Buy signal logic
|
|
double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double sl = askPrice - StopLoss * _Point;
|
|
double tp = askPrice + TakeProfit * _Point;
|
|
|
|
// Optional: Add confirmation from recent swing break
|
|
if(CheckRecentSwingBreak(true))
|
|
{
|
|
ExecuteBuyOrder(askPrice, sl, tp);
|
|
}
|
|
}
|
|
else if(allowSell)
|
|
{
|
|
// Sell signal logic
|
|
double bidPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double sl = bidPrice + StopLoss * _Point;
|
|
double tp = bidPrice - TakeProfit * _Point;
|
|
|
|
// Optional: Add confirmation from recent swing break
|
|
if(CheckRecentSwingBreak(false))
|
|
{
|
|
ExecuteSellOrder(bidPrice, sl, tp);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check Recent Swing Break |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckRecentSwingBreak(bool forBuy)
|
|
{
|
|
if(nodeCount < 2)
|
|
return true; // No recent swings, allow trade
|
|
|
|
// Find the most recent swing
|
|
int recentSwingIndex = -1;
|
|
for(int i = nodeCount - 1; i >= 0; i--)
|
|
{
|
|
if(nodes[i].index < 50) // Within recent 50 bars
|
|
{
|
|
recentSwingIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(recentSwingIndex < 0)
|
|
return true;
|
|
|
|
double currentPrice = iClose(_Symbol, _Period, 0);
|
|
|
|
if(forBuy)
|
|
{
|
|
// For buy: check if price has broken above a recent swing high
|
|
if(nodes[recentSwingIndex].isHigh)
|
|
{
|
|
return (currentPrice > nodes[recentSwingIndex].price);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For sell: check if price has broken below a recent swing low
|
|
if(!nodes[recentSwingIndex].isHigh)
|
|
{
|
|
return (currentPrice < nodes[recentSwingIndex].price);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Execute Buy Order using CTrade |
|
|
//+------------------------------------------------------------------+
|
|
void ExecuteBuyOrder(double price, double sl, double tp)
|
|
{
|
|
// Check if we already have a buy position
|
|
if(trade.Buy(LotSize, _Symbol, price, sl, tp, "BFS Buy Signal"))
|
|
{
|
|
Print("Buy order executed successfully");
|
|
}
|
|
else
|
|
{
|
|
Print("Buy order failed. Error: ", GetLastError(), " - ", trade.ResultRetcodeDescription());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Execute Sell Order using CTrade |
|
|
//+------------------------------------------------------------------+
|
|
void ExecuteSellOrder(double price, double sl, double tp)
|
|
{
|
|
// Check if we already have a sell position
|
|
if(trade.Sell(LotSize, _Symbol, price, sl, tp, "BFS Sell Signal"))
|
|
{
|
|
Print("Sell order executed successfully");
|
|
}
|
|
else
|
|
{
|
|
Print("Sell order failed. Error: ", GetLastError(), " - ", trade.ResultRetcodeDescription());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage Positions using CTrade |
|
|
//+------------------------------------------------------------------+
|
|
void ManagePositions()
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket))
|
|
{
|
|
string symbol = PositionGetString(POSITION_SYMBOL);
|
|
long magic = PositionGetInteger(POSITION_MAGIC);
|
|
|
|
if(symbol == _Symbol && magic == 12345)
|
|
{
|
|
// Check if we should close based on bias change
|
|
if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && currentBias < -BiasThreshold) ||
|
|
(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && currentBias > BiasThreshold))
|
|
{
|
|
// Close position due to bias reversal
|
|
if(trade.PositionClose(ticket))
|
|
{
|
|
Print("Position closed due to bias reversal. Ticket: ", ticket);
|
|
}
|
|
else
|
|
{
|
|
Print("Failed to close position. Error: ", GetLastError(), " - ", trade.ResultRetcodeDescription());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update Visualization |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateVisualization()
|
|
{
|
|
RemoveVisualization();
|
|
|
|
// Draw swing nodes
|
|
for(int i = 0; i < nodeCount; i++)
|
|
{
|
|
if(nodes[i].level >= 0 && nodes[i].index < 200) // Only show recent 200 bars
|
|
{
|
|
string objName = "SwingNode_" + IntegerToString(i);
|
|
|
|
// Create object based on swing type
|
|
if(nodes[i].isHigh)
|
|
{
|
|
// Draw swing high as downward arrow
|
|
ObjectCreate(0, objName, OBJ_ARROW_DOWN, 0, nodes[i].time, nodes[i].price);
|
|
ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
|
|
}
|
|
else
|
|
{
|
|
// Draw swing low as upward arrow
|
|
ObjectCreate(0, objName, OBJ_ARROW_UP, 0, nodes[i].time, nodes[i].price);
|
|
ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
|
|
}
|
|
|
|
// Set color based on bullish/bearish classification
|
|
if(nodes[i].bullish)
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, BullishColor);
|
|
else
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, BearishColor);
|
|
|
|
// Set size
|
|
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
|
|
|
|
// Add level annotation
|
|
string labelName = "Label_" + IntegerToString(i);
|
|
ObjectCreate(0, labelName, OBJ_TEXT, 0, nodes[i].time, nodes[i].price);
|
|
ObjectSetString(0, labelName, OBJPROP_TEXT, "L" + IntegerToString(nodes[i].level));
|
|
ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
|
|
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
|
|
ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_UPPER);
|
|
|
|
// Adjust label position based on swing type
|
|
if(nodes[i].isHigh)
|
|
ObjectSetDouble(0, labelName, OBJPROP_PRICE, nodes[i].price + 10 * _Point);
|
|
else
|
|
ObjectSetDouble(0, labelName, OBJPROP_PRICE, nodes[i].price - 10 * _Point);
|
|
}
|
|
}
|
|
|
|
// Draw edges between nodes
|
|
for(int e = 0; e < edgeCount; e++)
|
|
{
|
|
int fromIdx = edges[e].from;
|
|
int toIdx = edges[e].to;
|
|
|
|
// Only draw edges for visible nodes
|
|
if(fromIdx < nodeCount && toIdx < nodeCount &&
|
|
nodes[fromIdx].index < 200 && nodes[toIdx].index < 200)
|
|
{
|
|
string edgeName = "Edge_" + IntegerToString(e);
|
|
ObjectCreate(0, edgeName, OBJ_TREND, 0, nodes[fromIdx].time, nodes[fromIdx].price,
|
|
nodes[toIdx].time, nodes[toIdx].price);
|
|
ObjectSetInteger(0, edgeName, OBJPROP_COLOR, clrGray);
|
|
ObjectSetInteger(0, edgeName, OBJPROP_WIDTH, 1);
|
|
ObjectSetInteger(0, edgeName, OBJPROP_STYLE, STYLE_DASH);
|
|
}
|
|
}
|
|
|
|
// Draw bias indicator
|
|
string biasObjName = "BiasIndicator";
|
|
ObjectCreate(0, biasObjName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, biasObjName, OBJPROP_XDISTANCE, 10);
|
|
ObjectSetInteger(0, biasObjName, OBJPROP_YDISTANCE, 10);
|
|
ObjectSetInteger(0, biasObjName, OBJPROP_XSIZE, 150);
|
|
ObjectSetInteger(0, biasObjName, OBJPROP_YSIZE, 60);
|
|
ObjectSetInteger(0, biasObjName, OBJPROP_BGCOLOR, clrBlack);
|
|
ObjectSetInteger(0, biasObjName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
|
ObjectSetInteger(0, biasObjName, OBJPROP_BORDER_COLOR, clrGray);
|
|
|
|
// Bias value
|
|
string biasValueName = "BiasValue";
|
|
ObjectCreate(0, biasValueName, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, biasValueName, OBJPROP_XDISTANCE, 20);
|
|
ObjectSetInteger(0, biasValueName, OBJPROP_YDISTANCE, 20);
|
|
ObjectSetString(0, biasValueName, OBJPROP_TEXT, "Bias: " + DoubleToString(currentBias, 3));
|
|
ObjectSetInteger(0, biasValueName, OBJPROP_COLOR, clrWhite);
|
|
ObjectSetInteger(0, biasValueName, OBJPROP_FONTSIZE, 12);
|
|
|
|
// Trading status
|
|
string statusName = "TradingStatus";
|
|
ObjectCreate(0, statusName, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, statusName, OBJPROP_XDISTANCE, 20);
|
|
ObjectSetInteger(0, statusName, OBJPROP_YDISTANCE, 40);
|
|
|
|
string statusText = "Status: ";
|
|
if(allowBuy)
|
|
statusText += "BUY";
|
|
else if(allowSell)
|
|
statusText += "SELL";
|
|
else
|
|
statusText += "NEUTRAL";
|
|
|
|
ObjectSetString(0, statusName, OBJPROP_TEXT, statusText);
|
|
ObjectSetInteger(0, statusName, OBJPROP_COLOR, clrYellow);
|
|
ObjectSetInteger(0, statusName, OBJPROP_FONTSIZE, 10);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Remove Visualization |
|
|
//+------------------------------------------------------------------+
|
|
void RemoveVisualization()
|
|
{
|
|
// Remove all objects created by this EA
|
|
int total = ObjectsTotal(0);
|
|
for(int i = total - 1; i >= 0; i--)
|
|
{
|
|
string name = ObjectName(0, i);
|
|
if(StringFind(name, "SwingNode_") == 0 ||
|
|
StringFind(name, "Label_") == 0 ||
|
|
StringFind(name, "Edge_") == 0 ||
|
|
StringFind(name, "Bias") == 0 ||
|
|
StringFind(name, "TradingStatus") == 0)
|
|
{
|
|
ObjectDelete(0, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for New Bar |
|
|
//+------------------------------------------------------------------+
|
|
bool IsNewBar()
|
|
{
|
|
static datetime lastBarTime = 0;
|
|
datetime currentBarTime = iTime(_Symbol, _Period, 0);
|
|
|
|
if(lastBarTime != currentBarTime)
|
|
{
|
|
lastBarTime = currentBarTime;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get Time String |
|
|
//+------------------------------------------------------------------+
|
|
string GetTimeString(datetime t)
|
|
{
|
|
MqlDateTime dt;
|
|
TimeToStruct(t, dt);
|
|
return StringFormat("%04d.%02d.%02d %02d:%02d", dt.year, dt.mon, dt.day, dt.hour, dt.min);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Print Node Information |
|
|
//+------------------------------------------------------------------+
|
|
void PrintNodeInfo(SwingNode &node)
|
|
{
|
|
string type = node.isHigh ? "High" : "Low";
|
|
string direction = node.bullish ? "Bullish" : "Bearish";
|
|
Print(StringFormat("Node: Index=%d, Time=%s, Price=%.5f, Type=%s, Level=%d, Direction=%s",
|
|
node.index, GetTimeString(node.time), node.price, type, node.level, direction));
|
|
}
|