Files
zhutoutoutousan 605faf5310 Prepare source-only public release for develop.
Add cluster audit pipeline, united EA updates, brochure generators, and publication hygiene (gitignore, MT5 path desensitization, pre-upload scan). Remove tracked reports, models, and binary artifacts from the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 15:03:43 +02:00

329 lines
11 KiB
Plaintext

//+------------------------------------------------------------------+
//| DarvasBoxStrategy.mqh |
//+------------------------------------------------------------------+
// MQL5: no #if — use #ifdef only (no defined() / || in one #if)
#ifdef UNITED_V2_DYNAMIC_LOTS
extern double g_DB_LotSize;
#define DARVAS_TRADE_LOT (g_DB_LotSize)
#else
#ifdef CLUSTER0_ORCHESTRATOR
extern double g_DB_LotSize;
#define DARVAS_TRADE_LOT (g_DB_LotSize)
#else
#define DARVAS_TRADE_LOT 0.01
#endif
#endif
bool InitDarvasBox(string symbol)
{
dbData.symbol = symbol;
dbData.boxHigh = 0;
dbData.boxLow = 0;
dbData.boxFormed = false;
dbData.lastBoxTime = 0;
dbData.boxName = "DarvasBox_" + IntegerToString(DB_MagicNumber) + "_";
// Check if symbol exists
if(!SymbolSelect(symbol, true))
{
Print("DarvasBox: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
return false;
}
Sleep(100); // Wait for symbol to be ready
dbData.point = SymbolInfoDouble(symbol, SYMBOL_POINT);
dbData.minStopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * dbData.point;
dbData.maHandle = iMA(symbol, DB_TrendTimeframe, DB_MA_Period, 0, DB_MA_Method, DB_MA_Price);
// Same TF as box highs/lows (H1 loop in CalculateDarvasBox). PERIOD_CURRENT breaks when United EA
// runs on a chart timeframe other than H1 (volume/breakout no longer match the box).
dbData.volumeHandle = iVolumes(symbol, PERIOD_H1, VOLUME_TICK);
if(dbData.maHandle == INVALID_HANDLE || dbData.volumeHandle == INVALID_HANDLE)
{
Print("DarvasBox: Error creating indicators for '", symbol, "'");
return false;
}
dbData.trade.SetDeviationInPoints(10);
dbData.trade.SetTypeFilling(ORDER_FILLING_IOC);
dbData.trade.SetAsyncMode(false);
dbData.trade.SetExpertMagicNumber(DB_MagicNumber);
ObjectsDeleteAll(0, dbData.boxName);
dbData.isInitialized = true;
Print("DarvasBox: Successfully initialized for symbol '", symbol, "'");
return true;
}
void DeinitDarvasBox()
{
if(dbData.maHandle != INVALID_HANDLE) IndicatorRelease(dbData.maHandle);
if(dbData.volumeHandle != INVALID_HANDLE) IndicatorRelease(dbData.volumeHandle);
ObjectsDeleteAll(0, dbData.boxName);
}
void DrawDarvasBox()
{
if(!dbData.boxFormed) return;
datetime time1 = iTime(dbData.symbol, PERIOD_H1, DB_BoxPeriod);
datetime time2 = iTime(dbData.symbol, PERIOD_H1, 0);
ObjectsDeleteAll(0, dbData.boxName);
ObjectCreate(0, dbData.boxName + "Top", OBJ_TREND, 0, time1, dbData.boxHigh, time2, dbData.boxHigh);
ObjectCreate(0, dbData.boxName + "Bottom", OBJ_TREND, 0, time1, dbData.boxLow, time2, dbData.boxLow);
ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_COLOR, DB_BoxColor);
ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_COLOR, DB_BoxColor);
ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_WIDTH, DB_BoxWidth);
ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_WIDTH, DB_BoxWidth);
ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_RAY_RIGHT, true);
ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_RAY_RIGHT, true);
}
void CalculateDarvasBox()
{
double high = 0;
double low = DBL_MAX;
// Find highest high and lowest low in the period - EXACTLY like original
for(int i = 0; i < DB_BoxPeriod; i++)
{
high = MathMax(high, iHigh(dbData.symbol, PERIOD_H1, i));
low = MathMin(low, iLow(dbData.symbol, PERIOD_H1, i));
}
double range = high - low;
double allowedRange = DB_BoxDeviation * dbData.point; // Use dbData.point instead of _Point
if(DB_EnableLogging)
{
Print("DarvasBox: Box Calculation - High: ", high, " Low: ", low, " Range: ", range, " Allowed Range: ", allowedRange);
}
// Check if box is formed - EXACTLY like original
if(range <= allowedRange)
{
dbData.boxHigh = high;
dbData.boxLow = low;
dbData.boxFormed = true;
dbData.lastBoxTime = iTime(dbData.symbol, PERIOD_CURRENT, 0);
// Draw the box
DrawDarvasBox();
if(DB_EnableLogging)
Print("DarvasBox: Box Formed - High: ", dbData.boxHigh, " Low: ", dbData.boxLow, " Time: ", dbData.lastBoxTime);
}
else
{
dbData.boxFormed = false;
// Delete box if it exists
ObjectsDeleteAll(0, dbData.boxName);
}
}
bool ValidateStopLevels(double price, double &sl, double &tp, ENUM_ORDER_TYPE orderType)
{
double minSlDistance = MathMax(dbData.minStopLevel, DB_StopLoss * dbData.point);
double minTpDistance = MathMax(dbData.minStopLevel, DB_TakeProfit * dbData.point);
if(orderType == ORDER_TYPE_BUY)
{
sl = price - minSlDistance;
tp = price + minTpDistance;
}
else
{
sl = price + minSlDistance;
tp = price - minTpDistance;
}
return true;
}
bool IsTrendFavorable(ENUM_ORDER_TYPE orderType)
{
if(!DB_UseTrendFilter)
return true;
double ma[];
ArraySetAsSeries(ma, true);
if(CopyBuffer(dbData.maHandle, 0, 0, 2, ma) <= 0)
return false;
double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK);
double trendStrength = MathAbs(currentPrice - ma[0]) / dbData.point;
if(orderType == ORDER_TYPE_BUY)
return (currentPrice > ma[0] && trendStrength > DB_TrendThreshold);
else
return (currentPrice < ma[0] && trendStrength > DB_TrendThreshold);
}
bool CheckVolumeConditions()
{
if(!DB_UseVolumeSpikeFilter)
return true;
double volumes[];
ArraySetAsSeries(volumes, true);
if(CopyBuffer(dbData.volumeHandle, 0, 0, DB_VolumeMA_Period + 1, volumes) <= 0)
return false;
double volumeMA = 0;
for(int i = 1; i <= DB_VolumeMA_Period; i++)
volumeMA += volumes[i];
volumeMA /= DB_VolumeMA_Period;
double currentVolume = volumes[0];
if(volumeMA <= 0.0)
return (currentVolume > 0.0);
double volumeRatio = currentVolume / volumeMA;
return (volumeRatio > DB_VolumeThresholdMultiplier);
}
bool PlaceOrder(ENUM_ORDER_TYPE orderType, double price, double sl, double tp)
{
if(!ValidateStopLevels(price, sl, tp, orderType))
{
if(DB_EnableLogging)
Print("DarvasBox: Order rejected - Stop levels validation failed");
return false;
}
if(!IsTrendFavorable(orderType))
{
if(DB_EnableLogging)
Print("DarvasBox: Order rejected - Trend not favorable for ", EnumToString(orderType));
return false;
}
if(!CheckVolumeConditions())
{
if(DB_EnableLogging)
Print("DarvasBox: Order rejected - Volume conditions not met");
return false;
}
bool result = false;
const double lot = United_NormalizeVolume(dbData.symbol, DARVAS_TRADE_LOT);
if(lot <= 0.0)
{
Print("DarvasBox: Order rejected - invalid lot after normalize (raw=", DARVAS_TRADE_LOT, ")");
return false;
}
// Use market price (0) instead of explicit price - this ensures market order execution
// In backtesting, explicit price might fail if price has moved
if(orderType == ORDER_TYPE_BUY)
result = dbData.trade.Buy(lot, dbData.symbol, 0, sl, tp, "Darvas Box Breakout");
else
result = dbData.trade.Sell(lot, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown");
// Always log errors, success only if logging enabled
if(result)
{
if(DB_EnableLogging)
Print("DarvasBox: ", (orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"), " Order Placed Successfully");
}
else
{
// Always log failures with detailed info
uint retcode_uint = dbData.trade.ResultRetcode();
int retcode = (int)retcode_uint;
string desc = dbData.trade.ResultRetcodeDescription();
ulong deal = dbData.trade.ResultDeal();
ulong order = dbData.trade.ResultOrder();
Print("DarvasBox: ", (orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"),
" Order Failed - Retcode: ", retcode,
", Description: ", desc,
", Deal: ", deal,
", Order: ", order,
", Symbol: ", dbData.symbol,
", Requested Price: ", price,
", SL: ", sl,
", TP: ", tp);
}
return result;
}
void ProcessDarvasBox(string symbol)
{
// Skip if not initialized (symbol not available)
if(!dbData.isInitialized)
return;
dbData.symbol = symbol; // Update symbol in case it changed
// Calculate new box levels - EXACTLY like original (called every tick)
CalculateDarvasBox();
// Check for trading signals - EXACTLY like original (checked every tick)
if(dbData.boxFormed)
{
double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK);
long currentVolume_long = iVolume(dbData.symbol, PERIOD_H1, 0);
double currentVolume = (double)currentVolume_long;
if(DB_EnableLogging)
{
Print("DarvasBox: Current Price: ", currentPrice, " Box High: ", dbData.boxHigh, " Box Low: ", dbData.boxLow);
Print("DarvasBox: Current Volume: ", currentVolume, " Volume Threshold: ", DB_VolumeThreshold);
}
// Check for breakout above box - EXACTLY like original
if(currentPrice > dbData.boxHigh && currentVolume > DB_VolumeThreshold)
{
if(DB_EnableLogging)
Print("DarvasBox: Breakout Signal Detected - Price above box high");
// Buy signal
if(United_PrepareEntrySlot(dbData.trade, dbData.symbol, (ulong)DB_MagicNumber, DB_CloseUnprofitableOnNewSignal))
{
double sl = currentPrice - DB_StopLoss * dbData.point;
double tp = currentPrice + DB_TakeProfit * dbData.point;
if(DB_EnableLogging)
Print("DarvasBox: Preparing Buy Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp);
PlaceOrder(ORDER_TYPE_BUY, currentPrice, sl, tp);
}
else if(DB_EnableLogging)
Print("DarvasBox: Skipping Buy Signal - Position already exists");
}
// Check for breakdown below box - EXACTLY like original
if(currentPrice < dbData.boxLow && currentVolume > DB_VolumeThreshold)
{
if(DB_EnableLogging)
Print("DarvasBox: Breakdown Signal Detected - Price below box low");
// Sell signal
if(United_PrepareEntrySlot(dbData.trade, dbData.symbol, (ulong)DB_MagicNumber, DB_CloseUnprofitableOnNewSignal))
{
double sl = currentPrice + DB_StopLoss * dbData.point;
double tp = currentPrice - DB_TakeProfit * dbData.point;
if(DB_EnableLogging)
Print("DarvasBox: Preparing Sell Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp);
PlaceOrder(ORDER_TYPE_SELL, currentPrice, sl, tp);
}
else if(DB_EnableLogging)
Print("DarvasBox: Skipping Sell Signal - Position already exists");
}
}
else if(DB_EnableLogging)
Print("DarvasBox: No Box Formed - Waiting for consolidation");
}
//+------------------------------------------------------------------+