//+------------------------------------------------------------------+ //| EURUSD_Cause&EffectBased_Donchian_VolTarget.mq5 | //| Daily trend-following with volatility targeting & drawdown control| //| Entry: Donchian breakout (80); Exit: Donchian (20) | //| Risk: ATR stop, target vol ~8%, max 0.5% risk/trade | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, Forex EA Developer" #property version "2.20" #property description "EURUSD Donchian Trend Following with Volatility Targeting" #property description "Includes drawdown-based position sizing and trading pause" #property strict #property script_show_inputs #property indicator_separate_window #define MODE_HIGH 0 #define MODE_LOW 1 #define ERR_SUCCESS 0 #define ERR_INVALID_PARAMETER 1 #define REASON_PROGRAM = 0 #define REASON_REMOVE = 1 #define REASON_RECOMPILE = 2 #define REASON_CHARTCHANGE = 3 #define REASON_CHARTCLOSE = 4 #define REASON_PARAMETERS = 5 #define REASON_ACCOUNT = 6 #define REASON_TEMPLATE = 7 #define REASON_INITFAILED = 8 #define REASON_CLOSE = 9 #define J_ARRAY 1 #define J_OBJECT 2 #define J_STRING 3 #define J_NUMBER 4 #define J_DOUBLE 5 #define DEAL_TYPE_BUY 0 #define DEAL_TYPE_SELL 1 #define DEAL_TYPE_BALANCE 2 #define POSITION_TYPE_BUY 0 #define POSITION_TYPE_SELL 1 #define SYMBOL_POINT 1 #define SYMBOL_DIGITS 2 #define SYMBOL_TRADE_TICK_VALUE 3 #define SYMBOL_TRADE_TICK_SIZE 4 #define SYMBOL_TRADE_TICK_VALUE_LOT 5 #define SYMBOL_VOLUME_MIN 6 #define SYMBOL_VOLUME_MAX 7 #define SYMBOL_VOLUME_STEP 8 #define ACCOUNT_BALANCE 9 #define ACCOUNT_EQUITY 10 #define CORNER_RIGHT_LOWER 3 #define OBJ_LABEL 1 #define OBJPROP_CORNER 1 #define OBJPROP_XDISTANCE 2 #define OBJPROP_YDISTANCE 3 #define OBJPROP_COLOR 4 #define OBJPROP_TEXT 5 #define OBJPROP_FONTSIZE 6 #define FILE_READ 1 #define FILE_WRITE 2 #define FILE_TXT 3 #define FILE_ANSI 4 #define SEEK_END 2 #include #include #include #include #include #include //--- Input parameters input group "Strategy Settings" input string InpSymbol = "EURUSD"; input ENUM_TIMEFRAMES InpTF = PERIOD_D1; input int InpDonchianEntry = 80; // Breakout lookback input int InpDonchianExit = 20; // Exit channel input ulong InpMagicNumber = 20250824; // Customizable magic number input int InpMinTradeQualityScore = 60; // Minimum quality score for trades input group "Risk Management" input int InpATRPeriod = 20; input double InpATRStopMult = 3.0; // Stop = 3 * ATR input double InpAnnualVolTarget = 0.08; // 8% target vol input double InpMaxRiskPerTrade = 0.005; // 0.5% of equity cap input double InpReduceAtDD = 0.08; // Reduce size when DD >= 8% input double InpPauseAtDD = 0.10; // Pause new entries at DD >= 10% input double InpResumeDD = 0.05; // Resume when DD <= 5% input bool InpUseEquityInsteadOfBalance = false; // Use equity instead of balance for calculations input group "Risk Management" input double InpTrailStartATR = 1.0; input group "Trading Settings" input int InpMaxTradesPerDay = 1; // Maximum trades per day input string InpTradeStartTime = "00:00";// Start trading time (Broker time) input string InpTradeEndTime = "23:59";// End trading time (Broker time) input bool InpTradeOnMonday = true; // Trade on Monday input bool InpTradeOnTuesday = true; // Trade on Tuesday input bool InpTradeOnWednesday = true; // Trade on Wednesday input bool InpTradeOnThursday = true; // Trade on Thursday input bool InpTradeOnFriday = true; // Trade on Friday input group "EA Settings" input bool InpUseTrailingStop = true; input double InpTrailBuffer = 10.0; // Points buffer for trailing stop input bool InpEnableAlerts = true; input bool InpEnableLogging = true; // Enable detailed logging input int InpSlippage = 3; // Slippage in points input bool InpEnableTradeRetry = true; // Enable trade retry on errors // News filter input parameters input group "News Filter Settings" input bool InpEnableNewsFilter = true; input string InpNewsApiKey = "YOUR_API_KEY"; input string InpNewsApiUrl = "https://api.example.com/economic-calendar"; input int InpNewsLookaheadHours = 24; input int InpNewsCheckInterval = 30; input int InpPreNewsQuietPeriod = 60; input int InpPostNewsQuietPeriod = 30; input double InpNewsImpactPositionSize = 0.5; input string InpNewsCurrencyFilter = ""; input bool InpNewsAlertOnHighImpact = true; input bool InpNewsPauseTradingOnExtreme = true; input int InpNewsMinImpactScore = 2; input bool InpNewsIncludeWeekends = false; input group "News Impact Levels" input bool InpNewsReactToLowImpact = false; input bool InpNewsReactToMediumImpact = true; input bool InpNewsReactToHighImpact = true; input bool InpNewsReactToExtremeImpact = true; input group "News Trading Rules" input double InpNewsVolatilityMultiplier = 1.5; input int InpNewsMaxSpreadPips = 10; input bool InpNewsCloseBeforeEvent = false; input int InpNewsCloseBeforeMinutes = 30; input bool InpNewsAvoidNewEntries = true; input int InpNewsAvoidEntriesMinutes = 60; input group "News Session Filtering" input bool InpNewsFilterBySession = true; input bool InpNewsAsianSession = false; input bool InpNewsLondonSession = true; input bool InpNewsNewYorkSession = true; // Global variables datetime last_bar_time; double equity_high; CTrade trade; int atr_handle; int daily_trade_count; datetime last_trade_date; bool trading_paused = false; double last_atr_value = 0.0; string log_file_name; double account_high; datetime last_high_update; int last_indicator_error; string last_indicator_error_desc; double current_stop_multiplier; double volatility_position_factor = 1.0; string last_news_error; int last_news_error_code; datetime news_last_successful_check; double total_today_profit = 0.0; int total_today_trades = 0; double max_position_risk = 0.0; // News filtering variables double news_position_size_factor = 1.0; string high_impact_events[]; int news_quiet_period_until = 0; datetime last_news_check = 0; int news_impact_levels[4]; double portfolio_risk_exposure = 0.0; double current_correlation_factor = 1.0; double session_volatility_factor = 1.0; double peak_equity = 0.0; double max_drawdown_value = 0.0; datetime last_drawdown_calculation = 0; string last_news_response; string economic_calendar_events[]; int last_news_update_hour = -1; // Session flags bool asian_session = false; bool london_session = false; bool new_york_session = false; //+------------------------------------------------------------------+ //| Function Prototypes | //+------------------------------------------------------------------+ void InitializeNewsFiltering(); void CheckForNewsEvents(); void ProcessNewsResponse(string response); string VillahermosaNewsImpactAnalysis(); double GetNewsAdjustedPositionSize(string &cause_effect); bool ShouldPauseTradingForNews(string &cause_effect); void UpdateNewsBasedTradingRules(); void UpdateVolatilityBasedTradingRules(); void LogSystemStatus(); void InitializeDrawdownTracking(); void UpdateDrawdownMetrics(string &analysis); void UpdateDrawdownMetrics(); void ManageExistingPositions(); string VillahermosaDeinitAnalysis(const int reason); string GetDeinitReasonText(const int reason); void LogFinalAccountStatus(); void ObjectsDeleteAll(const long chart_id, const string prefix); void OnTimer(); void OnTrade(); string VillahermosaPostTradeAnalysis(ulong deal_ticket); string GetTradingSessionContext(); string CategorizeNewsImpact(string event_name); int GetNewsImpactScore(string event_name); void UpdateNewsTradingRules(); bool IsExtremeImpactEvent(string event_name); void UpdateStopLossMultiplier(double new_multiplier); string GetDayOfWeekName(int day_of_week); void UpdateChartNewsInfo(); double CalculateMarketVolatility(string &cause_effect); double CalculateMarketVolatility(); string AnalyzeVolatilityTrendContext(double &atr_values[]); string GetVolatilitySessionContext(); string VillahermosaVolatilityImpactAnalysis(double volatility_ratio); string ExtractJsonValue(string json, string key) double CalculateVolatilityPositionFactor(double volatility_ratio); void UpdateChartVolatilityInfo(double ratio, string analysis); double MinLot(); double NormalizeLots(double lots); bool IsTradeTime(); bool IsTradingDay(int day_of_week); void WriteLog(string message); void UpdateAccountHigh(); double CalculateCurrentDrawdown(string &analysis = ""); double CalculateCurrentDrawdown() { string dummy; return CalculateCurrentDrawdown(dummy); } double CalculateAbsoluteDrawdown(); double CalculateMaximumAdverseExcursion(); double CalculateRecoveryFactor(); string VillahermosaDrawdownAnalysis(double current_drawdown); void UpdateChartDrawdownInfo(double drawdown); double CalculateVillahermosaVolAdjustment(ENUM_ORDER_TYPE type, double atr); double CalculateCauseBasedPositionSize(ENUM_ORDER_TYPE type); double CalculateVillahermosaRiskAdjustment(ENUM_ORDER_TYPE type, string trade_cause, double atr); double CalculateMarketVolatility(string &cause_effect = ""); double CalculateMarketVolatility() { string dummy; return CalculateMarketVolatility(dummy); } double CalculateVolatilityRiskAdjustment(double atr); double CalculateTimeBasedRiskAdjustment(); double CalculateConcentrationRiskAdjustment(); double CalculateVillahermosaRiskFactor(ENUM_ORDER_TYPE type, string trade_cause, double atr); double VolTargetLots(double equity, double atr, ENUM_ORDER_TYPE type = WRONG_VALUE); double RiskCapLots(double equity, double atr, double stopMult, ENUM_ORDER_TYPE type = WRONG_VALUE, string trade_cause = ""); string VillahermosaEntryCauseAnalysis(datetime open_time, double open_price, ENUM_POSITION_TYPE type); string VillahermosaEffectAnalysis(ulong ticket, ENUM_POSITION_TYPE type, double open_price, double current_price, double profit, long position_age); int CalculateVillahermosaScore(string entry_cause, string effect_analysis, double profit, long position_age); bool IsTrendReversed(ENUM_POSITION_TYPE type, datetime open_time); string VillahermosaTradeCauseAnalysis(ENUM_ORDER_TYPE type, double price); string VillahermosaProjectedEffectAnalysis(ENUM_ORDER_TYPE type, double entry_price, double atr); int CalculateTradeQualityScore(string cause, string projected_effect); string VillahermosaClosureCauseAnalysis(ulong ticket, ENUM_POSITION_TYPE type, double open_price, double current_price, double profit, long position_age, string closure_reason); string VillahermosaPositionHealthCheck(ulong ticket, ENUM_POSITION_TYPE type, double open_price, double current_price, double profit, long position_age); double ArrayMean(double &arr[]); bool IsTrendAligned(ENUM_POSITION_TYPE type); int PositionDirection(string &reason = ""); string AnalyzeMarketContext(); string AnalyzeTimeContext(); void AnalyzePositionPerformance() double Donchian(string sym, ENUM_TIMEFRAMES tf, int lookback, int mode, int shift) void CloseAllPositions(string closure_reason = "Manual Closure"); double CalculatePositionRisk(ulong ticket, ENUM_POSITION_TYPE type, double volume, double open_price, double current_price); double CalculatePortfolioExposure(); string AnalyzeClosureEffect(double profit, double risk_released, long position_age); string AnalyzeRiskImpact(double exposure_reduction, double total_profit); bool OpenPosition(ENUM_ORDER_TYPE type, double lots, double atr, string trade_cause_param); double SafeSymbolInfoDouble(string symbol, int prop_id, double default_value = 0.0); long SafeSymbolInfoInteger(string symbol, int prop_id, long default_value = 0); void UpdateChartInfo(double dd, double upper, double lower, double exitU, double exitL, double atr); void TrailATR(int dir, double atr); string AnalyzeTrailingContext(double atr); string VillahermosaTrailingAnalysis(ulong ticket, ENUM_POSITION_TYPE type, double open_price, double current_price, double profit, long position_age, double atr); //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { account_high = 0; last_high_update = 0; trading_paused = false; if(InpDonchianEntry <= 0 || InpDonchianExit <= 0 || InpATRPeriod <= 0) { Alert("Invalid input parameters"); return INIT_PARAMETERS_INCORRECT; } // Validate input parameters if(InpDonchianEntry <= 0 || InpDonchianExit <= 0 || InpATRPeriod <= 0) { Alert("Invalid input parameters"); return INIT_PARAMETERS_INCORRECT; } // Initialize last_bar_time to the current bar's time MqlRates rates[]; ArraySetAsSeries(rates, true); if(CopyRates(InpSymbol, InpTF, 0, 1, rates) > 0) { last_bar_time = rates[0].time; } else { last_bar_time = 0; WriteLog("Error initializing last_bar_time: " + IntegerToString(GetLastError())); } // Initialize current_stop_multiplier current_stop_multiplier = InpATRStopMult; // Initialize log file name first thing log_file_name = "EURUSD_Donchian_Log_" + IntegerToString(GetTickCount()) + ".txt"; InitializeDrawdownTracking(); // Validate symbol if(SymbolInfoString(InpSymbol, SYMBOL_CURRENCY_BASE) == "") { if(!SymbolSelect(InpSymbol, true)) { WriteLog("Invalid symbol: " + InpSymbol); Alert("Invalid symbol: ", InpSymbol); return(INIT_PARAMETERS_INCORRECT); } } // Create ATR handle with error handling atr_handle = iATR(InpSymbol, InpTF, InpATRPeriod); if(atr_handle == INVALID_HANDLE) { last_indicator_error = GetLastError(); last_indicator_error_desc = ErrorDescription(last_indicator_error); WriteLog("Failed to create ATR handle: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); Alert("Failed to create ATR handle: ", IntegerToString(last_indicator_error)); return INIT_FAILED; } //--- Initialize trade object trade.SetExpertMagicNumber(InpMagicNumber); trade.SetMarginMode(); trade.SetTypeFillingBySymbol(InpSymbol); trade.SetDeviationInPoints(InpSlippage); //--- Initialize equity high and daily trade count equity_high = AccountInfoDouble(InpUseEquityInsteadOfBalance ? ACCOUNT_EQUITY : ACCOUNT_BALANCE); last_bar_time = 0; daily_trade_count = 0; last_trade_date = 0; trading_paused = false; // Initialize news filtering InitializeNewsFiltering(); // Initialize last_news_check last_news_check = 0; // Initialize news filtering arrays ArrayResize(high_impact_events, 0); news_quiet_period_until = 0; news_position_size_factor = 1.0; last_news_check = 0; // Initialize impact levels news_impact_levels[0] = InpNewsReactToLowImpact; news_impact_levels[1] = InpNewsReactToMediumImpact; news_impact_levels[2] = InpNewsReactToHighImpact; news_impact_levels[3] = InpNewsReactToExtremeImpact; // Initialize news events array ArrayResize(high_impact_events, 0); // Initialize session filters asian_session = InpNewsAsianSession; london_session = InpNewsLondonSession; new_york_session = InpNewsNewYorkSession; //--- Create log file log_file_name = "EURUSD_Donchian_Log_" + IntegerToString(GetTickCount()) + ".txt"; WriteLog("EA initialized for " + InpSymbol + " on " + EnumToString(InpTF) + " timeframe"); WriteLog("Magic number: " + IntegerToString(InpMagicNumber)); WriteLog("Account balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2)); WriteLog("Account equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2)); //--- Set timer for periodic checks EventSetTimer(60); // Check every minute return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release indicator handles if(atr_handle != INVALID_HANDLE) { IndicatorRelease(atr_handle); WriteLog("ATR indicator handle released"); } //--- Remove all graphical objects created by the EA ObjectsDeleteAll(0, "Villahermosa_"); ObjectsDeleteAll(0, "NewsInfo"); ObjectsDeleteAll(0, "VolatilityInfo"); ObjectsDeleteAll(0, "DrawdownInfo"); WriteLog("All graphical objects removed"); //--- Close any open file handles if(FileIsOpen(log_file_name)) { FileClose(log_file_name); WriteLog("Log file closed: " + log_file_name); } //--- Perform final Villahermosa system analysis string deinit_analysis = VillahermosaDeinitAnalysis(reason); WriteLog(deinit_analysis); //--- Log final account status LogFinalAccountStatus(); // Clean up news events array ArrayFree(high_impact_events); WriteLog("News events array freed"); //--- Remove timer EventKillTimer(); WriteLog("Timer event killed"); //--- Final cleanup log WriteLog("EA Villahermosa successfully deinitialized. Reason: " + GetDeinitReasonText(reason)); //--- Clean up comment from chart Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Check symbol if(_Symbol != InpSymbol) return; //--- Reset daily trade count if it's a new day MqlDateTime today; TimeCurrent(today); datetime current_date = StructToTime(today); today.hour = 0; today.min = 0; today.sec = 0; datetime start_of_day = StructToTime(today); if(last_trade_date != start_of_day) { daily_trade_count = 0; last_trade_date = start_of_day; WriteLog("New trading day started. Date: " + TimeToString(start_of_day, TIME_DATE)); } //--- Run once per new bar MqlRates rates[]; ArraySetAsSeries(rates, true); if(SafeCopyRates(InpSymbol, InpTF, 0, 1, rates) < 1) { WriteLog("Error copying rates: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); return; } if(ArraySize(rates) < 1) { WriteLog("No rates data available"); return; } if(!ValidatePrice(rates[0].close, "rates close")) { return; } //--- Check if this is a new bar if(last_bar_time == rates[0].time) { // Same bar, skip processing but still manage existing positions ManageExistingPositions(); return; } //--- New bar detected, update last_bar_time last_bar_time = rates[0].time; //--- Update all metrics and analyses UpdateAccountHigh(); string drawdown_analysis; double dd = CalculateCurrentDrawdown(drawdown_analysis); //--- Get ATR value double atr_val[1]; if(SafeCopyBuffer(atr_handle, 0, 0, 1, atr_val) < 1) { WriteLog("Error copying ATR buffer: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); return; } if(!ValidateATRValue(atr_val[0], "ATR calculation")) { WriteLog("Invalid ATR value: " + DoubleToString(atr_val[0])); return; } double atr = atr_val[0]; last_atr_value = atr; //--- Update news and volatility analyses UpdateNewsBasedTradingRules(); UpdateVolatilityBasedTradingRules(); //--- Donchian channels double upper = Donchian(InpSymbol, InpTF, InpDonchianEntry, MODE_HIGH, 1); double lower = Donchian(InpSymbol, InpTF, InpDonchianEntry, MODE_LOW, 1); double exitU = Donchian(InpSymbol, InpTF, InpDonchianExit, MODE_HIGH, 1); double exitL = Donchian(InpSymbol, InpTF, InpDonchianExit, MODE_LOW, 1); if(upper == 0 || lower == 0 || exitU == 0 || exitL == 0) { WriteLog("Error calculating Donchian channels"); return; } double close1 = rates[0].close; //--- Manage open position with Villahermosa analysis string position_reason; int pos_dir = PositionDirection(position_reason); // Log Villahermosa position analysis WriteLog("Position Analysis: " + position_reason); if(pos_dir != 0) { //--- Get position details for Villahermosa analysis ulong ticket = PositionGetTicket(0); if(ticket > 0) { ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); datetime open_time = (datetime)PositionGetInteger(POSITION_TIME); double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); double profit = PositionGetDouble(POSITION_PROFIT); long position_age = TimeCurrent() - open_time; //--- Villahermosa position health check string health_status = VillahermosaPositionHealthCheck(ticket, type, open_price, current_price, profit, position_age); WriteLog("Position Health: " + health_status); //--- Villahermosa effect analysis string effect_analysis = VillahermosaEffectAnalysis(ticket, type, open_price, current_price, profit, position_age); WriteLog("Position Effect: " + effect_analysis); //--- Villahermosa closure analysis string closure_cause = VillahermosaClosureCauseAnalysis(ticket, type, open_price, current_price, profit, position_age, "Donchian Exit Signal"); //--- Exit rules with Villahermosa analysis if((type == POSITION_TYPE_BUY && close1 < exitL) || (type == POSITION_TYPE_SELL && close1 > exitU)) { WriteLog("Closing position: " + closure_cause); CloseAllPositions("Donchian Exit Signal"); } //--- Optional trailing ATR stop with Villahermosa else if(InpUseTrailingStop) { // Villahermosa trailing analysis string trail_analysis = VillahermosaTrailingAnalysis(ticket, type, open_price, current_price, profit, position_age, atr); WriteLog("Trailing Analysis: " + trail_analysis); TrailATR(pos_dir, atr); } } } //--- Risk gating based on drawdown bool allow_new = (dd < InpPauseAtDD); double size_factor = (dd >= InpReduceAtDD) ? 0.5 : 1.0; // Apply news adjustment to position size string news_cause_effect; double news_factor = GetNewsAdjustedPositionSize(news_cause_effect); size_factor *= news_factor; if(dd >= InpPauseAtDD && !trading_paused) { trading_paused = true; if(InpEnableAlerts && pos_dir == 0) { Alert("Trading paused due to drawdown: ", DoubleToString(dd*100, 1), "%"); } WriteLog("Trading paused due to drawdown: " + DoubleToString(dd*100, 1) + "%"); } else if(dd <= InpResumeDD && trading_paused) { trading_paused = false; if(InpEnableAlerts) { Alert("Trading resumed. Drawdown: ", DoubleToString(dd*100, 1), "%"); } WriteLog("Trading resumed. Drawdown: " + DoubleToString(dd*100, 1) + "%"); } else if(dd >= InpReduceAtDD && InpEnableAlerts && !trading_paused) { Alert("Position size reduced due to drawdown: ", DoubleToString(dd*100, 1), "%"); } //--- Check trading time window and day of week bool in_trading_time = IsTradeTime(); bool is_trading_day = IsTradingDay(today.day_of_week); //--- Check if trading should be paused due to news string news_pause_analysis; bool news_pause = ShouldPauseTradingForNews(news_pause_analysis); if(news_pause) { WriteLog("Trading paused due to news: " + news_pause_analysis); trading_paused = true; } //--- Entry rules with Villahermosa analysis (only if flat and allowed) if(pos_dir == 0 && allow_new && !trading_paused && in_trading_time && is_trading_day && daily_trade_count < InpMaxTradesPerDay) { //--- Determine the potential trade direction ENUM_ORDER_TYPE potential_type = WRONG_VALUE; double entry_price = 0; if(close1 > upper) { potential_type = ORDER_TYPE_BUY; entry_price = SymbolInfoDouble(InpSymbol, SYMBOL_ASK); } else if(close1 < lower) { potential_type = ORDER_TYPE_SELL; entry_price = SymbolInfoDouble(InpSymbol, SYMBOL_BID); } // Only proceed if we have a valid trade direction if(potential_type != WRONG_VALUE) { //--- Villahermosa cause analysis for this potential trade string trade_cause = VillahermosaTradeCauseAnalysis(potential_type, entry_price); //--- Villahermosa projected effect analysis string projected_effect = VillahermosaProjectedEffectAnalysis(potential_type, entry_price, atr); //--- Calculate trade quality score int quality_score = CalculateTradeQualityScore(trade_cause, projected_effect); //--- Only proceed if quality score meets threshold if(quality_score >= 60) { //--- Volatility-targeted position sizing with Villahermosa adjustments double account_value = AccountInfoDouble(InpUseEquityInsteadOfBalance ? ACCOUNT_EQUITY : ACCOUNT_BALANCE); double lots_vol = VolTargetLots(account_value, atr, potential_type); double lots_riskcap = RiskCapLots(account_value, atr, InpATRStopMult, potential_type, trade_cause); double lots = MathMin(lots_vol, lots_riskcap) * size_factor; lots = NormalizeLots(lots); if(lots >= MinLot()) { if(OpenPosition(potential_type, lots, atr, trade_cause)) { daily_trade_count++; WriteLog("Trade executed with Villahermosa score: " + IntegerToString(quality_score) + "/100 | Cause: " + trade_cause + " | Projected: " + projected_effect); } } else { WriteLog("Lot size too small: " + DoubleToString(lots) + " (min: " + DoubleToString(MinLot()) + ")"); } } else { WriteLog("Trade rejected due to low quality score: " + IntegerToString(quality_score) + " | Cause: " + trade_cause + " | Projected: " + projected_effect); } } } //--- Update chart with information UpdateChartInfo(dd, upper, lower, exitU, exitL, atr); } //+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { //--- This function is called when any trade operation occurs if(!InpEnableLogging) return; //--- Get the last trade transaction HistorySelect(TimeCurrent() - 60, TimeCurrent()); int total_deals = HistoryDealsTotal(); if(total_deals > 0) { ulong ticket = HistoryDealGetTicket(total_deals - 1); if(ticket > 0) { string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL); long magic = HistoryDealGetInteger(ticket, DEAL_MAGIC); if(symbol == InpSymbol && magic == InpMagicNumber) { ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE); double volume = HistoryDealGetDouble(ticket, DEAL_VOLUME); double price = HistoryDealGetDouble(ticket, DEAL_PRICE); double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT); string comment = HistoryDealGetString(ticket, DEAL_COMMENT); string trade_action = ""; switch(deal_type) { case DEAL_TYPE_BUY: trade_action = "BUY"; break; case DEAL_TYPE_SELL: trade_action = "SELL"; break; case DEAL_TYPE_BALANCE: trade_action = "BALANCE"; break; default: trade_action = "UNKNOWN"; break; } string trade_analysis = "Trade Executed | Action: " + trade_action + " | Volume: " + DoubleToString(volume, 2) + " | Price: " + DoubleToString(price, 5) + " | Profit: " + DoubleToString(profit, 2) + " | Comment: " + comment; WriteLog(trade_analysis); //--- Perform Villahermosa post-trade analysis if(deal_type == DEAL_TYPE_BUY || deal_type == DEAL_TYPE_SELL) { string post_trade_analysis = VillahermosaPostTradeAnalysis(ticket); WriteLog("Post-Trade Analysis: " + post_trade_analysis); } } } } } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- Update drawdown metrics periodically UpdateDrawdownMetrics(); //--- Additional periodic checks CheckForNewsEvents(); CheckMarketConditions(); //--- Update Villahermosa analyses UpdateNewsBasedTradingRules(); UpdateVolatilityBasedTradingRules(); //--- Update equity high-water mark periodically double account_value = AccountInfoDouble(InpUseEquityInsteadOfBalance ? ACCOUNT_EQUITY : ACCOUNT_BALANCE); if(account_value > equity_high) { equity_high = account_value; WriteLog("New equity high: " + DoubleToString(equity_high, 2)); } //--- Log system status LogSystemStatus(); } //+------------------------------------------------------------------+ //| Get current trading session | //+------------------------------------------------------------------+ string GetCurrentTradingSession() { MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; if(hour >= 0 && hour < 5) return "Asian"; if(hour >= 8 && hour < 16) return "London"; if(hour >= 13 && hour < 21) return "NewYork"; return "Other"; } //+------------------------------------------------------------------+ //| Check if trading is allowed in current session | //+------------------------------------------------------------------+ bool IsTradingAllowedInSession() { string session = GetCurrentTradingSession(); if(session == "Asian") return InpNewsAsianSession; if(session == "London") return InpNewsLondonSession; if(session == "NewYork") return InpNewsNewYorkSession; return true; // Default to true for "Other" sessions } //+------------------------------------------------------------------+ //| Get session-based position size multiplier | //+------------------------------------------------------------------+ double GetSessionPositionSizeMultiplier() { string session = GetCurrentTradingSession(); if(session == "Asian") return 0.8; // Reduce size during Asian session if(session == "London") return 1.0; // Full size during London session if(session == "NewYork") return 1.0; // Full size during NY session return 0.7; // Reduce size during other hours } //+------------------------------------------------------------------+ //| Get session-based volatility multiplier | //+------------------------------------------------------------------+ double GetSessionVolatilityMultiplier() { string session = GetCurrentTradingSession(); if(session == "Asian") return 0.7; // Lower volatility during Asian session if(session == "London") return 1.2; // Higher volatility during London session if(session == "NewYork") return 1.5; // Highest volatility during NY session return 1.0; // Normal volatility during other hours } //+------------------------------------------------------------------+ //| SafeCopyBuffer - Wrapper for CopyBuffer with error handling | //+------------------------------------------------------------------+ int SafeCopyBuffer(int indicator_handle, int buffer_num, int start_pos, int count, double &buffer[]) { int result = CopyBuffer(indicator_handle, buffer_num, start_pos, count, buffer); if(result < 0) { last_indicator_error = GetLastError(); last_indicator_error_desc = ErrorDescription(last_indicator_error); WriteLog("CopyBuffer error: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); } return result; } //+------------------------------------------------------------------+ //| SafeCopyRates - Wrapper for CopyRates with error handling | //+------------------------------------------------------------------+ int SafeCopyRates(string symbol, ENUM_TIMEFRAMES timeframe, int start_pos, int count, MqlRates &rates[]) { int result = CopyRates(symbol, timeframe, start_pos, count, rates); if(result < 0) { last_indicator_error = GetLastError(); last_indicator_error_desc = ErrorDescription(last_indicator_error); WriteLog("CopyRates error: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); } return result; } //+------------------------------------------------------------------+ //| SafeCopyHigh - Wrapper for CopyHigh with error handling | //+------------------------------------------------------------------+ int SafeCopyHigh(string symbol, ENUM_TIMEFRAMES timeframe, int start_pos, int count, double &high[]) { int result = CopyHigh(symbol, timeframe, start_pos, count, high); if(result < 0) { last_indicator_error = GetLastError(); last_indicator_error_desc = ErrorDescription(last_indicator_error); WriteLog("CopyHigh error: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); } return result; } //+------------------------------------------------------------------+ //| SafeCopyLow - Wrapper for CopyLow with error handling | //+------------------------------------------------------------------+ int SafeCopyLow(string symbol, ENUM_TIMEFRAMES timeframe, int start_pos, int count, double &low[]) { int result = CopyLow(symbol, timeframe, start_pos, count, low); if(result < 0) { last_indicator_error = GetLastError(); last_indicator_error_desc = ErrorDescription(last_indicator_error); WriteLog("CopyLow error: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); } return result; } //+------------------------------------------------------------------+ //| ErrorDescription - Returns error code description | //+------------------------------------------------------------------+ string ErrorDescription(int error_code) { switch(error_code) { case 0: return "No error"; case 1: return "No error, but result unknown"; case 2: return "Common error"; case 3: return "Invalid trade parameters"; case 4: return "Trade server is busy"; case 5: return "Old version of the client terminal"; case 6: return "No connection with trade server"; case 7: return "Not enough rights"; case 8: return "Too frequent requests"; case 9: return "Malfunctional trade operation"; case 64: return "Account disabled"; case 65: return "Invalid account"; case 128: return "Trade timeout"; case 129: return "Invalid price"; case 130: return "Invalid stops"; case 131: return "Invalid trade volume"; case 132: return "Market is closed"; case 133: return "Trade is disabled"; case 134: return "Not enough money"; case 135: return "Price changed"; case 136: return "Off quotes"; case 137: return "Broker is busy"; case 138: return "Requote"; case 139: return "Order is locked"; case 140: return "Long positions only allowed"; case 141: return "Too many requests"; case 145: return "Modification denied because order is too close to market"; case 146: return "Trade context is busy"; case 147: return "Expirations are denied by broker"; case 148: return "The amount of open and pending orders has reached the limit"; case 149: return "An attempt to open an order opposite to the existing one"; case 150: return "Invalid order type"; case 4000: return "No error"; case 4001: return "Wrong function pointer"; case 4002: return "Array index is out of range"; case 4003: return "No memory for function call stack"; case 4004: return "Recursive stack overflow"; case 4005: return "Not enough stack for parameter"; case 4006: return "Not enough memory for parameter string"; case 4007: return "Not enough memory for temp string"; case 4008: return "Not initialized string"; case 4009: return "Not initialized string in array"; case 4010: return "No memory for array string"; case 4011: return "Too long string"; case 4012: return "Remainder from zero divide"; case 4013: return "Zero divide"; case 4014: return "Unknown command"; case 4015: return "Wrong jump"; case 4016: return "Not initialized array"; case 4017: return "DLL calls are not allowed"; case 4018: return "Cannot load library"; case 4019: return "Cannot call function"; case 4020: return "Expert function calls are not allowed"; case 4021: return "Not enough memory for string returned from function"; case 4022: return "System is busy"; case 4050: return "Invalid function parameters count"; case 4051: return "Invalid function parameter value"; case 4052: return "String function internal error"; case 4053: return "Some array error"; case 4054: return "Incorrect series array using"; case 4055: return "Custom indicator error"; case 4056: return "Arrays are incompatible"; case 4057: return "Global variables processing error"; case 4058: return "Global variable not found"; case 4059: return "Function is not allowed in testing mode"; case 4060: return "Function is not confirmed"; case 4061: return "Send mail error"; case 4062: return "String parameter expected"; case 4063: return "Integer parameter expected"; case 4064: return "Double parameter expected"; case 4065: return "Array as parameter expected"; case 4066: return "Requested history data is in updating state"; case 4067: return "Some error in trade operation"; case 4099: return "End of file"; case 4100: return "Some file error"; case 4101: return "Wrong file name"; case 4102: return "Too many opened files"; case 4103: return "Cannot open file"; case 4104: return "Incompatible access to a file"; case 4105: return "No order selected"; case 4106: return "Unknown symbol"; case 4107: return "Invalid price parameter"; case 4108: return "Invalid ticket"; case 4109: return "Trade is not allowed"; case 4110: return "Longs are not allowed"; case 4111: return "Shorts are not allowed"; case 4200: return "Object already exists"; case 4201: return "Unknown object property"; case 4202: return "Object does not exist"; case 4203: return "Unknown object type"; case 4204: return "No object name"; case 4205: return "Object coordinates error"; case 4206: return "No specified subwindow"; case 4207: return "Some error in object function"; default: return "Unknown error"; } } //+------------------------------------------------------------------+ //| ValidateATRValue - Checks if ATR value is valid | //+------------------------------------------------------------------+ bool ValidateATRValue(double atr_value, string context = "") { if(atr_value <= 0) { WriteLog("Invalid ATR value" + (context != "" ? " in " + context : "") + ": " + DoubleToString(atr_value)); return false; } return true; } //+------------------------------------------------------------------+ //| ValidatePrice - Checks if price value is valid | //+------------------------------------------------------------------+ bool ValidatePrice(double price, string context = "") { if(price <= 0) { WriteLog("Invalid price value" + (context != "" ? " in " + context : "") + ": " + DoubleToString(price)); return false; } return true; } //+------------------------------------------------------------------+ //| Initialize news filtering system | //+------------------------------------------------------------------+ void InitializeNewsFiltering() { last_news_check = 0; ArrayResize(high_impact_events, 0); news_quiet_period_until = 0; news_position_size_factor = 1.0; WriteLog("News filtering system initialized"); WriteLog("API Key: " + StringSubstr(InpNewsApiKey, 0, 5) + "..." + StringSubstr(InpNewsApiKey, StringLen(InpNewsApiKey) - 5)); WriteLog("API URL: " + InpNewsApiUrl); } //+------------------------------------------------------------------+ //| Check for high-impact news events using jblanked API | //+------------------------------------------------------------------+ void CheckForNewsEvents() { if(!InpEnableNewsFilter) return; // Handle initial case when last_news_check is 0 if(last_news_check == 0) { last_news_check = TimeCurrent() - (InpNewsCheckInterval * 60); } // Check if it's time to check for news if(TimeCurrent() - last_news_check < InpNewsCheckInterval * 60) return; last_news_check = TimeCurrent(); // Check if it's time to check for news if(TimeCurrent() - last_news_check < InpNewsCheckInterval * 60) return; last_news_check = TimeCurrent(); // Prepare API request string headers = "Content-Type: application/json\r\nAuthorization: Bearer " + InpNewsApiKey; char post[], result[]; string result_str; // Prepare request data (example structure - adjust based on actual API requirements) string request_data = "{\"symbol\":\"" + InpSymbol + "\",\"lookahead_hours\":" + IntegerToString(InpNewsLookaheadHours) + ",\"min_impact\":\"High\"}"; // Convert to char array for WebRequest StringToCharArray(request_data, post, 0, StringLen(request_data)); // Make API request int res = WebRequest("POST", InpNewsApiUrl, headers, 5000, post, result, headers); if(res == 200) // HTTP OK { result_str = CharArrayToString(result, 0, ArraySize(result)); ProcessNewsResponse(result_str); } else { WriteLog("News API request failed with error: " + IntegerToString(res)); } } void ProcessNewsResponse(string response) { // Reset previous events and settings ArrayFree(high_impact_events); ArrayResize(high_impact_events, 0); news_position_size_factor = 1.0; news_quiet_period_until = 0; // Validate input if(StringLen(response) == 0) { WriteLog("Empty news API response received"); return; } CJAson parser; if(!parser.Parse(response)) { WriteLog("Failed to parse news JSON response: " + response); return; } // Extract events array safely CJAsonValue* events = parser.GetNode("events"); if(events == NULL || events.GetType() != J_ARRAY) { WriteLog("No events array found in news response or invalid format"); parser.Release(); return; } int event_count = 0; datetime current_time = TimeCurrent(); // Process each event in the array with bounds checking for(int i = 0; i < events.Size(); i++) { // Add bounds checking if(i >= events.Size()) break; CJAsonValue* event = events[i]; if(event == NULL || event.GetType() != J_OBJECT) continue; // Extract event details with robust error handling string title = ""; string impact = ""; string timestamp_str = ""; string currency = ""; CJAsonValue* title_node = event["title"]; if(title_node != NULL && title_node.GetType() == J_STRING) title = title_node.ToString(); CJAsonValue* impact_node = event["impact"]; if(impact_node != NULL && impact_node.GetType() == J_STRING) impact = impact_node.ToString(); CJAsonValue* time_node = event["timestamp"]; if(time_node != NULL) { if(time_node.GetType() == J_STRING) timestamp_str = time_node.ToString(); else if(time_node.GetType() == J_NUMBER) timestamp_str = IntegerToString(time_node.ToInt()); } CJAsonValue* currency_node = event["currency"]; if(currency_node != NULL && currency_node.GetType() == J_STRING) currency = currency_node.ToString(); // Validate required fields if(StringLen(title) == 0 || StringLen(impact) == 0 || StringLen(timestamp_str) == 0) continue; // Only process events for our symbol's currency if(StringLen(currency) > 0) { string base_currency = SymbolInfoString(InpSymbol, SYMBOL_CURRENCY_BASE); string profit_currency = SymbolInfoString(InpSymbol, SYMBOL_CURRENCY_PROFIT); if(currency != base_currency && currency != profit_currency) continue; } // Convert timestamp to datetime datetime event_time = (datetime)StringToInteger(timestamp_str); if(event_time <= 0) continue; // Check if event is high impact and in the future if(impact == "High" && event_time > current_time) { // Calculate time until event int minutes_until_event = (int)((event_time - current_time) / 60); // Check if we're in the pre-news quiet period if(minutes_until_event <= InpPreNewsQuietPeriod) { // Apply news impact adjustments news_position_size_factor = InpNewsImpactPositionSize; news_quiet_period_until = (int)(event_time + InpPostNewsQuietPeriod * 60); // Add to high impact events with proper array handling int new_size = ArraySize(high_impact_events) + 1; ArrayResize(high_impact_events, new_size); high_impact_events[new_size-1] = title + " at " + TimeToString(event_time); event_count++; // Log the cause and effect string cause_effect = "News Cause: " + title + " at " + TimeToString(event_time) + " | Effect: Position size reduced to " + DoubleToString(news_position_size_factor * 100, 0) + "% for " + IntegerToString(InpPreNewsQuietPeriod + InpPostNewsQuietPeriod) + " minutes"; WriteLog(cause_effect); } else if(minutes_until_event <= InpPreNewsQuietPeriod * 2) { // Event is coming up but not yet imminent int new_size = ArraySize(high_impact_events) + 1; ArrayResize(high_impact_events, new_size); high_impact_events[new_size-1] = title + " at " + TimeToString(event_time); event_count++; WriteLog("Upcoming high impact news event: " + title + " at " + TimeToString(event_time) + " | Effect: Position size will be reduced in " + IntegerToString(minutes_until_event - InpPreNewsQuietPeriod) + " minutes"); } } } // Log results if(event_count == 0) { WriteLog("No high-impact news events found in the next " + IntegerToString(InpNewsLookaheadHours) + " hours"); } else { // Perform comprehensive Villahermosa analysis string news_analysis = VillahermosaNewsImpactAnalysis(); WriteLog(news_analysis); if(InpEnableAlerts) { Alert("News Alert: ", ArraySize(high_impact_events), " high-impact event(s) detected. Position size factor: ", DoubleToString(news_position_size_factor, 2)); } } // Clean up parser.Release(); } // Helper function to extract string value from JSON string GetJsonString(CJAsonValue* node, string defaultValue = "") { if(node == NULL || node.GetType() != J_STRING) return defaultValue; return node.ToString(); } // Helper function to extract integer value from JSON int GetJsonInt(CJAsonValue* node, int defaultValue = 0) { if(node == NULL || node.GetType() != J_NUMBER) return defaultValue; return node.ToInt(); } // Villahermosa News Impact Analysis function string VillahermosaNewsImpactAnalysis() { if(ArraySize(high_impact_events) == 0) { return "Villahermosa News Analysis | Cause: No scheduled high-impact economic events | " + "Effect: Normal market conditions expected | " + "Action: Continue standard trading approach with full position sizing"; } // Safe array access with bounds checking string primary_cause = ArraySize(high_impact_events) + " high-impact economic event(s) detected: "; for(int i = 0; i < MathMin(ArraySize(high_impact_events), 3); i++) { if(i < ArraySize(high_impact_events)) // Additional bounds check { primary_cause += high_impact_events[i]; if(i < MathMin(ArraySize(high_impact_events), 3) - 1) primary_cause += ", "; } } string analysis = "Villahermosa News Impact Analysis | "; string immediate_effects = ""; string projected_effects = ""; string risk_assessment = ""; string strategic_recommendation = ""; datetime current_time = TimeCurrent(); bool in_quiet_period = (news_quiet_period_until > current_time); int minutes_remaining = (int)((news_quiet_period_until - current_time) / 60); // Immediate effects analysis if(in_quiet_period) { immediate_effects = "Position size reduced to " + DoubleToString(news_position_size_factor * 100, 0) + "% of normal for " + IntegerToString(minutes_remaining) + " more minutes to mitigate news-induced volatility spikes"; projected_effects = "Expected: Increased volatility (50-100% above normal) during news release, " + "potential false breakouts, and widened spreads (5-10 pips above normal)"; risk_assessment = "Elevated risk of slippage (2-3x normal) and whipsaw price action. " + "Reduced position sizing decreases potential loss by " + DoubleToString((1 - news_position_size_factor) * 100, 0) + "%"; } else { immediate_effects = "Normal trading rules applied: Full position sizing (100%) and standard risk parameters"; projected_effects = "Expected: Normal market conditions with typical volatility and execution quality"; risk_assessment = "Risk management: Standard position sizing and risk parameters appropriate for current market conditions"; } // Strategic recommendation with Villahermosa methodology if(in_quiet_period) { strategic_recommendation = "Villahermosa Protocol: Maintain defensive posture. " + "Execute only high-probability setups with reduced size. " + "Focus on preserving capital during volatile period. " + "Resume normal trading after " + TimeToString(news_quiet_period_until); } else { strategic_recommendation = "Villahermosa Protocol: Prepare for defensive measures. " + "Begin reducing position sizes " + IntegerToString(InpPreNewsQuietPeriod) + " minutes before each event. " + "Consider closing existing positions 30 minutes before news if profitable"; } // Compile comprehensive analysis analysis += "Primary Cause: " + primary_cause + " | " + "Immediate Effects: " + immediate_effects + " | " + "Projected Effects: " + projected_effects + " | " + "Risk Assessment: " + risk_assessment + " | " + "Strategic Recommendation: " + strategic_recommendation; return analysis; } //+------------------------------------------------------------------+ //| Get news-adjusted position size factor with Cause & Effect | //+------------------------------------------------------------------+ double GetNewsAdjustedPositionSize(string &cause_effect = "") { datetime current_time = TimeCurrent(); string cause = ""; string effect = ""; string impact_analysis = ""; string recommendation = ""; // Check if we're in a news quiet period if(news_quiet_period_until > current_time) { int minutes_remaining = (int)((news_quiet_period_until - current_time) / 60); // Determine cause based on news context if(ArraySize(high_impact_events) > 0) { cause = "High-impact news event detected: " + high_impact_events[0]; if(ArraySize(high_impact_events) > 1) { cause += " and " + IntegerToString(ArraySize(high_impact_events) - 1) + " other events"; } } else { cause = "News quiet period active (unknown specific events)"; } // Calculate effect on position sizing double size_reduction = (1.0 - news_position_size_factor) * 100; effect = "Position size reduced by " + DoubleToString(size_reduction, 1) + "% to " + DoubleToString(news_position_size_factor * 100, 1) + "% of normal for " + IntegerToString(minutes_remaining) + " more minutes"; // Impact analysis impact_analysis = "Expected impact: Reduced volatility exposure by " + DoubleToString(size_reduction, 1) + "%, decreasing potential " + "profit/loss by approximately " + DoubleToString(size_reduction, 1) + "%"; // Recommendation recommendation = "Villahermosa Protocol: Maintain reduced position sizing until " + TimeToString(news_quiet_period_until) + " to navigate news volatility"; cause_effect = "News Impact Analysis | Cause: " + cause + " | Effect: " + effect + " | Impact: " + impact_analysis + " | Recommendation: " + recommendation; return news_position_size_factor; } // Normal market conditions cause = "No active high-impact news events detected"; effect = "Normal position sizing (100%) applied"; impact_analysis = "Standard market conditions: No expected news-induced volatility spikes"; recommendation = "Continue with standard Villahermosa position sizing methodology"; cause_effect = "News Impact Analysis | Cause: " + cause + " | Effect: " + effect + " | Impact: " + impact_analysis + " | Recommendation: " + recommendation; return 1.0; // Normal position size } //+------------------------------------------------------------------+ //| Check if trading should be paused due to news events | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ bool ShouldPauseTradingForNews(string &cause_effect = "") { datetime current_time = TimeCurrent(); string cause = ""; string effect = ""; string risk_analysis = ""; string recommendation = ""; bool should_pause = false; // Check if we're in a news quiet period if(news_quiet_period_until > current_time) { int minutes_remaining = (int)((news_quiet_period_until - current_time) / 60); // Analyze news events to determine if trading should be paused if(ArraySize(high_impact_events) > 0) { // Check for extreme impact events that warrant complete trading pause for(int i = 0; i < ArraySize(high_impact_events); i++) { string event = high_impact_events[i]; // Identify events that typically cause extreme volatility if(StringFind(event, "NFP") >= 0 || StringFind(event, "Non-Farm Payrolls") >= 0 || StringFind(event, "FOMC") >= 0 || StringFind(event, "Federal Reserve") >= 0 || StringFind(event, "ECB") >= 0 || StringFind(event, "Interest Rate") >= 0 || StringFind(event, "CPI") >= 0) { should_pause = true; cause = "Extreme volatility expected from high-impact event: " + event; effect = "Trading paused completely to avoid unpredictable price action and potential slippage"; risk_analysis = "Risk of 3-5x normal volatility, 10-20 pip spreads, and 5-10x normal slippage"; recommendation = "Villahermosa Protocol: Complete trading pause until " + TimeToString(news_quiet_period_until) + ". Resume with caution after event conclusion."; break; } } if(!should_pause) { cause = "Moderate news events detected: " + high_impact_events[0]; if(ArraySize(high_impact_events) > 1) { cause += " and " + IntegerToString(ArraySize(high_impact_events) - 1) + " other events"; } effect = "Trading continues with reduced position sizing (" + DoubleToString(news_position_size_factor * 100, 1) + "%)"; risk_analysis = "Expected 2-3x normal volatility with moderate slippage risk"; recommendation = "Villahermosa Protocol: Continue trading with reduced size for " + IntegerToString(minutes_remaining) + " more minutes"; } } else { cause = "News quiet period active but no specific high-impact events identified"; effect = "Trading continues with normal position sizing"; risk_analysis = "Minimal expected impact on market conditions"; recommendation = "Continue standard trading approach"; } } else { cause = "No active news quiet period detected"; effect = "Normal trading conditions with standard position sizing"; risk_analysis = "Market operating under normal volatility conditions"; recommendation = "Execute standard Villahermosa trading strategy"; } // Compile comprehensive cause and effect analysis cause_effect = "News Trading Analysis | Cause: " + cause + " | Effect: " + effect + " | Risk: " + risk_analysis + " | Recommendation: " + recommendation; // Log the analysis WriteLog(cause_effect); return should_pause; } //+------------------------------------------------------------------+ //| Update trading decisions based on news analysis | //| with comprehensive Cause & Effect framework | //+------------------------------------------------------------------+ void UpdateNewsBasedTradingRules(string &cause_effect = "") { string overall_cause = ""; string overall_effect = ""; string market_impact = ""; string strategic_recommendation = ""; string risk_implications = ""; // Check for news events (cause) CheckForNewsEvents(); // Perform Villahermosa analysis string news_analysis = VillahermosaNewsImpactAnalysis(); // Determine the primary cause from news analysis if(ArraySize(high_impact_events) > 0) { overall_cause = ArraySize(high_impact_events) + " high-impact news event(s) detected: "; for(int i = 0; i < MathMin(ArraySize(high_impact_events), 3); i++) { overall_cause += high_impact_events[i]; if(i < MathMin(ArraySize(high_impact_events), 3) - 1) overall_cause += ", "; } } else { overall_cause = "No high-impact news events detected in the next " + IntegerToString(InpNewsLookaheadHours) + " hours"; } // Determine the effect on trading rules datetime current_time = TimeCurrent(); if(news_quiet_period_until > current_time) { int minutes_remaining = (int)((news_quiet_period_until - current_time) / 60); overall_effect = "Trading rules modified for " + IntegerToString(minutes_remaining) + " minutes: Position size reduced to " + DoubleToString(news_position_size_factor * 100, 1) + "% of normal"; market_impact = "Expected: Increased volatility (2-3x normal), wider spreads (5-10 pips above normal), " + "and higher slippage risk (2-3x normal execution variance)"; risk_implications = "Risk management: Reduced position sizing decreases potential loss by " + DoubleToString((1 - news_position_size_factor) * 100, 1) + "% while maintaining " + "market exposure during volatile period"; strategic_recommendation = "Villahermosa Protocol: Execute only high-probability setups with reduced size. " + "Focus on capital preservation. Avoid new positions 15 minutes before and after " + "scheduled news events"; } else { overall_effect = "Normal trading rules applied: Full position sizing (100%) and standard risk parameters"; market_impact = "Expected: Normal market conditions with typical volatility and execution quality"; risk_implications = "Risk management: Standard position sizing and risk parameters appropriate for " + "current market conditions"; strategic_recommendation = "Villahermosa Protocol: Continue with standard trading approach. " + "Monitor for emerging news events that may change market conditions"; } // Add market context to analysis string market_context = GetTradingSessionContext(); double volatility_ratio = CalculateMarketVolatility(); overall_cause += " in " + market_context + " context with " + DoubleToString(volatility_ratio, 2) + "x normal volatility"; // Compile comprehensive cause and effect analysis cause_effect = "News-Based Trading Rules Update | " + "Primary Cause: " + overall_cause + " | " + "Trading Effect: " + overall_effect + " | " + "Market Impact: " + market_impact + " | " + "Risk Implications: " + risk_implications + " | " + "Strategic Recommendation: " + strategic_recommendation + " | " + "Villahermosa Analysis: " + news_analysis; // Log the detailed analysis WriteLog(cause_effect); // Update chart with news information UpdateChartNewsInfo(); // Execute rule changes based on analysis ExecuteNewsBasedRuleChanges(); } //+------------------------------------------------------------------+ //| UpdateVolatilityBasedTradingRules - Adjust rules based on vol | //+------------------------------------------------------------------+ void UpdateVolatilityBasedTradingRules() { string rules_analysis = "Volatility-Based Trading Rules Update: "; // Get current volatility ratio double atr_values[20]; if(CopyBuffer(atr_handle, 0, 0, 20, atr_values) >= 20) { double current_atr = atr_values[0]; double atr_sum = 0; for(int i = 1; i < 20; i++) atr_sum += atr_values[i]; double atr_avg = atr_sum / 19; if(atr_avg > 0) { double vol_ratio = current_atr / atr_avg; if(vol_ratio > 2.0) { rules_analysis += "Extreme volatility detected (" + DoubleToString(vol_ratio, 2) + "x normal). Position sizing reduced by 40%."; volatility_position_factor = 0.6; } else if(vol_ratio > 1.5) { rules_analysis += "High volatility detected (" + DoubleToString(vol_ratio, 2) + "x normal). Position sizing reduced by 25%."; volatility_position_factor = 0.75; } else if(vol_ratio < 0.5) { rules_analysis += "Low volatility detected (" + DoubleToString(vol_ratio, 2) + "x normal). Position sizing increased by 15%."; volatility_position_factor = 1.15; } else { rules_analysis += "Normal volatility conditions (" + DoubleToString(vol_ratio, 2) + "x normal). Standard position sizing."; volatility_position_factor = 1.0; } } } WriteLog(rules_analysis); } //+------------------------------------------------------------------+ //| LogSystemStatus - Logs current system status | //+------------------------------------------------------------------+ void LogSystemStatus() { string status_report = "System Status Report: "; // Account information double equity = AccountInfoDouble(ACCOUNT_EQUITY); double balance = AccountInfoDouble(ACCOUNT_BALANCE); double drawdown = (balance > 0) ? (balance - equity) / balance * 100 : 0; status_report += "Equity: " + DoubleToString(equity, 2) + " | Balance: " + DoubleToString(balance, 2) + " | Drawdown: " + DoubleToString(drawdown, 2) + "%"; // Position information int position_count = 0; double total_profit = 0; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == InpSymbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { position_count++; total_profit += PositionGetDouble(POSITION_PROFIT); } } status_report += " | Positions: " + IntegerToString(position_count) + " | P/L: " + DoubleToString(total_profit, 2); // Trading state status_report += " | Trading: " + (trading_paused ? "PAUSED" : "ACTIVE") + " | Daily Trades: " + IntegerToString(daily_trade_count) + "/" + IntegerToString(InpMaxTradesPerDay); // News state datetime current_time = TimeCurrent(); if(news_quiet_period_until > current_time) { int minutes_remaining = (int)((news_quiet_period_until - current_time) / 60); status_report += " | News Impact: Active (" + IntegerToString(minutes_remaining) + " min remaining)"; } else { status_report += " | News Impact: None"; } WriteLog(status_report); } //+------------------------------------------------------------------+ //| Initialize drawdown tracking | //+------------------------------------------------------------------+ void InitializeDrawdownTracking() { // Set initial account high account_high = AccountInfoDouble(InpUseEquityInsteadOfBalance ? ACCOUNT_EQUITY : ACCOUNT_BALANCE); last_high_update = TimeCurrent(); WriteLog("Drawdown tracking initialized"); WriteLog("Using " + (InpUseEquityInsteadOfBalance ? "Equity" : "Balance") + " for drawdown calculations. Initial value: " + DoubleToString(account_high, 2)); // Set up periodic drawdown checking (every 5 minutes) EventSetTimer(300); } //+------------------------------------------------------------------+ //| Update drawdown metrics with analysis | //+------------------------------------------------------------------+ void UpdateDrawdownMetrics(string &analysis) { // Update account high-water mark UpdateAccountHigh(); // Calculate current drawdown with analysis double current_drawdown = CalculateCurrentDrawdown(analysis); // Update trading pause state based on drawdown if(current_drawdown >= InpPauseAtDD && !trading_paused) { trading_paused = true; if(InpEnableAlerts) { Alert("Trading paused due to drawdown: ", DoubleToString(current_drawdown*100, 1), "%"); } WriteLog("Trading paused due to drawdown: " + DoubleToString(current_drawdown*100, 1) + "%"); } else if(current_drawdown <= InpResumeDD && trading_paused) { trading_paused = false; if(InpEnableAlerts) { Alert("Trading resumed. Drawdown: ", DoubleToString(current_drawdown*100, 1), "%"); } WriteLog("Trading resumed. Drawdown: " + DoubleToString(current_drawdown*100, 1) + "%"); } else if(current_drawdown >= InpReduceAtDD && InpEnableAlerts && !trading_paused) { Alert("Position size reduced due to drawdown: ", DoubleToString(current_drawdown*100, 1), "%"); } // Update chart with drawdown information UpdateChartDrawdownInfo(current_drawdown); } //+------------------------------------------------------------------+ //| Manage existing positions (trailing stops, etc.) | //+------------------------------------------------------------------+ void ManageExistingPositions() { string position_reason; int pos_dir = PositionDirection(position_reason); if(pos_dir != 0) { //--- Get position details ulong ticket = PositionGetTicket(0); if(ticket > 0) { ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); datetime open_time = (datetime)PositionGetInteger(POSITION_TIME); double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); double profit = PositionGetDouble(POSITION_PROFIT); long position_age = TimeCurrent() - open_time; //--- Villahermosa trailing analysis string trail_analysis = VillahermosaTrailingAnalysis(ticket, type, open_price, current_price, profit, position_age, last_atr_value); WriteLog("Trailing Analysis: " + trail_analysis); //--- Optional trailing ATR stop with Villahermosa if(InpUseTrailingStop) { TrailATR(pos_dir, last_atr_value); } } } } //+------------------------------------------------------------------+ //| Villahermosa Deinitialization Analysis | //+------------------------------------------------------------------+ string VillahermosaDeinitAnalysis(const int reason) { string analysis = "Villahermosa Deinitialization Analysis | "; string cause = ""; string effect = ""; string recommendation = ""; switch(reason) { case REASON_ACCOUNT: cause = "Account changed"; effect = "Trading environment no longer valid for current strategy"; recommendation = "Reinitialize EA after verifying account compatibility"; break; case REASON_CHARTCHANGE: cause = "Chart symbol or timeframe changed"; effect = "Trading strategy parameters no longer match chart settings"; recommendation = "Reinitialize EA with correct symbol/timeframe settings"; break; case REASON_CHARTCLOSE: cause = "Chart closed"; effect = "Trading operations terminated"; recommendation = "EA will automatically stop trading"; break; case REASON_PARAMETERS: cause = "Input parameters changed"; effect = "Strategy configuration modified"; recommendation = "Review new parameters before reinitializing"; break; case REASON_RECOMPILE: cause = "EA recompiled"; effect = "New code version loaded"; recommendation = "Verify new version functionality before trading"; break; case REASON_REMOVE: cause = "EA removed from chart"; effect = "Trading strategy completely terminated"; recommendation = "All positions remain open unless manually closed"; break; case REASON_TEMPLATE: cause = "Template changed"; effect = "Chart appearance modified but trading logic intact"; recommendation = "Reinitialize EA if trading parameters affected"; break; case REASON_INITFAILED: cause = "Initialization failed"; effect = "EA could not be properly initialized"; recommendation = "Check input parameters and system status"; break; case REASON_CLOSE: cause = "Terminal closed"; effect = "Trading operations terminated"; recommendation = "EA will restart when terminal is reopened"; break; default: cause = "Unknown reason (" + IntegerToString(reason) + ")"; effect = "Unexpected deinitialization"; recommendation = "Check logs for potential issues"; break; } analysis += "Cause: " + cause + " | Effect: " + effect + " | Recommendation: " + recommendation; return analysis; } //+------------------------------------------------------------------+ //| Get deinitialization reason text | //+------------------------------------------------------------------+ string GetDeinitReasonText(const int reason) { switch(reason) { case REASON_ACCOUNT: return "Account changed"; case REASON_CHARTCHANGE: return "Chart changed"; case REASON_CHARTCLOSE: return "Chart closed"; case REASON_CLOSE: return "Terminal closed"; case REASON_INITFAILED: return "Initialization failed"; case REASON_PARAMETERS: return "Parameters changed"; case REASON_RECOMPILE: return "Recompiled"; case REASON_REMOVE: return "EA removed"; case REASON_TEMPLATE: return "Template changed"; default: return "Unknown reason (" + IntegerToString(reason) + ")"; } } //+------------------------------------------------------------------+ //| Log final account status | //+------------------------------------------------------------------+ void LogFinalAccountStatus() { double equity = AccountInfoDouble(ACCOUNT_EQUITY); double balance = AccountInfoDouble(ACCOUNT_BALANCE); double profit = equity - balance; string status = "Final Account Status | " + "Equity: " + DoubleToString(equity, 2) + " | Balance: " + DoubleToString(balance, 2) + " | P/L: " + DoubleToString(profit, 2) + " (" + DoubleToString((balance != 0) ? (profit / balance) * 100 : 0, 2) + "%)"; // Calculate performance metrics int total_positions = PositionsTotal(); int total_orders = OrdersTotal(); status += " | Open Positions: " + IntegerToString(total_positions) + " | Pending Orders: " + IntegerToString(total_orders); WriteLog(status); // Log Villahermosa performance summary if(profit > 0) { WriteLog("Villahermosa Performance: Profitable session (+" + DoubleToString(profit, 2) + ")"); } else if(profit < 0) { WriteLog("Villahermosa Performance: Unprofitable session (" + DoubleToString(profit, 2) + ")"); } else { WriteLog("Villahermosa Performance: Break-even session"); } } //+------------------------------------------------------------------+ //| ObjectsDeleteAll with prefix function | //+------------------------------------------------------------------+ void ObjectsDeleteAll(const long chart_id, const string prefix) { int total = ObjectsTotal(chart_id, 0, -1); for(int i = total - 1; i >= 0; i--) { string name = ObjectName(chart_id, i, 0, -1); if(StringFind(name, prefix) == 0) { ObjectDelete(chart_id, name); } } } //+------------------------------------------------------------------+ //| CheckMarketConditions - Analyzes current market environment | //+------------------------------------------------------------------+ void CheckMarketConditions() { string market_analysis = "Market Conditions Analysis: "; string volatility_context = ""; string trend_context = ""; string liquidity_context = ""; // Get current volatility double atr_values[1]; if(CopyBuffer(atr_handle, 0, 0, 1, atr_values) == 1) { double atr_pips = atr_values[0] / SymbolInfoDouble(InpSymbol, SYMBOL_POINT); if(atr_pips > 50) volatility_context = "High Volatility (" + DoubleToString(atr_pips, 1) + " pips)"; else if(atr_pips > 30) volatility_context = "Moderate Volatility (" + DoubleToString(atr_pips, 1) + " pips)"; else volatility_context = "Low Volatility (" + DoubleToString(atr_pips, 1) + " pips)"; } // Get trend context MqlRates rates[50]; if(CopyRates(InpSymbol, InpTF, 0, 50, rates) == 50) { double sum = 0; for(int i = 0; i < 50; i++) sum += rates[i].close; double ma50 = sum / 50; sum = 0; for(int i = 0; i < 20; i++) sum += rates[i].close; double ma20 = sum / 20; if(ma20 > ma50 * 1.02) trend_context = "Strong Uptrend"; else if(ma20 > ma50) trend_context = "Weak Uptrend"; else if(ma20 < ma50 * 0.98) trend_context = "Strong Downtrend"; else if(ma20 < ma50) trend_context = "Weak Downtrend"; else trend_context = "Sideways/Ranging"; } // Get liquidity context (using spread as proxy) double spread = SymbolInfoInteger(InpSymbol, SYMBOL_SPREAD) * SymbolInfoDouble(InpSymbol, SYMBOL_POINT); double spread_pips = spread / SymbolInfoDouble(InpSymbol, SYMBOL_POINT); if(spread_pips > 20) liquidity_context = "Low Liquidity (Spread: " + DoubleToString(spread_pips, 1) + " pips)"; else if(spread_pips > 10) liquidity_context = "Moderate Liquidity (Spread: " + DoubleToString(spread_pips, 1) + " pips)"; else liquidity_context = "High Liquidity (Spread: " + DoubleToString(spread_pips, 1) + " pips)"; // Compile market analysis market_analysis += "Volatility: " + volatility_context + " | Trend: " + trend_context + " | Liquidity: " + liquidity_context; WriteLog(market_analysis); // Update global market condition variables if needed if(InpEnableLogging) { // You could set global flags here for use in other functions // e.g., bool high_volatility = (atr_pips > 50); } } //+------------------------------------------------------------------+ //| AnalyzePositionPerformance - Analyzes current positions | //+------------------------------------------------------------------+ void AnalyzePositionPerformance() { string performance_analysis = "Position Performance Analysis: "; int winning_positions = 0; int losing_positions = 0; double total_profit = 0; double largest_win = 0; double largest_loss = 0; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == InpSymbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { double profit = PositionGetDouble(POSITION_PROFIT); total_profit += profit; if(profit > 0) { winning_positions++; if(profit > largest_win) largest_win = profit; } else { losing_positions++; if(profit < largest_loss) largest_loss = profit; } // Individual position analysis ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); datetime open_time = (datetime)PositionGetInteger(POSITION_TIME); long position_age = TimeCurrent() - open_time; string position_analysis = VillahermosaPositionHealthCheck(ticket, type, open_price, current_price, profit, position_age); WriteLog("Position " + IntegerToString(ticket) + ": " + position_analysis); } } // Summary analysis int total_positions = winning_positions + losing_positions; double win_rate = (total_positions > 0) ? (double)winning_positions / total_positions * 100 : 0; performance_analysis += "Positions: " + IntegerToString(total_positions) + " | Win Rate: " + DoubleToString(win_rate, 1) + "%" + " | P/L: " + DoubleToString(total_profit, 2) + " | Best: " + DoubleToString(largest_win, 2) + " | Worst: " + DoubleToString(largest_loss, 2); WriteLog(performance_analysis); } //+------------------------------------------------------------------+ //| Enhanced Donchian function with error handling | //+------------------------------------------------------------------+ double Donchian(string sym, ENUM_TIMEFRAMES tf, int lookback, int mode, int shift) { if(lookback <= 0) { WriteLog("Invalid lookback period in Donchian: " + IntegerToString(lookback)); return 0; } if(mode == MODE_HIGH) { double highs[]; ArraySetAsSeries(highs, true); int copied = SafeCopyHigh(sym, tf, shift, lookback, highs); if(copied <= 0 || copied < lookback) { WriteLog("Error copying highs in Donchian: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); return 0; } int max_index = ArrayMaximum(highs, 0, copied); if(max_index < 0) { WriteLog("ArrayMaximum failed in Donchian"); return 0; } double result = highs[max_index]; if(!ValidatePrice(result, "Donchian high")) { return 0; } return result; } else if(mode == MODE_LOW) { double lows[]; ArraySetAsSeries(lows, true); int copied = SafeCopyLow(sym, tf, shift, lookback, lows); if(copied <= 0 || copied < lookback) { WriteLog("Error copying lows in Donchian: " + IntegerToString(last_indicator_error) + " - " + last_indicator_error_desc); return 0; } int min_index = ArrayMinimum(lows, 0, copied); if(min_index < 0) { WriteLog("ArrayMinimum failed in Donchian"); return 0; } double result = lows[min_index]; if(!ValidatePrice(result, "Donchian low")) { return 0; } return result; } else { WriteLog("Invalid mode in Donchian: " + IntegerToString(mode)); return 0; } } //+------------------------------------------------------------------+ //| Villahermosa News Impact Categorization | //| Classifies news events by expected market impact | //+------------------------------------------------------------------+ string CategorizeNewsImpact(string event_name) { // Extreme impact events (typically cause trading pauses) if(StringFind(event_name, "NFP") >= 0 || StringFind(event_name, "Non-Farm Payrolls") >= 0 || StringFind(event_Name, "FOMC") >= 0 || StringFind(event_name, "Federal Reserve") >= 0 || StringFind(event_name, "Interest Rate") >= 0) { return "Extreme Impact"; } // High impact events (reduce position size but don't pause) if(StringFind(event_name, "CPI") >= 0 || StringFind(event_name, "ECB") >= 0 || StringFind(event_name, "GDP") >= 0 || StringFind(event_name, "Retail Sales") >= 0) { return "High Impact"; } // Medium impact events if(StringFind(event_name, "Unemployment") >= 0 || StringFind(event_name, "PMI") >= 0 || StringFind(event_name, "Housing Starts") >= 0) { return "Medium Impact"; } return "Low Impact"; } //+------------------------------------------------------------------+ //| Get News Impact Score | //| Quantifies news impact on a scale of 0-100 | //+------------------------------------------------------------------+ int GetNewsImpactScore(string event_name) { string impact = CategorizeNewsImpact(event_name); if(impact == "Extreme Impact") return 90; if(impact == "High Impact") return 70; if(impact == "Medium Impact") return 50; if(impact == "Low Impact") return 30; return 0; // Unknown impact } //+------------------------------------------------------------------+ //| Update News Trading Rules | //| Comprehensive news impact analysis with cause & effect | //+------------------------------------------------------------------+ void UpdateNewsTradingRules() { string cause_effect; bool pause_trading = ShouldPauseTradingForNews(cause_effect); // Additional analysis based on market conditions double volatility_ratio = CalculateMarketVolatility(); string market_context = GetTradingSessionContext(); // Enhance the cause-effect analysis with market context string enhanced_analysis = cause_effect + " | Market Context: " + market_context + " | Volatility Ratio: " + DoubleToString(volatility_ratio, 2) + "x"; // Update global trading pause state if(pause_trading) { trading_paused = true; if(InpEnableAlerts) { Alert("Trading paused due to news: ", cause_effect); } } WriteLog(enhanced_analysis); } //+------------------------------------------------------------------+ //| Execute news-based trading rule changes | //| with Cause & Effect tracking | //+------------------------------------------------------------------+ void ExecuteNewsBasedRuleChanges() { string execution_cause = ""; string execution_effect = ""; datetime current_time = TimeCurrent(); if(news_quiet_period_until > current_time) { execution_cause = "Active news quiet period until " + TimeToString(news_quiet_period_until); execution_effect = "Position sizing reduced to " + DoubleToString(news_position_size_factor * 100, 1) + "% of normal"; // Additional rule modifications based on news impact if(ArraySize(high_impact_events) > 0 && IsExtremeImpactEvent(high_impact_events[0])) { execution_effect += ", stop losses widened by 25% to account for increased volatility"; UpdateStopLossMultiplier(InpATRStopMult * 1.25); } } else { execution_cause = "No active news quiet period"; execution_effect = "Standard trading rules restored: Full position sizing and normal stop parameters"; // Reset any news-based modifications UpdateStopLossMultiplier(InpATRStopMult); // Reset to original } // Log execution details WriteLog("Rule Execution | Cause: " + execution_cause + " | Effect: " + execution_effect); } //+------------------------------------------------------------------+ //| Check if event is considered extreme impact | //+------------------------------------------------------------------+ bool IsExtremeImpactEvent(string event_name) { // Convert event name to uppercase for case-insensitive comparison string event_name_upper = StringToUpper(event_name); // Events that typically cause extreme market movements string extreme_events[] = { "NFP", "NON-FARM PAYROLLS", "FOMC", "FEDERAL RESERVE", "INTEREST RATE", "ECB", "BOE", "BOJ", "CPI", "GDP", "ELECTIONS", "REFERENDUM" }; for(int i = 0; i < ArraySize(extreme_events); i++) { if(StringFind(event_name_upper, extreme_events[i]) >= 0) return true; } return false; } //+------------------------------------------------------------------+ //| Update stop loss multiplier based on market conditions | //+------------------------------------------------------------------+ void UpdateStopLossMultiplier(double new_multiplier) { string cause = "Market condition change detected"; string effect = "Stop loss multiplier adjusted from " + DoubleToString(current_stop_multiplier, 2) + " to " + DoubleToString(new_multiplier, 2); current_stop_multiplier = new_multiplier; WriteLog("Risk Parameter Update | Cause: " + cause + " | Effect: " + effect); } //+------------------------------------------------------------------+ //| Update chart with news information | //+------------------------------------------------------------------+ void UpdateChartNewsInfo() { string news_text = "News Impact: "; if(ArraySize(high_impact_events) > 0) { string cause_effect; double news_factor = GetNewsAdjustedPositionSize(cause_effect); news_text += DoubleToString(news_factor * 100, 0) + "% position size\n"; news_text += "Next quiet period until: " + TimeToString(news_quiet_period_until) + "\n"; for(int i = 0; i < MathMin(ArraySize(high_impact_events), 3); i++) { news_text += "• " + high_impact_events[i] + "\n"; } if(ArraySize(high_impact_events) > 3) { news_text += "• ... and " + IntegerToString(ArraySize(high_impact_events) - 3) + " more\n"; } // Add Villahermosa analysis summary string analysis = VillahermosaNewsImpactAnalysis(); // Extract the first line for concise display int line_end = StringFind(analysis, "|"); if(line_end > 0) { news_text += "Analysis: " + StringSubstr(analysis, 0, line_end) + "\n"; } } else { news_text += "None detected\n"; news_text += "Normal trading conditions\n"; } // Create or update the news information object on chart if(ObjectFind(0, "NewsInfo") < 0) { ObjectCreate(0, "NewsInfo", OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, "NewsInfo", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(0, "NewsInfo", OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, "NewsInfo", OBJPROP_YDISTANCE, 50); ObjectSetInteger(0, "NewsInfo", OBJPROP_COLOR, clrWhite); ObjectSetInteger(0, "NewsInfo", OBJPROP_BACK, false); ObjectSetInteger(0, "NewsInfo", OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, "NewsInfo", OBJPROP_SELECTABLE, false); } // Set the text and adjust background color based on news impact ObjectSetString(0, "NewsInfo", OBJPROP_TEXT, news_text); // Change background color based on news impact level if(ArraySize(high_impact_events) > 0) { if(news_position_size_factor < 0.5) ObjectSetInteger(0, "NewsInfo", OBJPROP_BGCOLOR, clrRed); else if(news_position_size_factor < 0.8) ObjectSetInteger(0, "NewsInfo", OBJPROP_BGCOLOR, clrOrange); else ObjectSetInteger(0, "NewsInfo", OBJPROP_BGCOLOR, clrGreen); } else { ObjectSetInteger(0, "NewsInfo", OBJPROP_BGCOLOR, clrDarkGreen); } // Update the object to ensure it's visible ObjectSetInteger(0, "NewsInfo", OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS); } //+------------------------------------------------------------------+ //| Calculate Market Volatility Ratio (complete implementation) | //+------------------------------------------------------------------+ double CalculateMarketVolatility(string &cause_effect) { string volatility_cause = ""; string volatility_effect = ""; string volatility_recommendation = ""; double volatility_ratio = 1.0; // Get current ATR value double atr_values[21]; // Get 21 values to calculate 20-period average + current int copied = CopyBuffer(atr_handle, 0, 0, 21, atr_values); if(copied < 21) { volatility_cause = "Insufficient ATR data available (only " + IntegerToString(copied) + " values instead of 21)"; volatility_effect = "Cannot calculate accurate volatility ratio"; volatility_recommendation = "Using default ratio of 1.0 until sufficient data is collected"; if(cause_effect != "") { cause_effect = "Volatility Analysis | Cause: " + volatility_cause + " | Effect: " + volatility_effect + " | Recommendation: " + volatility_recommendation; } return 1.0; } // Calculate average of the last 20 periods (excluding current) double atr_sum = 0; for(int i = 1; i < 21; i++) atr_sum += atr_values[i]; double atr_avg = atr_sum / 20; double current_atr = atr_values[0]; if(atr_avg <= 0) { volatility_cause = "Average ATR value is zero or negative (unusual market conditions)"; volatility_effect = "Volatility ratio calculation impossible due to invalid denominator"; volatility_recommendation = "Using default ratio of 1.0 and monitoring market conditions"; if(cause_effect != "") { cause_effect = "Volatility Analysis | Cause: " + volatility_cause + " | Effect: " + volatility_effect + " | Recommendation: " + volatility_recommendation; } return 1.0; } volatility_ratio = current_atr / atr_avg; // Determine cause based on volatility ratio if(volatility_ratio > 2.0) { volatility_cause = "Extreme volatility detected: Current ATR (" + DoubleToString(current_atr, 5) + ") is " + DoubleToString(volatility_ratio, 2) + "x the 20-period average (" + DoubleToString(atr_avg, 5) + ")"; volatility_effect = "Market experiencing unusually high volatility, likely due to news events, " + "economic data releases, or significant market sentiment shifts"; volatility_recommendation = "Reduce position sizes by 30-50% to account for increased risk of " + "slippage and unpredictable price movements"; } else if(volatility_ratio > 1.5) { volatility_cause = "Elevated volatility detected: Current ATR (" + DoubleToString(current_atr, 5) + ") is " + DoubleToString(volatility_ratio, 2) + "x the 20-period average (" + DoubleToString(atr_avg, 5) + ")"; volatility_effect = "Market volatility above normal levels, potentially indicating upcoming " + "market moves or increased uncertainty"; volatility_recommendation = "Reduce position sizes by 15-30% and consider widening stop losses " + "to avoid being stopped out by normal volatility"; } else if(volatility_ratio > 1.2) { volatility_cause = "Slightly elevated volatility: Current ATR (" + DoubleToString(current_atr, 5) + ") is " + DoubleToString(volatility_ratio, 2) + "x the 20-period average (" + DoubleToString(atr_avg, 5) + ")"; volatility_effect = "Modest increase in volatility, possibly indicating normal market fluctuations " + "or early stages of a volatility expansion"; volatility_recommendation = "Maintain normal position sizing but monitor for further volatility increases"; } else if(volatility_ratio < 0.8) { volatility_cause = "Low volatility detected: Current ATR (" + DoubleToString(current_atr, 5) + ") is " + DoubleToString(volatility_ratio, 2) + "x the 20-period average (" + DoubleToString(atr_avg, 5) + ")"; volatility_effect = "Market experiencing unusually low volatility, potentially indicating consolidation " + "or lack of market direction"; volatility_recommendation = "Consider increasing position sizes by 10-20% to capitalize on potential " + "breakout opportunities, but be prepared for volatility expansion"; } else if(volatility_ratio < 0.5) { volatility_cause = "Extremely low volatility detected: Current ATR (" + DoubleToString(current_atr, 5) + ") is " + DoubleToString(volatility_ratio, 2) + "x the 20-period average (" + DoubleToString(atr_avg, 5) + ")"; volatility_effect = "Market in a highly compressed state, often preceding significant price movements " + "when volatility eventually expands"; volatility_recommendation = "Prepare for potential breakout by setting alerts for key support/resistance " + "levels. Consider increasing position sizes but with tight risk management"; } else { volatility_cause = "Normal volatility conditions: Current ATR (" + DoubleToString(current_atr, 5) + ") is " + DoubleToString(volatility_ratio, 2) + "x the 20-period average (" + DoubleToString(atr_avg, 5) + ")"; volatility_effect = "Market volatility within normal expected range, indicating stable trading conditions"; volatility_recommendation = "Continue with standard position sizing and risk management protocols"; } // Add trend context to volatility analysis string trend_context = AnalyzeVolatilityTrendContext(atr_values); volatility_cause += " | Trend Context: " + trend_context; // Add market session context string session_context = GetVolatilitySessionContext(); volatility_cause += " | Session: " + session_context; // Compile complete cause and effect analysis if requested if(cause_effect != "") { cause_effect = "Volatility Analysis | Cause: " + volatility_cause + " | Effect: " + volatility_effect + " | Recommendation: " + volatility_recommendation; } return volatility_ratio; } // Overload without parameter double CalculateMarketVolatility() { string dummy; return CalculateMarketVolatility(dummy); } //+------------------------------------------------------------------+ //| Analyze volatility trend context | //+------------------------------------------------------------------+ string AnalyzeVolatilityTrendContext(double &atr_values[]) { // Calculate simple linear regression to determine volatility trend double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0; int n = ArraySize(atr_values); for(int i = 0; i < n; i++) { sum_x += i; sum_y += atr_values[i]; sum_xy += i * atr_values[i]; sum_x2 += i * i; } double slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x); double trend_strength = MathAbs(slope) / (sum_y / n) * 100; string trend_analysis = ""; if(slope > 0 && trend_strength > 5) { trend_analysis = "Volatility increasing strongly (" + DoubleToString(trend_strength, 1) + "% trend strength) - expect continued expansion"; } else if(slope > 0 && trend_strength > 2) { trend_analysis = "Volatility increasing moderately (" + DoubleToString(trend_strength, 1) + "% trend strength) - monitor for further expansion"; } else if(slope > 0) { trend_analysis = "Volatility increasing slightly (" + DoubleToString(trend_strength, 1) + "% trend strength) - minimal impact expected"; } else if(slope < 0 && trend_strength > 5) { trend_analysis = "Volatility decreasing strongly (" + DoubleToString(trend_strength, 1) + "% trend strength) - expect continued compression"; } else if(slope < 0 && trend_strength > 2) { trend_analysis = "Volatility decreasing moderately (" + DoubleToString(trend_strength, 1) + "% trend strength) - monitor for potential breakout"; } else if(slope < 0) { trend_analysis = "Volatility decreasing slightly (" + DoubleToString(trend_strength, 1) + "% trend strength) - minimal impact expected"; } else { trend_analysis = "Volatility trend is flat (" + DoubleToString(trend_strength, 1) + "% trend strength) - stable conditions"; } return trend_analysis; } //+------------------------------------------------------------------+ //| Get volatility session context | //+------------------------------------------------------------------+ string GetVolatilitySessionContext() { MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; if((hour >= 8 && hour <= 12)) return "London Session (Typically higher volatility)"; if((hour >= 13 && hour <= 17)) return "New York Session (Typically highest volatility)"; if((hour >= 1 && hour <= 4)) return "Asian Session (Typically lower volatility)"; if(hour >= 21 || hour <= 5) return "Low Liquidity Session (Typically erratic volatility)"; return "Standard Trading Hours (Typical volatility)"; } //+------------------------------------------------------------------+ //| Villahermosa Volatility Impact Analysis | //| Comprehensive cause & effect analysis of volatility conditions | //+------------------------------------------------------------------+ string VillahermosaVolatilityImpactAnalysis(double volatility_ratio) { string analysis = "Villahermosa Volatility Impact Analysis: "; string primary_cause = ""; string market_effects = ""; string trading_effects = ""; string risk_assessment = ""; string strategic_recommendation = ""; // Primary cause analysis if(volatility_ratio > 2.0) { primary_cause = "Extreme volatility regime detected (" + DoubleToString(volatility_ratio, 2) + "x normal volatility). Typically caused by major economic events, " + "central bank announcements, or unexpected news developments"; market_effects = "Price movements become erratic and unpredictable. Spreads widen significantly " + "(often 2-3x normal). Slippage increases substantially on market orders"; trading_effects = "Breakout strategies may experience false signals. Trend-following systems may " + "experience whipsaw. Risk per trade increases significantly due to wider stops"; risk_assessment = "High risk of slippage (3-5x normal) and failed breakouts. Position sizing should " + "be reduced by 30-50% to maintain consistent risk exposure"; strategic_recommendation = "Villahermosa Protocol: Reduce position sizes by 40%. Avoid entering new " + "positions during peak volatility. Focus on preserving capital until " + "volatility returns to normal levels"; } else if(volatility_ratio > 1.5) { primary_cause = "High volatility regime detected (" + DoubleToString(volatility_ratio, 2) + "x normal volatility). Often precedes significant market moves or occurs during " + "periods of market uncertainty"; market_effects = "Price movements become more pronounced but somewhat predictable. Spreads widen " + "(1.5-2x normal). Slippage increases on market orders"; trading_effects = "Trend-following systems may perform well if volatility expansion aligns with " + "trend direction. Breakout strategies require wider filters to avoid false signals"; risk_assessment = "Moderate risk of slippage (2-3x normal). Position sizing should be reduced by " + "20-30% to account for increased volatility"; strategic_recommendation = "Villahermosa Protocol: Reduce position sizes by 25%. Favor trend-following " + "over mean-reversion strategies. Consider widening stop losses by 25% to " + "avoid premature stopouts"; } else if(volatility_ratio > 1.2) { primary_cause = "Elevated volatility regime detected (" + DoubleToString(volatility_ratio, 2) + "x normal volatility). Typical during active trading sessions or early stages of " + "volatility expansion"; market_effects = "Slightly increased price movements but generally orderly. Minimal spread widening. " + "Slight increase in slippage possible during fast markets"; trading_effects = "Most trading strategies perform normally. May provide better opportunities for " + "breakout traders without excessive false signals"; risk_assessment = "Slightly elevated risk. Position sizing adjustments optional but recommended for " + "risk-averse traders"; strategic_recommendation = "Villahermosa Protocol: Maintain normal position sizing. Monitor for further " + "volatility increases. Consider small (10%) position size reduction if " + "volatility continues to rise"; } else if(volatility_ratio < 0.8) { primary_cause = "Low volatility regime detected (" + DoubleToString(volatility_ratio, 2) + "x normal volatility). Often indicates market consolidation, lack of direction, " + "or low participation"; market_effects = "Price movements become constrained and range-bound. Spreads may tighten. " + "Slippage decreases on market orders"; trading_effects = "Trend-following systems may underperform. Mean-reversion and range-bound " + "strategies may excel. Breakouts become less reliable but more significant when they occur"; risk_assessment = "Lower than normal risk per trade due to smaller price movements. Position " + "sizing can be increased by 10-20% to maintain expected returns"; strategic_recommendation = "Villahermosa Protocol: Increase position sizes by 15%. Focus on " + "range-bound strategies. Prepare for eventual volatility expansion " + "by setting alerts on key support/resistance levels"; } else if(volatility_ratio < 0.5) { primary_cause = "Extremely low volatility regime detected (" + DoubleToString(volatility_ratio, 2) + "x normal volatility). Often precedes significant market moves as volatility " + "tends to mean-revert explosively"; market_effects = "Price movements become minimal and highly predictable. Spreads may tighten " + "significantly. Almost no slippage on market orders"; trading_effects = "Most trend-following systems struggle. Mean-reversion strategies may work " + "well but with small profits. Breakout strategies require patience but can " + "be highly profitable when volatility expands"; risk_assessment = "Very low risk per trade due to minimal price movements. Position sizing can " + "be increased by 20-30% but with caution for eventual volatility expansion"; strategic_recommendation = "Villahermosa Protocol: Increase position sizes by 25%. Focus on " + "preparing for volatility expansion rather than current conditions. " + "Set buy-stop and sell-stop orders outside consolidation ranges to " + "capture breakouts"; } else { primary_cause = "Normal volatility regime detected (" + DoubleToString(volatility_ratio, 2) + "x normal volatility). Market conditions are stable and predictable"; market_effects = "Price movements follow typical patterns with expected ranges. Spreads are normal. " + "Slippage is minimal during normal market conditions"; trading_effects = "All trading strategies should perform as expected. No special adjustments needed " + "for volatility conditions"; risk_assessment = "Normal risk environment. Standard position sizing and risk management protocols " + "are appropriate"; strategic_recommendation = "Villahermosa Protocol: Maintain standard trading approach. Continue " + "with normal position sizing and risk management. Monitor for changes " + "in volatility regime"; } // Compile comprehensive analysis analysis = "Villahermosa Volatility Impact Analysis | " + "Primary Cause: " + primary_cause + " | " + "Market Effects: " + market_effects + " | " + "Trading Effects: " + trading_effects + " | " + "Risk Assessment: " + risk_assessment + " | " + "Strategic Recommendation: " + strategic_recommendation; return analysis; } //+------------------------------------------------------------------+ //| Calculate position size factor based on volatility | //+------------------------------------------------------------------+ double CalculateVolatilityPositionFactor(double volatility_ratio) { if(volatility_ratio > 2.0) return 0.6; // 40% reduction if(volatility_ratio > 1.5) return 0.75; // 25% reduction if(volatility_ratio > 1.2) return 0.9; // 10% reduction if(volatility_ratio < 0.5) return 1.25; // 25% increase if(volatility_ratio < 0.8) return 1.15; // 15% increase return 1.0; // No adjustment } //+------------------------------------------------------------------+ //| Update chart with volatility information | //+------------------------------------------------------------------+ void UpdateChartVolatilityInfo(double ratio, string analysis) { string volatility_text = "Volatility Ratio: " + DoubleToString(ratio, 2) + "x\n"; volatility_text += "Position Size Factor: " + DoubleToString(volatility_position_factor * 100, 0) + "%\n"; // Extract the primary cause from the analysis for display int cause_start = StringFind(analysis, "Cause: "); if(cause_start >= 0) { cause_start += 7; // Move past "Cause: " int cause_end = StringFind(analysis, " |", cause_start); if(cause_end > cause_start) { string primary_cause = StringSubstr(analysis, cause_start, cause_end - cause_start); volatility_text += "Condition: " + primary_cause + "\n"; } } // This would be integrated with your existing chart update function // For example: ObjectSetString(0, "VolatilityInfo", OBJPROP_TEXT, volatility_text); } //+------------------------------------------------------------------+ //| Get minimum lot size | //+------------------------------------------------------------------+ double MinLot() { double min_lot; SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_MIN, min_lot); return min_lot; } //+------------------------------------------------------------------+ //| Normalize lot size to broker's requirements | //+------------------------------------------------------------------+ double NormalizeLots(double lots) { double min_lot, max_lot, lot_step; // Get symbol trade parameters if(!SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_MIN, min_lot) || !SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_MAX, max_lot) || !SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_STEP, lot_step)) { WriteLog("Error getting lot size parameters for " + InpSymbol); return 0.0; } // Ensure lots is within minimum and maximum limits if(lots < min_lot) return 0.0; if(lots > max_lot) lots = max_lot; // Normalize to the nearest step size lots = MathRound(lots / lot_step) * lot_step; return MathMax(min_lot, MathMin(max_lot, lots)); } //+------------------------------------------------------------------+ //| Check if current time is within trading hours | //+------------------------------------------------------------------+ bool IsTradeTime() { datetime current = TimeCurrent(); MqlDateTime curr_time; TimeToStruct(current, curr_time); string start_times[]; string end_times[]; if(StringSplit(InpTradeStartTime, ':', start_times) < 2) { WriteLog("Invalid start time format: " + InpTradeStartTime); return true; } if(StringSplit(InpTradeEndTime, ':', end_times) < 2) { WriteLog("Invalid end time format: " + InpTradeEndTime); return true; } int start_hour = (int)StringToInteger(start_times[0]); int start_min = (int)StringToInteger(start_times[1]); int end_hour = (int)StringToInteger(end_times[0]); int end_min = (int)StringToInteger(end_times[1]); int current_minute = curr_time.hour * 60 + curr_time.min; int start_minute = start_hour * 60 + start_min; int end_minute = end_hour * 60 + end_min; return (current_minute >= start_minute && current_minute <= end_minute); } //+------------------------------------------------------------------+ //| Check if current day is a trading day | //+------------------------------------------------------------------+ bool IsTradingDay(int day_of_week) { switch(day_of_week) { case 1: return InpTradeOnMonday; // Monday case 2: return InpTradeOnTuesday; // Tuesday case 3: return InpTradeOnWednesday; // Wednesday case 4: return InpTradeOnThursday; // Thursday case 5: return InpTradeOnFriday; // Friday default: return false; // Weekend } } //+------------------------------------------------------------------+ //| Write to log file | //+------------------------------------------------------------------+ void WriteLog(string message) { if(!InpEnableLogging) return; // Ensure log_file_name is initialized if(log_file_name == "") { // Fallback initialization if somehow we reach here without OnInit log_file_name = "EURUSD_Donchian_Log_" + IntegerToString(GetTickCount()) + ".txt"; } string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS); string log_entry = timestamp + " - " + message; // Write to experts log Print(log_entry); // Write to file int file_handle = FileOpen(log_file_name, FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI); if(file_handle != INVALID_HANDLE) { FileSeek(file_handle, 0, SEEK_END); FileWrite(file_handle, log_entry); FileClose(file_handle); } } //+------------------------------------------------------------------+ //| Update account high-water mark | //+------------------------------------------------------------------+ void UpdateAccountHigh() { double current_value = AccountInfoDouble(InpUseEquityInsteadOfBalance ? ACCOUNT_EQUITY : ACCOUNT_BALANCE); MqlDateTime today, last_update; TimeCurrent(today); TimeToStruct(last_high_update, last_update); if(today.day != last_update.day || today.mon != last_update.mon || today.year != last_update.year) { account_high = current_value; last_high_update = TimeCurrent(); WriteLog("New day - account high reset to: " + DoubleToString(account_high, 2)); } else if(current_value > account_high) { account_high = current_value; last_high_update = TimeCurrent(); WriteLog("New account high: " + DoubleToString(account_high, 2) + " (" + (InpUseEquityInsteadOfBalance ? "Equity" : "Balance") + ")"); } } //+------------------------------------------------------------------+ //| Calculate current drawdown percentage (updated implementation) | //+------------------------------------------------------------------+ double CalculateCurrentDrawdown(string &analysis) { double current_value = AccountInfoDouble(InpUseEquityInsteadOfBalance ? ACCOUNT_EQUITY : ACCOUNT_BALANCE); if(account_high <= 0.0) { if(analysis != "") analysis = "Account high is zero or negative"; return 0.0; } double drawdown = (account_high - current_value) / account_high; if(analysis != "") analysis = "Current drawdown: " + DoubleToString(drawdown*100, 2) + "%"; return drawdown; } // Overload without parameter double CalculateCurrentDrawdown() { string dummy; return CalculateCurrentDrawdown(dummy); } //+------------------------------------------------------------------+ //| Calculate peak-to-valley drawdown in absolute terms | //+------------------------------------------------------------------+ double CalculateAbsoluteDrawdown() { double current_value = AccountInfoDouble(InpUseEquityInsteadOfBalance ? ACCOUNT_EQUITY : ACCOUNT_BALANCE); double initial_balance = AccountInfoDouble(ACCOUNT_BALANCE); // Always use balance for absolute drawdown return MathMax(0, initial_balance - current_value); } //+------------------------------------------------------------------+ //| Calculate maximum adverse excursion for current positions | //+------------------------------------------------------------------+ double CalculateMaximumAdverseExcursion() { double max_excursion = 0.0; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == InpSymbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); double price_excursion = 0.0; if(type == POSITION_TYPE_BUY) price_excursion = (open_price - current_price) / open_price; // Negative for profitable positions else price_excursion = (current_price - open_price) / open_price; // Negative for profitable positions if(price_excursion > max_excursion) max_excursion = price_excursion; } } return max_excursion; } //+------------------------------------------------------------------+ //| Calculate recovery factor based on drawdown and profit | //+------------------------------------------------------------------+ double CalculateRecoveryFactor() { double net_profit = 0.0; double max_drawdown = 0.0; // Get historical trades for recovery factor calculation datetime start_time = TimeCurrent() - 30*24*60*60; // Last 30 days HistorySelect(start_time, TimeCurrent()); int total_deals = HistoryDealsTotal(); for(int i = 0; i < total_deals; i++) { ulong ticket = HistoryDealGetTicket(i); if(ticket > 0 && HistoryDealGetString(ticket, DEAL_SYMBOL) == InpSymbol && HistoryDealGetInteger(ticket, DEAL_MAGIC) == InpMagicNumber) { double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT); net_profit += profit; // Simple max drawdown calculation from history // In a real implementation, you'd track equity curve if(profit < 0 && MathAbs(profit) > max_drawdown) max_drawdown = MathAbs(profit); } } if(max_drawdown == 0.0) return 100.0; // No drawdown return net_profit / max_drawdown; } //+------------------------------------------------------------------+ //| Villahermosa Drawdown Analysis | //+------------------------------------------------------------------+ string VillahermosaDrawdownAnalysis(double current_drawdown) { string analysis = "Villahermosa Drawdown Analysis: "; string drawdown_cause = ""; string drawdown_effect = ""; string recovery_strategy = ""; // Analyze drawdown cause if(current_drawdown >= InpPauseAtDD) { drawdown_cause = "Significant drawdown (" + DoubleToString(current_drawdown*100, 1) + "%) exceeding pause threshold (" + DoubleToString(InpPauseAtDD*100, 1) + "%)"; drawdown_effect = "Trading paused to prevent further capital erosion"; recovery_strategy = "Wait for drawdown to reduce to " + DoubleToString(InpResumeDD*100, 1) + "% before resuming trading with reduced position sizes"; } else if(current_drawdown >= InpReduceAtDD) { drawdown_cause = "Elevated drawdown (" + DoubleToString(current_drawdown*100, 1) + "%) exceeding reduction threshold (" + DoubleToString(InpReduceAtDD*100, 1) + "%)"; drawdown_effect = "Position sizes reduced by 50% to limit risk exposure"; recovery_strategy = "Continue trading with reduced size until drawdown decreases below " + DoubleToString(InpReduceAtDD*100, 1) + "%"; } else if(current_drawdown > 0.02) // More than 2% { drawdown_cause = "Moderate drawdown (" + DoubleToString(current_drawdown*100, 1) + "%) within acceptable limits"; drawdown_effect = "Normal trading continues with standard position sizing"; recovery_strategy = "Monitor position performance and adjust stops if necessary"; } else { drawdown_cause = "Minimal drawdown (" + DoubleToString(current_drawdown*100, 1) + "%) - normal market fluctuation"; drawdown_effect = "No action needed - continue standard trading approach"; recovery_strategy = "Maintain current strategy as drawdown is within expected parameters"; } // Add market context to analysis string market_context = AnalyzeMarketContext(); string volatility_context = AnalyzeVolatilityContext(); // Compile comprehensive analysis analysis += "Cause: " + drawdown_cause + " | Effect: " + drawdown_effect + " | Recovery: " + recovery_strategy + " | Market: " + market_context + " | Volatility: " + volatility_context; return analysis; } //+------------------------------------------------------------------+ //| Update chart with drawdown information | //+------------------------------------------------------------------+ void UpdateChartDrawdownInfo(double drawdown) { string comment = StringFormat("Drawdown: %.2f%%\n", drawdown*100) + StringFormat("Account High: %.2f %s\n", account_high, InpUseEquityInsteadOfBalance ? "Equity" : "Balance") + StringFormat("Trading Status: %s\n", trading_paused ? "PAUSED" : "ACTIVE") + StringFormat("Position Sizing: %s\n", (drawdown >= InpReduceAtDD) ? "REDUCED" : "NORMAL") + StringFormat("Time: %s", TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES)); Comment(comment); } //+------------------------------------------------------------------+ //| Calculate Villahermosa volatility adjustment factor | //+------------------------------------------------------------------+ double CalculateVillahermosaVolAdjustment(ENUM_ORDER_TYPE type, double atr) { double factor = 1.0; // Default factor // 1. Volatility regime analysis double atr_values[50]; if(CopyBuffer(atr_handle, 0, 0, 50, atr_values) >= 50) { double atr_sum = 0; for(int i = 0; i < 50; i++) atr_sum += atr_values[i]; double atr_avg = atr_sum / 50; // Calculate volatility ratio (current vs average) double vol_ratio = atr / atr_avg; // Villahermosa volatility adjustment rules if(vol_ratio > 2.0) { // Extreme volatility - reduce position size factor *= 0.7; WriteLog("Villahermosa: Extreme volatility detected. Reducing position size by 30%"); } else if(vol_ratio > 1.5) { // High volatility - moderate reduction factor *= 0.85; WriteLog("Villahermosa: High volatility detected. Reducing position size by 15%"); } else if(vol_ratio < 0.5) { // Very low volatility - moderate increase factor *= 1.15; WriteLog("Villahermosa: Low volatility detected. Increasing position size by 15%"); } } // 2. Volatility trend analysis double atr_trend[5]; if(CopyBuffer(atr_handle, 0, 0, 5, atr_trend) >= 5) { // Simple linear regression to determine ATR trend double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0; for(int i = 0; i < 5; i++) { sum_x += i; sum_y += atr_trend[i]; sum_xy += i * atr_trend[i]; sum_x2 += i * i; } double slope = (5 * sum_xy - sum_x * sum_y) / (5 * sum_x2 - sum_x * sum_x); // Adjust position size based on volatility trend if(slope > 0) { // Volatility increasing - reduce position size factor *= 0.9; WriteLog("Villahermosa: Volatility increasing. Reducing position size by 10%"); } else if(slope < 0) { // Volatility decreasing - increase position size factor *= 1.1; WriteLog("Villahermosa: Volatility decreasing. Increasing position size by 10%"); } } return MathMax(factor, 0.5); // Ensure minimum factor of 0.5 } //+------------------------------------------------------------------+ //| Calculate cause-based position size adjustment | //| Implements Villahermosa methodology for position sizing | //+------------------------------------------------------------------+ double CalculateCauseBasedPositionSize(ENUM_ORDER_TYPE type) { double factor = 1.0; // Default factor (no adjustment) // Get current market conditions for cause analysis MqlRates current_rate; if(CopyRates(InpSymbol, InpTF, 0, 1, ¤t_rate) < 1) { WriteLog("Error copying rates in CalculateCauseBasedPositionSize: " + IntegerToString(GetLastError())); return factor; // Return default factor on error } // Calculate Donchian channels for cause determination double upper_band = Donchian(InpSymbol, InpTF, InpDonchianEntry, MODE_HIGH, 1); double lower_band = Donchian(InpSymbol, InpTF, InpDonchianEntry, MODE_LOW, 1); if(upper_band == 0 || lower_band == 0) { WriteLog("Error calculating Donchian channels in CalculateCauseBasedPositionSize"); return factor; // Return default factor on error } // 1. Primary Cause: Donchian Breakout Strength if(type == ORDER_TYPE_BUY) { // Calculate how far above the upper band we are (as a percentage) double breakout_strength = (current_rate.close - upper_band) / upper_band * 100; if(breakout_strength > 0.1) // Strong breakout (0.1% above upper band) { factor *= 1.25; // Increase size for strong breakouts WriteLog("Villahermosa: Strong bullish breakout detected (" + DoubleToString(breakout_strength, 2) + "%). Increasing position size by 25%"); } else if(breakout_strength > 0) // Weak breakout { factor *= 1.1; // Moderate increase for weak breakouts WriteLog("Villahermosa: Weak bullish breakout detected (" + DoubleToString(breakout_strength, 2) + "%). Increasing position size by 10%"); } } else if(type == ORDER_TYPE_SELL) { // Calculate how far below the lower band we are (as a percentage) double breakout_strength = (lower_band - current_rate.close) / lower_band * 100; if(breakout_strength > 0.1) // Strong breakout (0.1% below lower band) { factor *= 1.25; // Increase size for strong breakouts WriteLog("Villahermosa: Strong bearish breakout detected (" + DoubleToString(breakout_strength, 2) + "%). Increasing position size by 25%"); } else if(breakout_strength > 0) // Weak breakout { factor *= 1.1; // Moderate increase for weak breakouts WriteLog("Villahermosa: Weak bearish breakout detected (" + DoubleToString(breakout_strength, 2) + "%). Increasing position size by 10%"); } } // 2. Volatility-based Adjustment double atr_values[20]; if(CopyBuffer(atr_handle, 0, 0, 20, atr_values) >= 20) { double current_atr = atr_values[0]; double atr_sum = 0; for(int i = 0; i < 20; i++) atr_sum += atr_values[i]; double atr_avg = atr_sum / 20; if(atr_avg > 0) { double vol_ratio = current_atr / atr_avg; // Adjust position size based on volatility regime if(vol_ratio > 2.0) { factor *= 0.7; // Significant reduction in extreme volatility WriteLog("Villahermosa: Extreme volatility detected (ratio: " + DoubleToString(vol_ratio, 2) + "). Reducing position size by 30%"); } else if(vol_ratio > 1.5) { factor *= 0.85; // Moderate reduction in high volatility WriteLog("Villahermosa: High volatility detected (ratio: " + DoubleToString(vol_ratio, 2) + "). Reducing position size by 15%"); } else if(vol_ratio < 0.5) { factor *= 1.15; // Increase in low volatility WriteLog("Villahermosa: Low volatility detected (ratio: " + DoubleToString(vol_ratio, 2) + "). Increasing position size by 15%"); } } } // 3. Trend Alignment Analysis MqlRates rates[50]; if(CopyRates(InpSymbol, InpTF, 0, 50, rates) >= 50) { // Calculate 20-period and 50-period simple moving averages double sum_20 = 0, sum_50 = 0; for(int i = 0; i < 50; i++) { if(i < 20) sum_20 += rates[i].close; sum_50 += rates[i].close; } double ma_20 = sum_20 / 20; double ma_50 = sum_50 / 50; // Strong trend alignment (both MAs aligned with trade direction) if((type == ORDER_TYPE_BUY && current_rate.close > ma_20 && ma_20 > ma_50) || (type == ORDER_TYPE_SELL && current_rate.close < ma_20 && ma_20 < ma_50)) { factor *= 1.2; // Increase size for strong trend alignment WriteLog("Villahermosa: Strong trend alignment detected. Increasing position size by 20%"); } // Weak trend alignment (price aligned but MAs not fully aligned) else if((type == ORDER_TYPE_BUY && current_rate.close > ma_20) || (type == ORDER_TYPE_SELL && current_rate.close < ma_20)) { factor *= 1.1; // Moderate increase for weak trend alignment WriteLog("Villahermosa: Weak trend alignment detected. Increasing position size by 10%"); } // Counter-trend (price against the shorter-term MA) else { factor *= 0.8; // Reduce size for counter-trend trades WriteLog("Villahermosa: Counter-trend detected. Reducing position size by 20%"); } } // 4. Time-based Session Analysis MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; // Increase size during high liquidity sessions if((hour >= 8 && hour <= 12) || (hour >= 14 && hour <= 17)) { factor *= 1.15; // London/NY overlap and main sessions WriteLog("Villahermosa: High liquidity session detected. Increasing position size by 15%"); } // Decrease size during low liquidity sessions else if(hour >= 21 || hour <= 5) { factor *= 0.7; // Asian session and late NY WriteLog("Villahermosa: Low liquidity session detected. Reducing position size by 30%"); } // 5. Drawdown-based Risk Reduction double drawdown = CalculateCurrentDrawdown(); if(drawdown >= InpPauseAtDD) { factor = 0; // No new positions at maximum drawdown WriteLog("Villahermosa: Maximum drawdown reached. Preventing new positions"); } else if(drawdown >= InpReduceAtDD) { factor *= 0.5; // Reduce size at elevated drawdown WriteLog("Villahermosa: Elevated drawdown detected. Reducing position size by 50%"); } // Ensure factor is within reasonable bounds (0.1x to 2.0x) factor = MathMax(MathMin(factor, 2.0), 0.1); // Final log of the calculated factor WriteLog("Villahermosa: Final cause-based position size factor: " + DoubleToString(factor, 2)); return factor; } //+------------------------------------------------------------------+ //| Calculate Villahermosa risk adjustment factor | //| with Cause & Effect analysis | //+------------------------------------------------------------------+ double CalculateVillahermosaRiskAdjustment(ENUM_ORDER_TYPE type, string trade_cause, double atr) { double factor = 1.0; // Default factor string cause_effect_report = "Villahermosa Risk Analysis: "; // 1. Market Condition Analysis - Drawdown Effect double drawdown = CalculateCurrentDrawdown(); string drawdown_cause = ""; string drawdown_effect = ""; if(drawdown >= InpPauseAtDD) { factor = 0.0; // No new positions at maximum drawdown drawdown_cause = "Drawdown exceeded maximum threshold (" + DoubleToString(InpPauseAtDD*100, 1) + "%)"; drawdown_effect = "Complete position prevention to protect capital"; cause_effect_report += "| Drawdown Cause: " + drawdown_cause + " | Effect: " + drawdown_effect; } else if(drawdown >= InpReduceAtDD) { factor *= 0.5; // Reduce position size at elevated drawdown drawdown_cause = "Drawdown exceeded reduction threshold (" + DoubleToString(InpReduceAtDD*100, 1) + "%)"; drawdown_effect = "Position size reduced by 50% to limit risk exposure"; cause_effect_report += "| Drawdown Cause: " + drawdown_cause + " | Effect: " + drawdown_effect; } else if(drawdown > 0) { drawdown_cause = "Drawdown within acceptable limits (" + DoubleToString(drawdown*100, 1) + "%)"; drawdown_effect = "No position size reduction needed"; cause_effect_report += "| Drawdown Cause: " + drawdown_cause + " | Effect: " + drawdown_effect; } // 2. Volatility-Based Risk Adjustment double atr_values[20]; if(CopyBuffer(atr_handle, 0, 0, 20, atr_values) >= 20) { double atr_sum = 0; for(int i = 0; i < 20; i++) atr_sum += atr_values[i]; double atr_avg = atr_sum / 20; if(atr_avg > 0) { double vol_ratio = atr / atr_avg; string volatility_cause = ""; string volatility_effect = ""; if(vol_ratio > 2.0) { factor *= 0.6; // Significant reduction in extreme volatility volatility_cause = "Extreme volatility detected (ratio: " + DoubleToString(vol_ratio, 2) + ")"; volatility_effect = "Position size reduced by 40% to account for unpredictable market conditions"; } else if(vol_ratio > 1.8) { factor *= 0.7; // Reduce risk in high volatility volatility_cause = "High volatility detected (ratio: " + DoubleToString(vol_ratio, 2) + ")"; volatility_effect = "Position size reduced by 30% to limit risk in turbulent market"; } else if(vol_ratio > 1.5) { factor *= 0.85; // Moderate reduction in elevated volatility volatility_cause = "Elevated volatility detected (ratio: " + DoubleToString(vol_ratio, 2) + ")"; volatility_effect = "Position size reduced by 15% as precaution"; } else if(vol_ratio < 0.7) { factor *= 1.1; // Slight increase in low volatility volatility_cause = "Low volatility detected (ratio: " + DoubleToString(vol_ratio, 2) + ")"; volatility_effect = "Position size increased by 10% to capitalize on stable conditions"; } else { volatility_cause = "Normal volatility conditions (ratio: " + DoubleToString(vol_ratio, 2) + ")"; volatility_effect = "No volatility-based adjustment needed"; } cause_effect_report += "| Volatility Cause: " + volatility_cause + " | Effect: " + volatility_effect; } } // 3. Trend Confluence Analysis MqlRates rates[50]; if(CopyRates(InpSymbol, InpTF, 0, 50, rates) >= 50) { // Calculate multiple timeframes for trend confirmation double sum_20 = 0, sum_50 = 0; for(int i = 0; i < 50; i++) { if(i < 20) sum_20 += rates[i].close; sum_50 += rates[i].close; } double ma_20 = sum_20 / 20; double ma_50 = sum_50 / 50; string trend_cause = ""; string trend_effect = ""; bool bullish_trend = (rates[0].close > ma_20 && ma_20 > ma_50); bool bearish_trend = (rates[0].close < ma_20 && ma_20 < ma_50); // Check if trade direction aligns with trend if((type == ORDER_TYPE_BUY && bullish_trend) || (type == ORDER_TYPE_SELL && bearish_trend)) { factor *= 1.15; // Increase size for trend confluence trend_cause = "Trade direction aligns with strong trend"; trend_effect = "Position size increased by 15% due to favorable trend conditions"; } else if((type == ORDER_TYPE_BUY && bearish_trend) || (type == ORDER_TYPE_SELL && bullish_trend)) { factor *= 0.8; // Reduce size for counter-trend trades trend_cause = "Trade direction opposes strong trend"; trend_effect = "Position size reduced by 20% due to counter-trend conditions"; } else { trend_cause = "No clear trend direction or neutral market conditions"; trend_effect = "No trend-based adjustment applied"; } cause_effect_report += "| Trend Cause: " + trend_cause + " | Effect: " + trend_effect; } // 4. Time-Based Session Analysis MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; string session_cause = ""; string session_effect = ""; // Adjust risk based on trading session if((hour >= 8 && hour <= 12) || (hour >= 14 && hour <= 17)) { // High liquidity sessions (London/NY overlap and main sessions) factor *= 1.1; session_cause = "High liquidity trading session ("; session_cause += (hour >= 8 && hour <= 12) ? "London/NY Overlap" : "Main Session"; session_cause += ")"; session_effect = "Position size increased by 10% to capitalize on high liquidity"; } else if(hour >= 1 && hour <= 4) { // Asian session - reduced liquidity factor *= 0.8; session_cause = "Asian trading session"; session_effect = "Position size reduced by 20% due to lower liquidity"; } else if(hour >= 21 || hour <= 5) { // Late NY / Early Asian - very low liquidity factor *= 0.6; session_cause = "Low liquidity hours (Late NY/Early Asian)"; session_effect = "Position size reduced by 40% due to extremely low liquidity"; } else { session_cause = "Standard trading hours"; session_effect = "No session-based adjustment applied"; } cause_effect_report += "| Session Cause: " + session_cause + " | Effect: " + session_effect; // 5. Recent Performance Analysis int recent_trades = 0; int winning_trades = 0; datetime last_week = TimeCurrent() - 7*24*60*60; HistorySelect(last_week, TimeCurrent()); int total_history = HistoryDealsTotal(); for(int i = 0; i < total_history; i++) { ulong ticket = HistoryDealGetTicket(i); if(ticket > 0 && HistoryDealGetString(ticket, DEAL_SYMBOL) == InpSymbol && HistoryDealGetInteger(ticket, DEAL_MAGIC) == InpMagicNumber) { recent_trades++; if(HistoryDealGetDouble(ticket, DEAL_PROFIT) > 0) winning_trades++; } } string performance_cause = ""; string performance_effect = ""; if(recent_trades > 0) { double win_rate = (double)winning_trades / recent_trades; if(win_rate < 0.3) // Poor recent performance { factor *= 0.7; performance_cause = "Poor recent performance (Win rate: " + DoubleToString(win_rate*100, 1) + "%)"; performance_effect = "Position size reduced by 30% due to recent trading challenges"; } else if(win_rate > 0.7) // Excellent recent performance { factor *= 1.2; performance_cause = "Excellent recent performance (Win rate: " + DoubleToString(win_rate*100, 1) + "%)"; performance_effect = "Position size increased by 20% due to strong recent performance"; } else { performance_cause = "Average recent performance (Win rate: " + DoubleToString(win_rate*100, 1) + "%)"; performance_effect = "No performance-based adjustment applied"; } } else { performance_cause = "No recent trade history available"; performance_effect = "No performance-based adjustment applied"; } cause_effect_report += "| Performance Cause: " + performance_cause + " | Effect: " + performance_effect; // Ensure factor is within reasonable bounds (0.1x to 2.0x) factor = MathMax(MathMin(factor, 2.0), 0.1); // Final cause and effect report cause_effect_report += " | Final Risk Factor: " + DoubleToString(factor, 2); WriteLog(cause_effect_report); return factor; } //+------------------------------------------------------------------+ //| Calculate volatility-based risk adjustment | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ double CalculateVolatilityRiskAdjustment(double atr) { double factor = 1.0; string cause_effect_report = "Volatility Risk Analysis: "; // Compare current ATR to its historical average double atr_values[50]; if(CopyBuffer(atr_handle, 0, 0, 50, atr_values) >= 50) { double atr_sum = 0; for(int i = 0; i < 50; i++) atr_sum += atr_values[i]; double atr_avg = atr_sum / 50; if(atr_avg > 0) { double vol_ratio = atr / atr_avg; string volatility_cause = ""; string volatility_effect = ""; // Extreme volatility scenario if(vol_ratio > 2.0) { factor = 0.6; // Significant reduction in extreme volatility volatility_cause = "Extreme volatility detected (Current ATR: " + DoubleToString(atr, 5) + ", Ratio: " + DoubleToString(vol_ratio, 2) + "x historical average)"; volatility_effect = "Position size reduced by 40% to protect against unpredictable price swings"; } // High volatility scenario else if(vol_ratio > 1.5) { factor = 0.8; // Moderate reduction in high volatility volatility_cause = "High volatility detected (Current ATR: " + DoubleToString(atr, 5) + ", Ratio: " + DoubleToString(vol_ratio, 2) + "x historical average)"; volatility_effect = "Position size reduced by 20% to account for increased market uncertainty"; } // Low volatility scenario else if(vol_ratio < 0.5) { factor = 1.2; // Increase risk in low volatility volatility_cause = "Low volatility detected (Current ATR: " + DoubleToString(atr, 5) + ", Ratio: " + DoubleToString(vol_ratio, 2) + "x historical average)"; volatility_effect = "Position size increased by 20% to capitalize on stable market conditions"; } // Normal volatility scenario else { volatility_cause = "Normal volatility conditions (Current ATR: " + DoubleToString(atr, 5) + ", Ratio: " + DoubleToString(vol_ratio, 2) + "x historical average)"; volatility_effect = "No volatility-based adjustment needed"; } // Add volatility trend analysis string trend_cause = ""; string trend_effect = ""; // Calculate volatility trend (simple linear regression of last 10 ATR values) double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0; int trend_period = MathMin(10, 50); for(int i = 0; i < trend_period; i++) { sum_x += i; sum_y += atr_values[i]; sum_xy += i * atr_values[i]; sum_x2 += i * i; } double slope = (trend_period * sum_xy - sum_x * sum_y) / (trend_period * sum_x2 - sum_x * sum_x); double trend_strength = MathAbs(slope) / atr_avg * 100; // Analyze volatility trend if(slope > 0 && trend_strength > 5) // Volatility increasing significantly { factor *= 0.9; // Additional reduction for increasing volatility trend_cause = "Volatility is increasing (Trend strength: " + DoubleToString(trend_strength, 1) + "%)"; trend_effect = "Additional 10% reduction due to rising volatility trend"; } else if(slope < 0 && trend_strength > 5) // Volatility decreasing significantly { factor *= 1.1; // Additional increase for decreasing volatility trend_cause = "Volatility is decreasing (Trend strength: " + DoubleToString(trend_strength, 1) + "%)"; trend_effect = "Additional 10% increase due to falling volatility trend"; } else { trend_cause = "Volatility trend is stable (Trend strength: " + DoubleToString(trend_strength, 1) + "%)"; trend_effect = "No trend-based adjustment applied"; } // Add market regime analysis string regime_cause = ""; string regime_effect = ""; // Calculate volatility of volatility (standard deviation of ATR values) double sum_sq_diff = 0; for(int i = 0; i < 50; i++) { sum_sq_diff += MathPow(atr_values[i] - atr_avg, 2); } double atr_stddev = MathSqrt(sum_sq_diff / 50); double vov_ratio = atr_stddev / atr_avg; // Volatility of volatility if(vov_ratio > 0.3) // High volatility of volatility { factor *= 0.85; // Reduce size in unpredictable volatility regimes regime_cause = "High volatility of volatility detected (VoV ratio: " + DoubleToString(vov_ratio, 2) + ")"; regime_effect = "Additional 15% reduction due to unpredictable volatility regime"; } else if(vov_ratio < 0.1) // Low volatility of volatility { factor *= 1.1; // Increase size in stable volatility regimes regime_cause = "Low volatility of volatility detected (VoV ratio: " + DoubleToString(vov_ratio, 2) + ")"; regime_effect = "Additional 10% increase due to stable volatility regime"; } else { regime_cause = "Moderate volatility of volatility (VoV ratio: " + DoubleToString(vov_ratio, 2) + ")"; regime_effect = "No regime-based adjustment applied"; } // Compile comprehensive cause and effect report cause_effect_report += "Level: " + volatility_cause + " | Effect: " + volatility_effect; cause_effect_report += " | Trend: " + trend_cause + " | Effect: " + trend_effect; cause_effect_report += " | Regime: " + regime_cause + " | Effect: " + regime_effect; cause_effect_report += " | Final Factor: " + DoubleToString(factor, 2); WriteLog(cause_effect_report); } else { WriteLog("Volatility Risk Analysis: Invalid ATR average calculation"); } } else { WriteLog("Volatility Risk Analysis: Unable to retrieve ATR history data"); } return factor; } //+------------------------------------------------------------------+ //| Calculate time-based risk adjustment | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ double CalculateTimeBasedRiskAdjustment() { double factor = 1.0; string cause_effect_report = "Time-Based Risk Analysis: "; MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; int day_of_week = current_time.day_of_week; string session_cause = ""; string session_effect = ""; string day_cause = ""; string day_effect = ""; // Adjust risk based on trading session if((hour >= 8 && hour <= 12) || (hour >= 14 && hour <= 17)) { factor = 1.15; // Increase risk during London/NY overlap and main sessions session_cause = "High liquidity session detected ("; if(hour >= 8 && hour <= 12) session_cause += "London/NY Overlap: 08:00-12:00"; else session_cause += "Main Session: 14:00-17:00"; session_cause += ")"; session_effect = "Position size increased by 15% to capitalize on high liquidity and volatility"; } else if(hour >= 1 && hour <= 4) { factor = 0.7; // Reduce risk during Asian session session_cause = "Asian session detected (01:00-04:00)"; session_effect = "Position size reduced by 30% due to lower liquidity and potential false breakouts"; } else if(hour >= 21 || hour <= 5) { factor = 0.5; // Significant reduction during low liquidity hours session_cause = "Low liquidity hours detected ("; if(hour >= 21) session_cause += "21:00-24:00"; else session_cause += "00:00-05:00"; session_cause += ")"; session_effect = "Position size reduced by 50% due to extremely low liquidity and wide spreads"; } else { session_cause = "Standard trading hours (" + IntegerToString(hour) + ":00)"; session_effect = "No session-based adjustment applied"; } // Adjust risk based on day of week if(day_of_week == 5) // Friday { factor *= 0.8; // Reduce risk on Fridays day_cause = "Friday detected"; day_effect = "Position size reduced by 20% due to weekend gap risk and potential position squaring"; } else if(day_of_week == 1) // Monday { factor *= 0.9; // Slight reduction on Mondays day_cause = "Monday detected"; day_effect = "Position size reduced by 10% due to potential weekend gap fills and uncertain weekly opening"; } else if(day_of_week == 4) // Thursday { factor *= 1.05; // Slight increase on Thursdays day_cause = "Thursday detected"; day_effect = "Position size increased by 5% to capitalize on mid-week trend continuity"; } else { day_cause = "Standard trading day (" + GetDayOfWeekName(day_of_week) + ")"; day_effect = "No day-based adjustment applied"; } // Add market session transition analysis string transition_cause = ""; string transition_effect = ""; // Check if we're in a session transition period (first or last hour of session) if((hour == 8 || hour == 12 || hour == 14 || hour == 17 || hour == 21 || hour == 1 || hour == 5)) { factor *= 0.9; // Slight reduction during session transitions transition_cause = "Session transition period detected (" + IntegerToString(hour) + ":00)"; transition_effect = "Position size reduced by 10% due to potential volatility spikes during session changes"; } // Compile comprehensive cause and effect report cause_effect_report += "Session: " + session_cause + " | Effect: " + session_effect; cause_effect_report += " | Day: " + day_cause + " | Effect: " + day_effect; if(transition_cause != "") { cause_effect_report += " | Transition: " + transition_cause + " | Effect: " + transition_effect; } cause_effect_report += " | Final Factor: " + DoubleToString(factor, 2); WriteLog(cause_effect_report); return factor; } //+------------------------------------------------------------------+ //| Helper function to get day of week name | //+------------------------------------------------------------------+ string GetDayOfWeekName(int day_of_week) { switch(day_of_week) { case 0: return "Sunday"; case 1: return "Monday"; case 2: return "Tuesday"; case 3: return "Wednesday"; case 4: return "Thursday"; case 5: return "Friday"; case 6: return "Saturday"; default: return "Unknown"; } } //+------------------------------------------------------------------+ //| Calculate position concentration risk adjustment | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ double CalculateConcentrationRiskAdjustment() { double factor = 1.0; string cause_effect_report = "Position Concentration Analysis: "; // Count current open positions and calculate total exposure int position_count = 0; double total_risk_exposure = 0.0; double account_equity = AccountInfoDouble(ACCOUNT_EQUITY); for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == InpSymbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { position_count++; // Calculate risk exposure for this position double position_volume = PositionGetDouble(POSITION_VOLUME); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); double price_diff = MathAbs(current_price - open_price); double tick_value = SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_VALUE_LOT); double tick_size = SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_SIZE); if(tick_size > 0) { double risk_exposure = (price_diff / tick_size) * tick_value * position_volume; total_risk_exposure += risk_exposure; } } } // Calculate risk as percentage of equity double risk_percentage = (account_equity > 0) ? (total_risk_exposure / account_equity) * 100 : 0; string position_cause = ""; string position_effect = ""; string risk_cause = ""; string risk_effect = ""; // Adjust risk based on position concentration if(position_count >= 3) { factor = 0.7; // Reduce risk if already have multiple positions position_cause = "Multiple positions detected (" + IntegerToString(position_count) + " open positions)"; position_effect = "Position size reduced by 30% to avoid overconcentration"; } else if(position_count >= 1) { factor = 0.85; // Moderate reduction with existing positions position_cause = "Existing position detected (" + IntegerToString(position_count) + " open position)"; position_effect = "Position size reduced by 15% to manage overall exposure"; } else { position_cause = "No existing positions detected"; position_effect = "No position-based adjustment applied"; } // Additional risk-based adjustment based on total exposure if(risk_percentage > 5.0) // More than 5% equity at risk { factor *= 0.8; // Additional reduction for high risk exposure risk_cause = "High risk exposure detected (" + DoubleToString(risk_percentage, 1) + "% of equity at risk)"; risk_effect = "Additional 20% reduction to limit overall portfolio risk"; } else if(risk_percentage > 2.0) // More than 2% equity at risk { factor *= 0.9; // Moderate reduction for elevated risk exposure risk_cause = "Elevated risk exposure detected (" + DoubleToString(risk_percentage, 1) + "% of equity at risk)"; risk_effect = "Additional 10% reduction to manage portfolio risk"; } else if(risk_percentage > 0) { risk_cause = "Moderate risk exposure (" + DoubleToString(risk_percentage, 1) + "% of equity at risk)"; risk_effect = "No additional risk-based adjustment applied"; } // Consider correlation risk (placeholder for more advanced correlation analysis) string correlation_cause = ""; string correlation_effect = ""; // In a more advanced implementation, we would analyze correlation between positions if(position_count > 1) { // Simple assumption: multiple positions on same symbol are highly correlated factor *= 0.9; // Additional reduction for correlated positions correlation_cause = "Highly correlated positions detected (same symbol)"; correlation_effect = "Additional 10% reduction to account for correlation risk"; } else { correlation_cause = "No correlation risk detected"; correlation_effect = "No correlation-based adjustment applied"; } // Compile comprehensive cause and effect report cause_effect_report += "Positions: " + position_cause + " | Effect: " + position_effect; if(risk_cause != "") { cause_effect_report += " | Risk: " + risk_cause + " | Effect: " + risk_effect; } if(correlation_cause != "") { cause_effect_report += " | Correlation: " + correlation_cause + " | Effect: " + correlation_effect; } cause_effect_report += " | Final Factor: " + DoubleToString(factor, 2); WriteLog(cause_effect_report); return factor; } //+------------------------------------------------------------------+ //| Calculate Villahermosa risk adjustment factor | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ double CalculateVillahermosaRiskFactor(ENUM_ORDER_TYPE type, string trade_cause, double atr) { double factor = 1.0; // Default factor string cause_effect_report = "Villahermosa Risk Factor Analysis: "; string overall_effect = ""; // 1. Drawdown-based risk adjustment double drawdown = CalculateCurrentDrawdown(); string drawdown_cause = ""; string drawdown_effect = ""; if(drawdown >= InpPauseAtDD) { factor = 0.0; // No new positions at maximum drawdown drawdown_cause = "Drawdown exceeded maximum threshold (" + DoubleToString(InpPauseAtDD*100, 1) + "%)"; drawdown_effect = "Complete trading halt to protect capital from further erosion"; cause_effect_report += "| Drawdown Cause: " + drawdown_cause + " | Effect: " + drawdown_effect; } else if(drawdown >= InpReduceAtDD) { factor = 0.5; // Reduce position size at elevated drawdown drawdown_cause = "Drawdown exceeded reduction threshold (" + DoubleToString(InpReduceAtDD*100, 1) + "%)"; drawdown_effect = "Position size reduced by 50% to limit exposure during challenging period"; cause_effect_report += "| Drawdown Cause: " + drawdown_cause + " | Effect: " + drawdown_effect; } else { drawdown_cause = "Drawdown within acceptable limits (" + DoubleToString(drawdown*100, 2) + "%)"; drawdown_effect = "No drawdown-based adjustment required"; cause_effect_report += "| Drawdown Cause: " + drawdown_cause + " | Effect: " + drawdown_effect; } // Only proceed with further analysis if not in complete pause mode if(factor > 0) { // 2. Cause-based risk adjustment double cause_factor = CalculateVillahermosaRiskAdjustment(trade_cause); factor *= cause_factor; string cause_based_effect = "Position size adjusted by " + DoubleToString((cause_factor - 1) * 100, 1) + "% based on trade cause quality"; cause_effect_report += "| Cause-Based Effect: " + cause_based_effect; // 3. Volatility-based risk adjustment double vol_factor = CalculateVolatilityRiskAdjustment(atr); factor *= vol_factor; string vol_based_effect = "Position size adjusted by " + DoubleToString((vol_factor - 1) * 100, 1) + "% based on volatility conditions"; cause_effect_report += "| Volatility-Based Effect: " + vol_based_effect; // 4. Time-based risk adjustment double time_factor = CalculateTimeBasedRiskAdjustment(); factor *= time_factor; string time_based_effect = "Position size adjusted by " + DoubleToString((time_factor - 1) * 100, 1) + "% based on session timing"; cause_effect_report += "| Time-Based Effect: " + time_based_effect; // 5. Position concentration risk adjustment double conc_factor = CalculateConcentrationRiskAdjustment(); factor *= conc_factor; string conc_based_effect = "Position size adjusted by " + DoubleToString((conc_factor - 1) * 100, 1) + "% based on current position concentration"; cause_effect_report += "| Concentration-Based Effect: " + conc_based_effect; // 6. Trade direction and market condition analysis string direction_cause = ""; string direction_effect = ""; // Analyze market momentum and direction alignment MqlRates rates[20]; if(CopyRates(InpSymbol, InpTF, 0, 20, rates) >= 20) { // Calculate short-term momentum double momentum = (rates[0].close - rates[19].close) / rates[19].close * 100; if((type == ORDER_TYPE_BUY && momentum > 0.5) || (type == ORDER_TYPE_SELL && momentum < -0.5)) { factor *= 1.1; // Increase size for momentum alignment direction_cause = "Trade direction aligns with strong market momentum (" + DoubleToString(momentum, 1) + "%)"; direction_effect = "Position size increased by 10% due to momentum confluence"; } else if((type == ORDER_TYPE_BUY && momentum < -0.5) || (type == ORDER_TYPE_SELL && momentum > 0.5)) { factor *= 0.9; // Decrease size for momentum divergence direction_cause = "Trade direction opposes strong market momentum (" + DoubleToString(momentum, 1) + "%)"; direction_effect = "Position size reduced by 10% due to momentum divergence"; } else { direction_cause = "Market momentum is neutral (" + DoubleToString(momentum, 1) + "%)"; direction_effect = "No momentum-based adjustment applied"; } cause_effect_report += "| Momentum Cause: " + direction_cause + " | Effect: " + direction_effect; } // 7. Economic calendar consideration (placeholder for integration) string economic_cause = "No major economic events detected"; // This would integrate with economic calendar data string economic_effect = "No economic event-based adjustment"; cause_effect_report += "| Economic Cause: " + economic_cause + " | Effect: " + economic_effect; // 8. Overall market sentiment analysis string sentiment_cause = ""; string sentiment_effect = ""; // Simple sentiment analysis based on recent price action int up_bars = 0, down_bars = 0; for(int i = 0; i < 10; i++) { if(rates[i].close > rates[i].open) up_bars++; else if(rates[i].close < rates[i].open) down_bars++; } double sentiment_ratio = (double)(up_bars - down_bars) / 10; if(sentiment_ratio > 0.3) // Bullish sentiment { if(type == ORDER_TYPE_BUY) { factor *= 1.05; sentiment_cause = "Strong bullish market sentiment (" + DoubleToString(sentiment_ratio*100, 1) + "%)"; sentiment_effect = "Position size increased by 5% for bullish alignment"; } else { factor *= 0.95; sentiment_cause = "Strong bullish market sentiment (" + DoubleToString(sentiment_ratio*100, 1) + "%)"; sentiment_effect = "Position size reduced by 5% for bearish divergence"; } } else if(sentiment_ratio < -0.3) // Bearish sentiment { if(type == ORDER_TYPE_SELL) { factor *= 1.05; sentiment_cause = "Strong bearish market sentiment (" + DoubleToString(sentiment_ratio*100, 1) + "%)"; sentiment_effect = "Position size increased by 5% for bearish alignment"; } else { factor *= 0.95; sentiment_cause = "Strong bearish market sentiment (" + DoubleToString(sentiment_ratio*100, 1) + "%)"; sentiment_effect = "Position size reduced by 5% for bullish divergence"; } } else { sentiment_cause = "Neutral market sentiment (" + DoubleToString(sentiment_ratio*100, 1) + "%)"; sentiment_effect = "No sentiment-based adjustment applied"; } cause_effect_report += "| Sentiment Cause: " + sentiment_cause + " | Effect: " + sentiment_effect; } // Ensure factor is within reasonable bounds (0.0 to 2.0) factor = MathMax(MathMin(factor, 2.0), 0.0); // Determine overall effect category if(factor == 0) { overall_effect = "Trading completely paused due to excessive drawdown"; } else if(factor < 0.5) { overall_effect = "Significantly reduced position size due to multiple risk factors"; } else if(factor < 0.8) { overall_effect = "Moderately reduced position size due to several risk factors"; } else if(factor < 1.0) { overall_effect = "Slightly reduced position size due to minor risk factors"; } else if(factor == 1.0) { overall_effect = "Standard position size with no risk-based adjustments"; } else if(factor <= 1.5) { overall_effect = "Increased position size due to favorable conditions"; } else { overall_effect = "Significantly increased position size due to highly favorable conditions"; } // Final cause and effect report cause_effect_report += " | Overall Effect: " + overall_effect; cause_effect_report += " | Final Risk Factor: " + DoubleToString(factor, 2); WriteLog(cause_effect_report); return MathMax(factor, 0.1); // Ensure minimum factor of 0.1 } //+------------------------------------------------------------------+ //| Calculate position size with Villahermosa cause & effect analysis| //+------------------------------------------------------------------+ double VolTargetLots(double equity, double atr, ENUM_ORDER_TYPE type = WRONG_VALUE) { string cause_effect_report = "Villahermosa Position Sizing Analysis: "; string calculation_steps = ""; double pt = SymbolInfoDouble(InpSymbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS); double pip_value = (digits == 5 || digits == 3) ? pt * 10 : pt; if(pip_value <= 0) { WriteLog("Invalid pip value calculation"); return 0.0; } // Calculate ATR in pips double atr_pips = atr / pip_value; calculation_steps += "ATR in pips: " + DoubleToString(atr_pips, 2) + " | "; // Calculate daily volatility target from annual target double daily_vol_target = InpAnnualVolTarget / MathSqrt(252.0); calculation_steps += "Daily vol target: " + DoubleToString(daily_vol_target*100, 2) + "% | "; // Calculate dollar volatility target double dollars_vol_target = daily_vol_target * equity; calculation_steps += "Dollar vol target: $" + DoubleToString(dollars_vol_target, 2) + " | "; // Calculate dollar value per pip for the symbol double tick_value = SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_VALUE); double tick_size = SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_SIZE); if(tick_size <= 0) { WriteLog("Invalid tick size"); return 0.0; } double dollars_per_pip_per_lot = tick_value * (pip_value / tick_size); calculation_steps += "$ per pip per lot: $" + DoubleToString(dollars_per_pip_per_lot, 2) + " | "; if(dollars_per_pip_per_lot <= 0 || atr_pips <= 0) { WriteLog("Invalid dollars per pip or ATR pips calculation"); return 0.0; } // Base lot calculation double base_lots = dollars_vol_target / (atr_pips * dollars_per_pip_per_lot); calculation_steps += "Base lots: " + DoubleToString(base_lots, 2); cause_effect_report += calculation_steps; // Apply Villahermosa volatility adjustment factor double villahermosa_factor = CalculateVillahermosaVolAdjustment(type, atr); string vol_cause = "Volatility adjustment factor: " + DoubleToString(villahermosa_factor, 2); string vol_effect = "Lots after vol adjustment: " + DoubleToString(base_lots * villahermosa_factor, 2); cause_effect_report += " | " + vol_cause + " | " + vol_effect; // Apply Villahermosa cause-based position sizing double cause_based_factor = CalculateCauseBasedPositionSize(type); string cause_cause = "Cause-based adjustment factor: " + DoubleToString(cause_based_factor, 2); string cause_effect = "Lots after cause adjustment: " + DoubleToString(base_lots * villahermosa_factor * cause_based_factor, 2); cause_effect_report += " | " + cause_cause + " | " + cause_effect; // Final lot calculation with Villahermosa adjustments double final_lots = base_lots * villahermosa_factor * cause_based_factor; // Apply risk cap if needed double risk_cap_lots = RiskCapLots(equity, atr, InpATRStopMult, type); if(final_lots > risk_cap_lots) { string risk_cap_cause = "Final lots exceed risk cap (" + DoubleToString(final_lots, 2) + " > " + DoubleToString(risk_cap_lots, 2) + ")"; string risk_cap_effect = "Lots reduced to risk cap: " + DoubleToString(risk_cap_lots, 2); final_lots = risk_cap_lots; cause_effect_report += " | " + risk_cap_cause + " | " + risk_cap_effect; } // Normalize lots to broker requirements double normalized_lots = NormalizeLots(final_lots); if(normalized_lots != final_lots) { string normalize_cause = "Lot normalization required (" + DoubleToString(final_lots, 2) + ")"; string normalize_effect = "Lots normalized to: " + DoubleToString(normalized_lots, 2); cause_effect_report += " | " + normalize_cause + " | " + normalize_effect; final_lots = normalized_lots; } // Check if lots meet minimum requirement double min_lot = MinLot(); if(final_lots < min_lot) { string min_lot_cause = "Calculated lots below minimum (" + DoubleToString(final_lots, 2) + " < " + DoubleToString(min_lot, 2) + ")"; string min_lot_effect = "Position size too small, trade not taken"; cause_effect_report += " | " + min_lot_cause + " | " + min_lot_effect; WriteLog(cause_effect_report); return 0.0; } // Final cause and effect report string final_cause = "All adjustments applied"; string final_effect = "Final position size: " + DoubleToString(final_lots, 2) + " lots"; cause_effect_report += " | " + final_cause + " | " + final_effect; WriteLog(cause_effect_report); return final_lots; } //+------------------------------------------------------------------+ //| Calculate position size with Villahermosa cause & effect analysis| //+------------------------------------------------------------------+ double RiskCapLots(double equity, double atr, double stopMult, ENUM_ORDER_TYPE type = WRONG_VALUE, string trade_cause = "") { string cause_effect_report = "Villahermosa Risk Management Analysis: "; string calculation_steps = ""; // Calculate pip value based on symbol digits double pt = SymbolInfoDouble(InpSymbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS); double pip_value = (digits == 5 || digits == 3) ? pt * 10 : pt; if(pip_value <= 0) { string error_cause = "Invalid pip value calculation"; string error_effect = "Cannot calculate position size, returning 0"; WriteLog("RiskCapLots Error: " + error_cause + " | Effect: " + error_effect); return 0.0; } // Calculate ATR in pips and stop loss in pips double atr_pips = atr / pip_value; calculation_steps += "ATR in pips: " + DoubleToString(atr_pips, 2) + " | "; double stop_pips = stopMult * atr_pips; calculation_steps += "Stop loss in pips: " + DoubleToString(stop_pips, 2) + " | "; // Calculate dollar value per pip for the symbol double tick_value = SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_VALUE); double tick_size = SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_SIZE); if(tick_size <= 0) { string error_cause = "Invalid tick size"; string error_effect = "Cannot calculate position size, returning 0"; WriteLog("RiskCapLots Error: " + error_cause + " | Effect: " + error_effect); return 0.0; } double dollars_per_pip_per_lot = tick_value * (pip_value / tick_size); calculation_steps += "$ per pip per lot: $" + DoubleToString(dollars_per_pip_per_lot, 2) + " | "; if(stop_pips <= 0 || dollars_per_pip_per_lot <= 0) { string error_cause = "Invalid stop pips (" + DoubleToString(stop_pips, 2) + ") or dollars per pip (" + DoubleToString(dollars_per_pip_per_lot, 2) + ")"; string error_effect = "Cannot calculate position size, returning 0"; WriteLog("RiskCapLots Error: " + error_cause + " | Effect: " + error_effect); return 0.0; } // Base risk calculation double dollars_risk_cap = InpMaxRiskPerTrade * equity; calculation_steps += "Max risk amount: $" + DoubleToString(dollars_risk_cap, 2) + " | "; double base_lots = dollars_risk_cap / (stop_pips * dollars_per_pip_per_lot); string base_cause = "Base lots calculated from risk parameters"; string base_effect = "Base position size: " + DoubleToString(base_lots, 2) + " lots"; cause_effect_report += calculation_steps + " | " + base_cause + " | " + base_effect; // Apply Villahermosa risk adjustment factors double risk_factor = CalculateVillahermosaRiskFactor(type, trade_cause, atr); string risk_cause = "Villahermosa risk factor applied: " + DoubleToString(risk_factor, 2); string risk_effect = "Position size after risk factor: " + DoubleToString(base_lots * risk_factor, 2) + " lots"; cause_effect_report += " | " + risk_cause + " | " + risk_effect; // Final lot calculation with Villahermosa adjustments double final_lots = base_lots * risk_factor; // Apply additional risk constraints string constraint_cause = ""; string constraint_effect = ""; // Check against maximum allowed position size double max_lots = SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_MAX); if(final_lots > max_lots) { constraint_cause = "Calculated lots exceed maximum allowed (" + DoubleToString(final_lots, 2) + " > " + DoubleToString(max_lots, 2) + ")"; constraint_effect = "Lots reduced to maximum allowed: " + DoubleToString(max_lots, 2); final_lots = max_lots; cause_effect_report += " | " + constraint_cause + " | " + constraint_effect; } // Check against minimum allowed position size double min_lots = SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_MIN); if(final_lots < min_lots) { constraint_cause = "Calculated lots below minimum allowed (" + DoubleToString(final_lots, 2) + " < " + DoubleToString(min_lots, 2) + ")"; constraint_effect = "Position size too small, trade not viable"; cause_effect_report += " | " + constraint_cause + " | " + constraint_effect; WriteLog(cause_effect_report); return 0.0; } // Normalize lots to broker's step size double step_size = SymbolInfoDouble(InpSymbol, SYMBOL_VOLUME_STEP); double normalized_lots = MathRound(final_lots / step_size) * step_size; if(normalized_lots != final_lots) { constraint_cause = "Lot normalization required (" + DoubleToString(final_lots, 2) + ")"; constraint_effect = "Lots normalized to: " + DoubleToString(normalized_lots, 2); final_lots = normalized_lots; cause_effect_report += " | " + constraint_cause + " | " + constraint_effect; } // Final cause and effect report string final_cause = "All risk management rules applied"; string final_effect = "Final position size: " + DoubleToString(final_lots, 2) + " lots"; cause_effect_report += " | " + final_cause + " | " + final_effect; // Add trade cause information if available if(trade_cause != "") { cause_effect_report += " | Trade Cause: " + trade_cause; } WriteLog(cause_effect_report); return final_lots; } //+------------------------------------------------------------------+ //| Villahermosa Entry Cause Analysis | //| Determines the fundamental cause behind position entry | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ string VillahermosaEntryCauseAnalysis(datetime open_time, double open_price, ENUM_POSITION_TYPE type) { string cause_effect_report = "Entry Cause Analysis: "; string detailed_cause = ""; string expected_effect = ""; // Get market conditions at the time of position entry int bar_shift = iBarShift(InpSymbol, InpTF, open_time); // 1. Check for Donchian breakout (primary Villahermosa trigger) double upper_band = Donchian(InpSymbol, InpTF, InpDonchianEntry, MODE_HIGH, bar_shift+1); double lower_band = Donchian(InpSymbol, InpTF, InpDonchianEntry, MODE_LOW, bar_shift+1); if(type == POSITION_TYPE_BUY && MathAbs(open_price - upper_band) < SymbolInfoDouble(InpSymbol, SYMBOL_POINT) * 5) { detailed_cause = "Price (" + DoubleToString(open_price, 5) + ") broke above Donchian upper band (" + DoubleToString(upper_band, 5) + ")"; expected_effect = "Expect strong bullish momentum following breakout of " + IntegerToString(InpDonchianEntry) + "-period resistance"; cause_effect_report += "Donchian Breakout (Upper) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } else if(type == POSITION_TYPE_SELL && MathAbs(open_price - lower_band) < SymbolInfoDouble(InpSymbol, SYMBOL_POINT) * 5) { detailed_cause = "Price (" + DoubleToString(open_price, 5) + ") broke below Donchian lower band (" + DoubleToString(lower_band, 5) + ")"; expected_effect = "Expect strong bearish momentum following breakdown of " + IntegerToString(InpDonchianEntry) + "-period support"; cause_effect_report += "Donchian Breakout (Lower) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } // 2. Check for volatility contraction/expansion double atr_values[3]; if(CopyBuffer(atr_handle, 0, bar_shift, 3, atr_values) >= 3) { double atr_change = (atr_values[0] - atr_values[2]) / atr_values[2] * 100; if(MathAbs(atr_change) > 25) // Significant volatility change { if(atr_change > 0) { detailed_cause = "Volatility expanded by " + DoubleToString(atr_change, 1) + "% (from " + DoubleToString(atr_values[2], 5) + " to " + DoubleToString(atr_values[0], 5) + ")"; expected_effect = "Expect significant price movement following volatility expansion"; cause_effect_report += "Volatility Expansion | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } else { detailed_cause = "Volatility contracted by " + DoubleToString(MathAbs(atr_change), 1) + "% (from " + DoubleToString(atr_values[2], 5) + " to " + DoubleToString(atr_values[0], 5) + ")"; expected_effect = "Expect breakout following volatility contraction phase"; cause_effect_report += "Volatility Contraction | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } } } // 3. Check for trend alignment MqlRates rates[5]; if(CopyRates(InpSymbol, InpTF, bar_shift-4, 5, rates) >= 5) { bool uptrend = rates[4].close < rates[0].close; bool downtrend = rates[4].close > rates[0].close; double trend_strength = 0; if(uptrend) trend_strength = (rates[0].close - rates[4].close) / rates[4].close * 100; if(downtrend) trend_strength = (rates[4].close - rates[0].close) / rates[4].close * 100; if(type == POSITION_TYPE_BUY && uptrend) { detailed_cause = "Strong uptrend detected with " + DoubleToString(trend_strength, 1) + "% price increase over 5 bars"; expected_effect = "Expect trend continuation based on momentum alignment"; cause_effect_report += "Trend Alignment (Uptrend) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } if(type == POSITION_TYPE_SELL && downtrend) { detailed_cause = "Strong downtrend detected with " + DoubleToString(trend_strength, 1) + "% price decrease over 5 bars"; expected_effect = "Expect trend continuation based on momentum alignment"; cause_effect_report += "Trend Alignment (Downtrend) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } } // 4. Check for time-based entry (Asian/London/NY session) MqlDateTime entry_time_struct; TimeToStruct(open_time, entry_time_struct); int hour = entry_time_struct.hour; if((hour >= 1 && hour <= 4) || (hour >= 7 && hour <= 10)) { detailed_cause = "Entry during session overlap hours (" + IntegerToString(hour) + ":00)"; if(hour >= 1 && hour <= 4) expected_effect = "Expect increased volatility during Asian/London session overlap"; else expected_effect = "Expect increased volatility during London/NY session overlap"; cause_effect_report += "Session Overlap Entry | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } // 5. Check for price action patterns MqlRates current_bars[3]; if(CopyRates(InpSymbol, InpTF, bar_shift, 3, current_bars) >= 3) { // Check for pin bar reversal pattern if(type == POSITION_TYPE_BUY && current_bars[0].low < current_bars[1].low && current_bars[0].close > current_bars[0].open && (current_bars[0].close - current_bars[0].open) > (current_bars[0].high - current_bars[0].close) * 2) { detailed_cause = "Bullish pin bar pattern detected at support"; expected_effect = "Expect reversal to the upside following pin bar confirmation"; cause_effect_report += "Price Action (Pin Bar) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } if(type == POSITION_TYPE_SELL && current_bars[0].high > current_bars[1].high && current_bars[0].close < current_bars[0].open && (current_bars[0].open - current_bars[0].close) > (current_bars[0].close - current_bars[0].low) * 2) { detailed_cause = "Bearish pin bar pattern detected at resistance"; expected_effect = "Expect reversal to the downside following pin bar confirmation"; cause_effect_report += "Price Action (Pin Bar) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } } // 6. Check for momentum divergence double rsi_values[2]; int rsi_handle = iRSI(InpSymbol, InpTF, 14, PRICE_CLOSE); if(CopyBuffer(rsi_handle, 0, bar_shift, 2, rsi_values) >= 2) { if(type == POSITION_TYPE_BUY && current_bars[0].low < current_bars[1].low && rsi_values[0] > rsi_values[1]) { detailed_cause = "Bullish divergence detected (price made lower low while RSI made higher low)"; expected_effect = "Expect reversal to the upside following momentum divergence"; cause_effect_report += "Momentum Divergence | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } if(type == POSITION_TYPE_SELL && current_bars[0].high > current_bars[1].high && rsi_values[0] < rsi_values[1]) { detailed_cause = "Bearish divergence detected (price made higher high while RSI made lower high)"; expected_effect = "Expect reversal to the downside following momentum divergence"; cause_effect_report += "Momentum Divergence | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } } // Default return if no specific cause identified detailed_cause = "No clear technical catalyst identified for entry at " + DoubleToString(open_price, 5) + " on " + TimeToString(open_time); expected_effect = "Further analysis required to determine entry rationale"; cause_effect_report += "Unknown Cause | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } //+------------------------------------------------------------------+ //| Villahermosa Effect Analysis | //| Analyzes the current effect/performance of the position | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ string VillahermosaEffectAnalysis(ulong ticket, ENUM_POSITION_TYPE type, double open_price, double current_price, double profit, long position_age) { string cause_effect_report = "Position Effect Analysis: "; // Calculate key performance metrics double point = SymbolInfoDouble(InpSymbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS); double pip_value = (digits == 5 || digits == 3) ? point * 10 : point; double price_change_pips = MathAbs(current_price - open_price) / pip_value; double profit_ratio = (AccountInfoDouble(ACCOUNT_BALANCE) != 0) ? (profit / AccountInfoDouble(ACCOUNT_BALANCE)) * 100 : 0; double hours_open = position_age / 3600.0; // Determine price movement direction relative to position string price_direction = ""; if((type == POSITION_TYPE_BUY && current_price > open_price) || (type == POSITION_TYPE_SELL && current_price < open_price)) { price_direction = "favorable"; } else { price_direction = "adverse"; } // 1. Time-based effect analysis string time_cause = ""; string time_effect = ""; if(hours_open < 4) { time_cause = "Position opened recently (" + DoubleToString(hours_open, 1) + " hours ago)"; time_effect = "Early stage - monitoring initial price reaction and trade development"; } else if(hours_open < 24) { time_cause = "Position has been developing (" + DoubleToString(hours_open, 1) + " hours)"; time_effect = "Developing position - assessing momentum and trend confirmation"; } else if(hours_open < 72) { time_cause = "Position has matured (" + DoubleToString(hours_open, 1) + " hours)"; time_effect = "Mature position - evaluating profit targets and exit conditions"; } else { time_cause = "Position has aged significantly (" + DoubleToString(hours_open, 1) + " hours)"; time_effect = "Aged position - review needed for potential exit or adjustment"; } cause_effect_report += "Time: " + time_cause + " | Effect: " + time_effect; // 2. Price movement analysis string price_cause = ""; string price_effect = ""; price_cause = "Price moved " + DoubleToString(price_change_pips, 1) + " pips in " + price_direction + " direction"; if(price_direction == "favorable") { if(price_change_pips > 100) { price_effect = "Strong favorable movement - consider profit protection strategies"; } else if(price_change_pips > 50) { price_effect = "Moderate favorable movement - monitor for continuation signals"; } else { price_effect = "Minor favorable movement - awaiting further development"; } } else { if(price_change_pips > 100) { price_effect = "Significant adverse movement - review stop loss and risk management"; } else if(price_change_pips > 50) { price_effect = "Moderate adverse movement - monitor for potential reversal"; } else { price_effect = "Minor adverse movement - normal market fluctuation"; } } cause_effect_report += " | Price: " + price_cause + " | Effect: " + price_effect; // 3. Profit-based effect analysis string profit_cause = ""; string profit_effect = ""; profit_cause = "Position " + (profit >= 0 ? "profitable" : "unprofitable") + " (" + DoubleToString(profit, 2) + " / " + DoubleToString(profit_ratio, 2) + "%)"; if(profit > 0) { if(profit_ratio > 2.0) { profit_effect = "Strong positive effect - consider taking partial profits"; } else if(profit_ratio > 0.5) { profit_effect = "Moderate positive effect - monitor for profit target achievement"; } else { profit_effect = "Minor positive effect - position developing positively"; } } else { if(profit_ratio < -2.0) { profit_effect = "Strong negative effect - review trade thesis and risk management"; } else if(profit_ratio < -0.5) { profit_effect = "Moderate negative effect - monitor closely for potential recovery"; } else { profit_effect = "Minor negative effect - normal drawdown within expectations"; } } cause_effect_report += " | Profit: " + profit_cause + " | Effect: " + profit_effect; // 4. Volatility analysis string volatility_cause = ""; string volatility_effect = ""; // Get current ATR for volatility context double atr_values[1]; if(CopyBuffer(atr_handle, 0, 0, 1, atr_values) == 1) { double atr_pips = atr_values[0] / pip_value; double volatility_ratio = price_change_pips / (atr_pips * (hours_open / 24.0)); volatility_cause = "Volatility ratio: " + DoubleToString(volatility_ratio, 2) + " (ATR: " + DoubleToString(atr_pips, 1) + " pips)"; if(volatility_ratio > 1.5) { volatility_effect = "High volatility movement - expect increased price fluctuations"; } else if(volatility_ratio > 0.8) { volatility_effect = "Normal volatility movement - price action within expected range"; } else { volatility_effect = "Low volatility movement - price action quieter than expected"; } cause_effect_report += " | Volatility: " + volatility_cause + " | Effect: " + volatility_effect; } // 5. Market context analysis string market_cause = ""; string market_effect = ""; // Check if price is near key Donchian levels double upper_band = Donchian(InpSymbol, InpTF, InpDonchianExit, MODE_HIGH, 1); double lower_band = Donchian(InpSymbol, InpTF, InpDonchianExit, MODE_LOW, 1); double mid_band = (upper_band + lower_band) / 2; double distance_to_upper = MathAbs(current_price - upper_band) / pip_value; double distance_to_lower = MathAbs(current_price - lower_band) / pip_value; double distance_to_mid = MathAbs(current_price - mid_band) / pip_value; // Determine which level is closest if(distance_to_upper <= distance_to_lower && distance_to_upper <= distance_to_mid) { market_cause = "Price near upper Donchian band (" + DoubleToString(distance_to_upper, 1) + " pips)"; market_effect = "Potential resistance area - monitor for reversal signals"; } else if(distance_to_lower <= distance_to_upper && distance_to_lower <= distance_to_mid) { market_cause = "Price near lower Donchian band (" + DoubleToString(distance_to_lower, 1) + " pips)"; market_effect = "Potential support area - monitor for bounce signals"; } else { market_cause = "Price near middle of Donchian channel (" + DoubleToString(distance_to_mid, 1) + " pips)"; market_effect = "Neutral territory - direction uncertain"; } cause_effect_report += " | Market: " + market_cause + " | Effect: " + market_effect; // 6. Overall position assessment string overall_cause = ""; string overall_effect = ""; // Create overall assessment based on multiple factors if(profit > 0 && price_direction == "favorable" && hours_open < 72) { overall_cause = "Multiple positive factors: profitable, favorable price movement, reasonable duration"; overall_effect = "Position performing well - continue monitoring for exit signals"; } else if(profit < 0 && price_direction == "adverse" && hours_open > 24) { overall_cause = "Multiple negative factors: unprofitable, adverse price movement, extended duration"; overall_effect = "Position underperforming - consider early exit or adjustment"; } else { overall_cause = "Mixed signals across performance metrics"; overall_effect = "Position in uncertain state - continue monitoring with caution"; } cause_effect_report += " | Overall: " + overall_cause + " | Effect: " + overall_effect; return cause_effect_report; } //+------------------------------------------------------------------+ //| Calculate Villahermosa Validation Score | //| Scores position quality based on cause and effect alignment | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ int CalculateVillahermosaScore(string entry_cause, string effect_analysis, double profit, long position_age) { int score = 50; // Base score string score_report = "Villahermosa Validation Score Analysis: "; string scoring_details = ""; // Extract the base cause from the entry cause string (which may contain full cause-effect analysis) string base_entry_cause = entry_cause; if(StringFind(entry_cause, "|") >= 0) { // Extract just the cause type from the detailed report int separator_pos = StringFind(entry_cause, "|"); base_entry_cause = StringSubstr(entry_cause, 0, separator_pos); StringTrimLeft(base_entry_cause); StringTrimRight(base_entry_cause); } // 1. Score based on entry cause string entry_cause_effect = ""; if(base_entry_cause == "Donchian Breakout (Upper)" || base_entry_cause == "Donchian Breakout (Lower)") { score += 25; entry_cause_effect = "Strong entry cause (+25) - High probability setup based on Donchian breakout"; } else if(base_entry_cause == "Trend Alignment (Uptrend)" || base_entry_cause == "Trend Alignment (Downtrend)") { score += 15; entry_cause_effect = "Good entry cause (+15) - Trade aligns with established trend"; } else if(base_entry_cause == "Volatility Expansion") { score += 10; entry_cause_effect = "Moderate entry cause (+10) - Entered during volatility expansion phase"; } else if(base_entry_cause == "Session Overlap Entry") { score += 5; entry_cause_effect = "Basic entry cause (+5) - Entered during high-probability session overlap"; } else { entry_cause_effect = "Unknown entry cause (0) - No additional points for entry quality"; } scoring_details += "Entry: " + entry_cause_effect; // 2. Score based on effect analysis string effect_analysis_effect = ""; // Extract the base effect from the effect analysis string string base_effect_analysis = effect_analysis; if(StringFind(effect_analysis, "|") >= 0) { // Extract just the effect type from the detailed report int separator_pos = StringFind(effect_analysis, "|"); base_effect_analysis = StringSubstr(effect_analysis, 0, separator_pos); StringTrimLeft(base_effect_analysis); StringTrimRight(base_effect_analysis); } if(base_effect_analysis == "Early Stage (Monitoring)") { effect_analysis_effect = "Position too new (0) - Insufficient data for effect analysis"; } else if(base_effect_analysis == "Developing Position") { score += 10; effect_analysis_effect = "Positive development (+10) - Position developing as expected"; } else if(base_effect_analysis == "Mature Position") { score += 5; effect_analysis_effect = "Normal development (+5) - Position maturing normally"; } else if(base_effect_analysis == "Aged Position (Review Needed)") { score -= 15; effect_analysis_effect = "Concerning development (-15) - Position aged beyond optimal timeframe"; } else { // Handle detailed effect analysis reports if(StringFind(effect_analysis, "Strong favorable movement") >= 0) { score += 15; effect_analysis_effect = "Excellent development (+15) - Strong favorable price movement"; } else if(StringFind(effect_analysis, "Moderate favorable movement") >= 0) { score += 10; effect_analysis_effect = "Good development (+10) - Moderate favorable price movement"; } else if(StringFind(effect_analysis, "Significant adverse movement") >= 0) { score -= 20; effect_analysis_effect = "Poor development (-20) - Significant adverse price movement"; } else if(StringFind(effect_analysis, "Moderate adverse movement") >= 0) { score -= 10; effect_analysis_effect = "Concerning development (-10) - Moderate adverse price movement"; } else { effect_analysis_effect = "Neutral development (0) - No significant price movement detected"; } } scoring_details += " | Effect: " + effect_analysis_effect; // 3. Score based on profitability string profitability_effect = ""; if(profit > 0) { score += 10; profitability_effect = "Profitable position (+10) - Position showing positive returns"; } else if(profit < 0) { score -= 10; profitability_effect = "Unprofitable position (-10) - Position showing negative returns"; } else { profitability_effect = "Break-even position (0) - Position at entry price"; } scoring_details += " | Profitability: " + profitability_effect; // 4. Score based on position age (optimal 4-24 hours) double hours_open = position_age / 3600.0; string time_effect = ""; if(hours_open >= 4 && hours_open <= 24) { score += 10; time_effect = "Optimal timeframe (+10) - Position in ideal 4-24 hour window"; } else if(hours_open > 72) { score -= 15; time_effect = "Extended timeframe (-15) - Position beyond 72-hour review threshold"; } else if(hours_open < 4) { time_effect = "Early timeframe (0) - Position too new for time-based assessment"; } else { time_effect = "Acceptable timeframe (0) - Position outside optimal but within acceptable range"; } scoring_details += " | Time: " + time_effect; // 5. Additional factor: Risk-adjusted performance string risk_adjusted_effect = ""; if(profit != 0 && hours_open > 0) { double hourly_return = profit / hours_open; double account_size = AccountInfoDouble(ACCOUNT_BALANCE); if(account_size > 0) { double return_ratio = (hourly_return / account_size) * 100; if(return_ratio > 0.05) // Strong risk-adjusted returns { score += 5; risk_adjusted_effect = "Excellent risk-adjusted returns (+5)"; } else if(return_ratio < -0.05) // Poor risk-adjusted returns { score -= 5; risk_adjusted_effect = "Poor risk-adjusted returns (-5)"; } else { risk_adjusted_effect = "Average risk-adjusted returns (0)"; } scoring_details += " | Risk-Adjusted: " + risk_adjusted_effect; } } // Ensure score is within bounds score = MathMin(MathMax(score, 0), 100); // Determine quality rating based on final score string quality_rating = ""; if(score >= 90) quality_rating = "Exceptional Quality"; else if(score >= 80) quality_rating = "High Quality"; else if(score >= 70) quality_rating = "Good Quality"; else if(score >= 60) quality_rating = "Average Quality"; else if(score >= 50) quality_rating = "Below Average Quality"; else quality_rating = "Poor Quality"; // Final cause and effect report score_report += scoring_details + " | Final Score: " + IntegerToString(score) + "/100 (" + quality_rating + ")"; WriteLog(score_report); return score; } //+------------------------------------------------------------------+ //| Check if market trend has reversed since position entry | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ bool IsTrendReversed(ENUM_POSITION_TYPE type, datetime open_time) { string cause_effect_report = "Trend Reversal Analysis: "; bool is_reversed = false; string reversal_cause = ""; string reversal_effect = ""; // Get current trend direction using Donchian channels double current_upper = Donchian(InpSymbol, InpTF, 20, MODE_HIGH, 1); double current_lower = Donchian(InpSymbol, InpTF, 20, MODE_LOW, 1); double current_close = iClose(InpSymbol, InpTF, 1); // Calculate Donchian channel midpoint double channel_midpoint = (current_upper + current_lower) / 2; // Determine current trend bool current_uptrend = current_close > channel_midpoint; bool current_downtrend = current_close < channel_midpoint; // Calculate trend strength double trend_strength = 0; if(current_uptrend) { trend_strength = (current_close - channel_midpoint) / channel_midpoint * 100; } else if(current_downtrend) { trend_strength = (channel_midpoint - current_close) / channel_midpoint * 100; } // Get the original trend at position entry int bar_shift = iBarShift(InpSymbol, InpTF, open_time); double entry_upper = Donchian(InpSymbol, InpTF, 20, MODE_HIGH, bar_shift+1); double entry_lower = Donchian(InpSymbol, InpTF, 20, MODE_LOW, bar_shift+1); double entry_close = iClose(InpSymbol, InpTF, bar_shift+1); double entry_midpoint = (entry_upper + entry_lower) / 2; bool entry_uptrend = entry_close > entry_midpoint; bool entry_downtrend = entry_close < entry_midpoint; // Check if trend has reversed against our position if(type == POSITION_TYPE_BUY) { if(current_downtrend) { is_reversed = true; reversal_cause = "Price (" + DoubleToString(current_close, 5) + ") dropped below Donchian midpoint (" + DoubleToString(channel_midpoint, 5) + ") with " + DoubleToString(trend_strength, 1) + "% bearish deviation. Original entry was in uptrend."; reversal_effect = "Bullish position now against the dominant downtrend - consider exit or adjustment"; } else { reversal_cause = "Price (" + DoubleToString(current_close, 5) + ") remains above Donchian midpoint (" + DoubleToString(channel_midpoint, 5) + ") with " + DoubleToString(trend_strength, 1) + "% bullish deviation"; reversal_effect = "Bullish position aligns with current uptrend - trend remains favorable"; } } else if(type == POSITION_TYPE_SELL) { if(current_uptrend) { is_reversed = true; reversal_cause = "Price (" + DoubleToString(current_close, 5) + ") rose above Donchian midpoint (" + DoubleToString(channel_midpoint, 5) + ") with " + DoubleToString(trend_strength, 1) + "% bullish deviation. Original entry was in downtrend."; reversal_effect = "Bearish position now against the dominant uptrend - consider exit or adjustment"; } else { reversal_cause = "Price (" + DoubleToString(current_close, 5) + ") remains below Donchian midpoint (" + DoubleToString(channel_midpoint, 5) + ") with " + DoubleToString(trend_strength, 1) + "% bearish deviation"; reversal_effect = "Bearish position aligns with current downtrend - trend remains favorable"; } } // Add trend strength analysis string strength_analysis = ""; if(trend_strength > 1.0) { strength_analysis = "Strong trend (>" + DoubleToString(trend_strength, 1) + "%) - high conviction movement"; } else if(trend_strength > 0.5) { strength_analysis = "Moderate trend (" + DoubleToString(trend_strength, 1) + "%) - meaningful directional bias"; } else if(trend_strength > 0.1) { strength_analysis = "Weak trend (" + DoubleToString(trend_strength, 1) + "%) - minimal directional bias"; } else { strength_analysis = "Neutral price action - no clear trend direction"; } // Add channel width analysis for context double channel_width = (current_upper - current_lower) / current_lower * 100; string channel_analysis = ""; if(channel_width > 2.0) { channel_analysis = "Wide channel (" + DoubleToString(channel_width, 1) + "%) - high volatility environment"; } else if(channel_width > 1.0) { channel_analysis = "Moderate channel (" + DoubleToString(channel_width, 1) + "%) - normal volatility"; } else { channel_analysis = "Narrow channel (" + DoubleToString(channel_width, 1) + "%) - low volatility environment"; } // Compile comprehensive cause and effect report cause_effect_report += "Reversal: " + (is_reversed ? "Yes" : "No") + " | Cause: " + reversal_cause + " | Effect: " + reversal_effect + " | Strength: " + strength_analysis + " | Channel: " + channel_analysis; WriteLog(cause_effect_report); return is_reversed; } //+------------------------------------------------------------------+ //| Villahermosa Trade Cause Analysis | //| Determines the fundamental cause/reason for entering a trade | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ string VillahermosaTradeCauseAnalysis(ENUM_ORDER_TYPE type, double price) { string cause_effect_report = "Trade Cause Analysis: "; string detailed_cause = ""; string expected_effect = ""; // Get current market conditions for cause analysis MqlRates current_rate; if(CopyRates(InpSymbol, InpTF, 0, 1, ¤t_rate) < 1) { cause_effect_report += "Data Error | Cause: Unable to retrieve current market data | Effect: Cannot determine trade cause"; return cause_effect_report; } // Calculate Donchian channels for cause determination double upper_band = Donchian(InpSymbol, InpTF, InpDonchianEntry, MODE_HIGH, 1); double lower_band = Donchian(InpSymbol, InpTF, InpDonchianEntry, MODE_LOW, 1); // 1. Primary Cause: Donchian Breakout if(type == ORDER_TYPE_BUY && price >= upper_band) { double breakout_distance = (price - upper_band) / SymbolInfoDouble(InpSymbol, SYMBOL_POINT); detailed_cause = "Price (" + DoubleToString(price, 5) + ") broke above Donchian upper band (" + DoubleToString(upper_band, 5) + ") by " + DoubleToString(breakout_distance, 1) + " points"; expected_effect = "Expect strong bullish momentum following breakout of " + IntegerToString(InpDonchianEntry) + "-period resistance level"; cause_effect_report += "Donchian Breakout (Upper Channel) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } else if(type == ORDER_TYPE_SELL && price <= lower_band) { double breakout_distance = (lower_band - price) / SymbolInfoDouble(InpSymbol, SYMBOL_POINT); detailed_cause = "Price (" + DoubleToString(price, 5) + ") broke below Donchian lower band (" + DoubleToString(lower_band, 5) + ") by " + DoubleToString(breakout_distance, 1) + " points"; expected_effect = "Expect strong bearish momentum following breakdown of " + IntegerToString(InpDonchianEntry) + "-period support level"; cause_effect_report += "Donchian Breakout (Lower Channel) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } // 2. Volatility-based Cause double atr_values[5]; if(CopyBuffer(atr_handle, 0, 0, 5, atr_values) >= 5) { double atr_change = (atr_values[0] - atr_values[4]) / atr_values[4] * 100; if(MathAbs(atr_change) > 20) // Significant volatility change { if(atr_change > 0) { detailed_cause = "Volatility expanded by " + DoubleToString(atr_change, 1) + "% (from " + DoubleToString(atr_values[4], 5) + " to " + DoubleToString(atr_values[0], 5) + ") over 5 periods"; expected_effect = "Expect significant price movement following volatility expansion, typically with strong momentum"; cause_effect_report += "Volatility Expansion Breakout | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } else { detailed_cause = "Volatility contracted by " + DoubleToString(MathAbs(atr_change), 1) + "% (from " + DoubleToString(atr_values[4], 5) + " to " + DoubleToString(atr_values[0], 5) + ") over 5 periods"; expected_effect = "Expect breakout following volatility contraction, typically with strong directional movement"; cause_effect_report += "Volatility Contraction Breakout | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } } } // 3. Trend Alignment Cause MqlRates rates[10]; if(CopyRates(InpSymbol, InpTF, 0, 10, rates) >= 10) { // Simple trend detection using moving average of closes double sum = 0; for(int i = 0; i < 10; i++) sum += rates[i].close; double ma = sum / 10; double price_deviation = (current_rate.close - ma) / ma * 100; if(type == ORDER_TYPE_BUY && current_rate.close > ma) { detailed_cause = "Price (" + DoubleToString(current_rate.close, 5) + ") is above 10-period MA (" + DoubleToString(ma, 5) + ") by " + DoubleToString(price_deviation, 1) + "%"; expected_effect = "Expect continuation of uptrend as price maintains position above moving average"; cause_effect_report += "Trend Alignment (Above MA) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } else if(type == ORDER_TYPE_SELL && current_rate.close < ma) { detailed_cause = "Price (" + DoubleToString(current_rate.close, 5) + ") is below 10-period MA (" + DoubleToString(ma, 5) + ") by " + DoubleToString(MathAbs(price_deviation), 1) + "%"; expected_effect = "Expect continuation of downtrend as price maintains position below moving average"; cause_effect_report += "Trend Alignment (Below MA) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } } // 4. Time-based Cause (Session overlap) MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; if((hour >= 1 && hour <= 4) || (hour >= 7 && hour <= 10)) { detailed_cause = "Entry during session overlap hours (" + IntegerToString(hour) + ":00)"; if(hour >= 1 && hour <= 4) expected_effect = "Expect increased volatility and potential breakout during Asian/London session overlap"; else expected_effect = "Expect increased volatility and potential breakout during London/NY session overlap"; cause_effect_report += "Session Overlap Breakout | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } // 5. Price Action Pattern Recognition MqlRates current_bars[3]; if(CopyRates(InpSymbol, InpTF, 0, 3, current_bars) >= 3) { // Check for bullish engulfing pattern if(type == ORDER_TYPE_BUY && current_bars[0].close > current_bars[0].open && current_bars[1].close < current_bars[1].open && current_bars[0].open < current_bars[1].close && current_bars[0].close > current_bars[1].open) { detailed_cause = "Bullish engulfing pattern detected at potential support"; expected_effect = "Expect reversal to the upside following bullish engulfing pattern confirmation"; cause_effect_report += "Price Action (Bullish Engulfing) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } // Check for bearish engulfing pattern if(type == ORDER_TYPE_SELL && current_bars[0].close < current_bars[0].open && current_bars[1].close > current_bars[1].open && current_bars[0].open > current_bars[1].close && current_bars[0].close < current_bars[1].open) { detailed_cause = "Bearish engulfing pattern detected at potential resistance"; expected_effect = "Expect reversal to the downside following bearish engulfing pattern confirmation"; cause_effect_report += "Price Action (Bearish Engulfing) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } } // Default return if no specific cause identified detailed_cause = "No clear technical catalyst identified for entry at " + DoubleToString(price, 5); expected_effect = "Further analysis required to determine entry rationale"; cause_effect_report += "Technical Breakout (Unspecified Cause) | Cause: " + detailed_cause + " | Effect: " + expected_effect; return cause_effect_report; } //+------------------------------------------------------------------+ //| Villahermosa Projected Effect Analysis | //+------------------------------------------------------------------+ string VillahermosaProjectedEffectAnalysis(ENUM_ORDER_TYPE type, double entry_price, double atr) { string analysis = "Villahermosa Projected Effect Analysis: "; // Calculate projected targets double stop_loss = (type == ORDER_TYPE_BUY) ? entry_price - InpATRStopMult * atr : entry_price + InpATRStopMult * atr; double take_profit = (type == ORDER_TYPE_BUY) ? entry_price + (2 * InpATRStopMult * atr) : entry_price - (2 * InpATRStopMult * atr); double point = SymbolInfoDouble(InpSymbol, SYMBOL_POINT); double risk_pips = MathAbs(entry_price - stop_loss) / point; double reward_pips = MathAbs(take_profit - entry_price) / point; double risk_reward = reward_pips / risk_pips; analysis += "Entry: " + DoubleToString(entry_price, 5) + " | SL: " + DoubleToString(stop_loss, 5) + " | TP: " + DoubleToString(take_profit, 5) + " | Risk: " + DoubleToString(risk_pips, 1) + " pips" + " | Reward: " + DoubleToString(reward_pips, 1) + " pips" + " | R/R: " + DoubleToString(risk_reward, 2) + ":1"; // Market context analysis double volatility_ratio = CalculateMarketVolatility(); if(volatility_ratio > 1.5) { analysis += " | High volatility environment - expect larger price swings"; } else if(volatility_ratio < 0.7) { analysis += " | Low volatility environment - expect slower price movement"; } // Session context MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; if((hour >= 8 && hour <= 12) || (hour >= 14 && hour <= 17)) { analysis += " | High liquidity session - better execution expected"; } else if(hour >= 21 || hour <= 5) { analysis += " | Low liquidity session - potential for wider spreads"; } return analysis; } //+------------------------------------------------------------------+ //| Calculate Trade Quality Score | //| Scores trade quality based on cause and projected effect | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ int CalculateTradeQualityScore(string cause, string projected_effect) { int score = 50; // Base score string scoring_report = "Trade Quality Score Analysis: "; string scoring_details = ""; // Extract base cause from detailed cause string (if it contains full analysis) string base_cause = cause; if(StringFind(cause, "|") >= 0) { int separator_pos = StringFind(cause, "|"); base_cause = StringSubstr(cause, 0, separator_pos); StringTrimLeft(base_cause); StringTrimRight(base_cause); } // 1. Score based on entry cause string cause_scoring = ""; if(base_cause == "Donchian Breakout (Upper Channel)" || base_cause == "Donchian Breakout (Lower Channel)") { score += 25; cause_scoring = "Strong entry cause (+25) - High probability Donchian breakout setup"; } else if(base_cause == "Volatility Expansion Breakout") { score += 20; cause_scoring = "Good entry cause (+20) - Entered during volatility expansion phase"; } else if(base_cause == "Trend Alignment (Above MA)" || base_cause == "Trend Alignment (Below MA)") { score += 15; cause_scoring = "Moderate entry cause (+15) - Trade aligns with established trend"; } else if(base_cause == "Session Overlap Breakout") { score += 10; cause_scoring = "Basic entry cause (+10) - Entered during high-probability session overlap"; } else { cause_scoring = "Unknown entry cause (0) - No additional points for entry quality"; } scoring_details += "Entry: " + cause_scoring; // 2. Score based on projected effect string effect_scoring = ""; // Extract base effect from detailed effect string (if it contains full analysis) string base_effect = projected_effect; if(StringFind(projected_effect, "|") >= 0) { int separator_pos = StringFind(projected_effect, "|"); base_effect = StringSubstr(projected_effect, 0, separator_pos); StringTrimLeft(base_effect); StringTrimRight(base_effect); } if(base_effect == "London Session - Expect Strong Momentum") { score += 15; effect_scoring = "Favorable session conditions (+15) - London session typically provides strong momentum"; } else if(base_effect == "NY Session - Expect High Liquidity") { score += 10; effect_scoring = "Good session conditions (+10) - NY session provides high liquidity for efficient execution"; } else if(base_effect == "High Volatility - Expect Large Moves") { score += 5; effect_scoring = "Moderate conditions (+5) - High volatility offers opportunity but also increased risk"; } else if(base_effect == "Low Volatility - Expect Slow Grind") { score -= 10; effect_scoring = "Challenging conditions (-10) - Low volatility may limit profit potential"; } else { // Handle detailed effect analysis reports if(StringFind(projected_effect, "High probability setup") >= 0) { score += 20; effect_scoring = "Excellent projected conditions (+20) - Multiple factors align for high probability outcome"; } else if(StringFind(projected_effect, "Strong directional movement") >= 0) { score += 15; effect_scoring = "Favorable projected conditions (+15) - Expect strong directional movement"; } else if(StringFind(projected_effect, "Limited movement") >= 0) { score -= 5; effect_scoring = "Limited projected conditions (-5) - Expect constrained price movement"; } else { effect_scoring = "Standard projected conditions (0) - No significant adjustment for projected effect"; } } scoring_details += " | Projection: " + effect_scoring; // 3. Score based on market volatility double volatility_ratio = CalculateMarketVolatility(); string volatility_scoring = ""; if(volatility_ratio >= 0.8 && volatility_ratio <= 1.2) { score += 10; volatility_scoring = "Optimal volatility (+10) - Normal volatility conditions ideal for trend following"; } else if(volatility_ratio > 2.0) { score -= 15; volatility_scoring = "Excessive volatility (-15) - Extreme volatility increases risk and potential slippage"; } else if(volatility_ratio > 1.5) { score += 5; volatility_scoring = "Elevated volatility (+5) - Higher volatility offers opportunity with managed risk"; } else if(volatility_ratio < 0.5) { score -= 5; volatility_scoring = "Low volatility (-5) - Reduced volatility may limit profit potential"; } else { volatility_scoring = "Normal volatility (0) - No adjustment for volatility conditions"; } scoring_details += " | Volatility: " + volatility_scoring + " (Ratio: " + DoubleToString(volatility_ratio, 2) + ")"; // 4. Additional factor: Time of day analysis MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; string time_scoring = ""; if((hour >= 8 && hour <= 12) || (hour >= 14 && hour <= 17)) { score += 5; time_scoring = "Favorable trading hours (+5) - High liquidity sessions typically provide better execution"; } else if(hour >= 21 || hour <= 5) { score -= 5; time_scoring = "Challenging trading hours (-5) - Low liquidity sessions may increase slippage risk"; } else { time_scoring = "Standard trading hours (0) - No adjustment for time of day"; } scoring_details += " | Time: " + time_scoring; // 5. Additional factor: Risk-reward alignment string risk_reward_scoring = ""; // Check if we're in a high probability setup for better risk-reward if((base_cause == "Donchian Breakout (Upper Channel)" || base_cause == "Donchian Breakout (Lower Channel)") && (base_effect == "London Session - Expect Strong Momentum" || StringFind(projected_effect, "High probability setup") >= 0)) { score += 10; risk_reward_scoring = "Optimal risk-reward alignment (+10) - High probability setup with favorable conditions"; } else if(base_cause == "Session Overlap Breakout" && (hour >= 21 || hour <= 5)) { score -= 5; risk_reward_scoring = "Poor risk-reward alignment (-5) - Lower probability setup with challenging conditions"; } else { risk_reward_scoring = "Standard risk-reward alignment (0) - No additional adjustment"; } scoring_details += " | Risk-Reward: " + risk_reward_scoring; // Ensure score is within bounds score = MathMin(MathMax(score, 0), 100); // Determine quality rating based on final score string quality_rating = ""; if(score >= 90) quality_rating = "Exceptional Quality"; else if(score >= 80) quality_rating = "High Quality"; else if(score >= 70) quality_rating = "Good Quality"; else if(score >= 60) quality_rating = "Average Quality"; else if(score >= 50) quality_rating = "Below Average Quality"; else quality_rating = "Poor Quality"; // Final cause and effect report scoring_report += scoring_details + " | Final Score: " + IntegerToString(score) + "/100 (" + quality_rating + ")"; WriteLog(scoring_report); return score; } //+------------------------------------------------------------------+ //| Villahermosa Closure Cause Analysis | //| Determines if a position should be closed based on cause-effect | //| with comprehensive Cause & Effect analysis | //+------------------------------------------------------------------+ string VillahermosaClosureCauseAnalysis(ulong ticket, ENUM_POSITION_TYPE type, double open_price, double current_price, double profit, long position_age, string closure_reason) { string cause_effect_report = "Closure Cause Analysis: "; string detailed_cause = ""; string closure_effect = ""; string market_context = ""; string risk_assessment = ""; // Calculate key metrics for decision making double point = SymbolInfoDouble(InpSymbol, SYMBOL_POINT); double price_change_pips = MathAbs(current_price - open_price) / point; double profit_ratio = (AccountInfoDouble(ACCOUNT_EQUITY) != 0) ? (MathAbs(profit) / AccountInfoDouble(ACCOUNT_EQUITY)) * 100 : 0; double hours_open = position_age / 3600.0; // Get current market context double volatility_ratio = CalculateMarketVolatility(); MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; // Determine market context for cause analysis if(volatility_ratio > 2.0) market_context = "extremely volatile market (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; else if(volatility_ratio > 1.5) market_context = "highly volatile market (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; else if(volatility_ratio < 0.5) market_context = "low volatility market (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; else market_context = "normal volatility market"; // Add session context if((hour >= 8 && hour <= 12) || (hour >= 14 && hour <= 17)) market_context += " during high liquidity session"; else if(hour >= 1 && hour <= 4) market_context += " during Asian session"; else if(hour >= 21 || hour <= 5) market_context += " during low liquidity session"; // 1. Check for Donchian exit condition (primary Villahermosa exit signal) double exit_upper = Donchian(InpSymbol, InpTF, InpDonchianExit, MODE_HIGH, 1); double exit_lower = Donchian(InpSymbol, InpTF, InpDonchianExit, MODE_LOW, 1); if((type == POSITION_TYPE_BUY && current_price < exit_lower) || (type == POSITION_TYPE_SELL && current_price > exit_upper)) { double distance_to_exit = 0; if(type == POSITION_TYPE_BUY) distance_to_exit = (exit_lower - current_price) / point; else distance_to_exit = (current_price - exit_upper) / point; detailed_cause = "Price action violated Donchian exit channel in " + market_context + ". Price (" + DoubleToString(current_price, 5) + ") crossed " + (type == POSITION_TYPE_BUY ? "lower" : "upper") + " exit band (" + DoubleToString((type == POSITION_TYPE_BUY ? exit_lower : exit_upper), 5) + ") by " + DoubleToString(distance_to_exit, 1) + " pips"; closure_effect = "Systematic exit triggered to preserve capital as trend reversal is indicated. " + "This prevents further drawdown in a changing market environment"; risk_assessment = "Risk mitigation: Prevents potential additional loss of " + DoubleToString(distance_to_exit * 2, 1) + " pips if trend continues"; cause_effect_report += "Donchian Exit Signal | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } // 2. Check for profit target achievement double tp_distance_pips = 0; if(type == POSITION_TYPE_BUY && current_price > open_price) tp_distance_pips = (current_price - open_price) / point; else if(type == POSITION_TYPE_SELL && current_price < open_price) tp_distance_pips = (open_price - current_price) / point; // Villahermosa Profit Target: 1.5x ATR as minimum profit target double atr_value[1]; if(CopyBuffer(atr_handle, 0, 0, 1, atr_value) >= 1) { double atr_pips = atr_value[0] / point; if(tp_distance_pips >= (atr_pips * 1.5)) { detailed_cause = "Position achieved profit target of " + DoubleToString(tp_distance_pips, 1) + " pips (1.5x ATR of " + DoubleToString(atr_pips, 1) + " pips) in " + market_context; closure_effect = "Take profit executed to lock in gains at predefined risk-adjusted target. " + "This realizes profits while they exist and frees capital for new opportunities"; risk_assessment = "Reward captured: " + DoubleToString(profit, 2) + " (" + DoubleToString(profit_ratio, 2) + "% of equity) with " + DoubleToString(tp_distance_pips/atr_pips, 1) + "x risk-to-reward ratio"; cause_effect_report += "Profit Target Achieved | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } } // 3. Check for time-based exit (Villahermosa Time Stop) if(hours_open > 72) // 3-day maximum hold time { detailed_cause = "Position age exceeded maximum hold time of 72 hours. " + "Position has been open for " + DoubleToString(hours_open, 1) + " hours in " + market_context; closure_effect = "Time stop activated to prevent overexposure to single position. " + "Extended holding periods increase risk without corresponding reward potential"; risk_assessment = "Time risk: Position held " + DoubleToString(hours_open - 72, 1) + " hours beyond optimal timeframe, increasing exposure to unexpected market events"; cause_effect_report += "Time Stop Activated | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } // 4. Check for volatility-based exit if(volatility_ratio > 2.0) // Volatility more than doubled { detailed_cause = "Market volatility increased significantly to " + DoubleToString(volatility_ratio, 2) + "x normal levels in " + market_context + ". Current ATR: " + DoubleToString(atr_value[0], 5); closure_effect = "Excessive volatility exit triggered to protect from unpredictable price swings. " + "High volatility environments increase slippage and reduce strategy effectiveness"; risk_assessment = "Volatility risk: " + DoubleToString(volatility_ratio, 1) + "x normal volatility increases potential loss by " + DoubleToString(volatility_ratio * 100 - 100, 0) + "%"; cause_effect_report += "Excessive Volatility | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } // 5. Check for trend reversal exit if(IsTrendReversed(type, (datetime)PositionGetInteger(POSITION_TIME))) { detailed_cause = "Market trend reversed against position direction in " + market_context; closure_effect = "Trend reversal exit executed as original trade thesis is no longer valid. " + "Continuing to hold against the trend increases risk disproportionately to reward"; risk_assessment = "Trend risk: Holding against established trend could result in " + DoubleToString(price_change_pips * 2, 1) + " pips additional drawdown"; cause_effect_report += "Trend Reversal | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } // 6. Check for manual closure reasons if(closure_reason == "Drawdown Protection" && profit < 0) { detailed_cause = "Drawdown protection triggered with " + DoubleToString(profit_ratio, 2) + "% equity loss on this position in " + market_context; closure_effect = "Risk management protocol activated to limit further losses according to " + "Villahermosa drawdown rules. This preserves capital for future opportunities"; risk_assessment = "Drawdown risk: Continuing could result in additional " + DoubleToString(profit_ratio * 1.5, 2) + "% equity loss based on current market conditions"; cause_effect_report += "Drawdown Protection | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } else if(closure_reason == "Strategy Shutdown") { detailed_cause = "Strategy shutdown initiated in " + market_context; closure_effect = "Systematic position closure due to strategy termination. " + "All positions are being closed to ensure clean exit and prevent orphaned positions"; risk_assessment = "Systemic risk: Strategy termination prevents further position management, " + "making open positions vulnerable to market moves without oversight"; cause_effect_report += "Strategy Shutdown | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } else if(closure_reason == "Manual Closure") { detailed_cause = "Manual intervention requested in " + market_context; closure_effect = "Position closed by trader discretion outside of systematic rules. " + "This may be due to external factors not captured by automated analysis"; risk_assessment = "Discretionary action: Manual override of system rules, " + "typically in response to unique market conditions or external events"; cause_effect_report += "Manual Intervention | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } // 7. Check for significant adverse movement without hitting stop loss double adverse_movement = 0; if((type == POSITION_TYPE_BUY && current_price < open_price) || (type == POSITION_TYPE_SELL && current_price > open_price)) { adverse_movement = (type == POSITION_TYPE_BUY) ? (open_price - current_price) / point : (current_price - open_price) / point; } if(adverse_movement > 50) // More than 50 pips adverse movement { detailed_cause = "Significant adverse movement of " + DoubleToString(adverse_movement, 1) + " pips against position without hitting stop loss in " + market_context; closure_effect = "Discretionary exit due to substantial adverse price action beyond normal expectations. " + "This prevents further loss in an unexpectedly unfavorable move"; risk_assessment = "Adverse movement risk: " + DoubleToString(adverse_movement, 1) + " pips against position suggests potential for additional " + DoubleToString(adverse_movement * 0.5, 1) + " pips drawdown"; cause_effect_report += "Adverse Movement | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } // 8. Check for low momentum or stagnation if(hours_open > 24 && MathAbs(profit_ratio) < 0.5) { detailed_cause = "Position stagnation: " + DoubleToString(hours_open, 1) + " hours open with minimal progress (" + DoubleToString(profit_ratio, 2) + "% equity change) in " + market_context; closure_effect = "Opportunity cost exit to free capital for more productive opportunities. " + "Stagnant positions tie up capital that could be deployed more effectively elsewhere"; risk_assessment = "Opportunity cost: Capital tied in stagnant position could potentially generate " + DoubleToString(profit_ratio * 2, 2) + "% in more favorable market conditions"; cause_effect_report += "Position Stagnation | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } // Default: Do not close if no Villahermosa exit condition is met detailed_cause = "No systematic closure conditions met in " + market_context + ". Position remains within acceptable parameters after " + DoubleToString(hours_open, 1) + " hours"; closure_effect = "Position should be maintained according to Villahermosa methodology. " + "Current market conditions and position performance don't warrant exit"; risk_assessment = "Retention benefit: Maintaining position could result in " + DoubleToString(price_change_pips * 0.5, 1) + " pips additional profit " + "based on current trajectory"; cause_effect_report += "Do Not Close | Cause: " + detailed_cause + " | Effect: " + closure_effect + " | Risk: " + risk_assessment; return cause_effect_report; } //+------------------------------------------------------------------+ //| Villahermosa Position Health Check | //| Analyzes position health with cause & effect analysis | //+------------------------------------------------------------------+ string VillahermosaPositionHealthCheck(ulong ticket, ENUM_POSITION_TYPE type, double open_price, double current_price, double profit, long position_age) { string cause_effect_report = "Position Health Analysis: "; string health_status = ""; string detailed_cause = ""; string recommended_effect = ""; double hours_open = position_age / 3600.0; double price_change = (type == POSITION_TYPE_BUY) ? (current_price - open_price) / open_price * 10000 : (open_price - current_price) / open_price * 10000; // Get current market volatility double atr_values[1]; double volatility_ratio = 1.0; if(CopyBuffer(atr_handle, 0, 0, 1, atr_values) == 1) { double atr_avg_values[20]; if(CopyBuffer(atr_handle, 0, 0, 20, atr_avg_values) == 20) { double atr_avg = ArrayMean(atr_avg_values); volatility_ratio = atr_values[0] / atr_avg; } } // Check trend alignment bool is_trend_aligned = IsTrendAligned(type); // Analyze profit/loss status with cause-effect if(profit > 0) { health_status = "Profitable Position"; if(hours_open < 12) { detailed_cause = "Position in early profit phase (" + DoubleToString(hours_open, 1) + " hours)"; if(is_trend_aligned && volatility_ratio < 1.5) recommended_effect = "Let position develop - trend aligned with low volatility"; else recommended_effect = "Monitor closely - consider partial profit taking due to " + (volatility_ratio >= 1.5 ? "high volatility" : "trend divergence"); } else if(hours_open < 48) { detailed_cause = "Position in mature profit phase (" + DoubleToString(hours_open, 1) + " hours)"; recommended_effect = "Implement trailing stop to protect gains while allowing upside"; } else { detailed_cause = "Position in extended profit phase (" + DoubleToString(hours_open, 1) + " hours)"; recommended_effect = "Close portion of position to realize gains and reduce exposure"; } } else { health_status = "Unprofitable Position"; if(hours_open < 6) { detailed_cause = "Early drawdown phase (" + DoubleToString(hours_open, 1) + " hours)"; recommended_effect = "Normal market noise - maintain position with stop loss protection"; } else if(hours_open < 24) { detailed_cause = "Moderate drawdown phase (" + DoubleToString(hours_open, 1) + " hours)"; if(is_trend_aligned) recommended_effect = "Trend remains aligned - hold position with close monitoring"; else recommended_effect = "Trend divergence detected - consider reducing position size"; } else { detailed_cause = "Extended drawdown phase (" + DoubleToString(hours_open, 1) + " hours)"; recommended_effect = "Review initial entry thesis - consider closing if market conditions changed"; } } // Add volatility analysis to cause if(volatility_ratio > 2.0) detailed_cause += " with extreme volatility (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; else if(volatility_ratio > 1.5) detailed_cause += " with high volatility (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; else if(volatility_ratio < 0.5) detailed_cause += " with low volatility (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; // Compile final cause-effect report cause_effect_report += health_status + " | Cause: " + detailed_cause + " | Effect: " + recommended_effect; return cause_effect_report; } //+------------------------------------------------------------------+ //| Helper function to calculate array mean | //+------------------------------------------------------------------+ double ArrayMean(double &arr[]) { double sum = 0.0; for(int i = 0; i < ArraySize(arr); i++) sum += arr[i]; return sum / ArraySize(arr); } //+------------------------------------------------------------------+ //| Check if current trend aligns with position direction | //+------------------------------------------------------------------+ bool IsTrendAligned(ENUM_POSITION_TYPE type) { double ma_fast = iMA(InpSymbol, InpTF, 20, 0, MODE_SMA, PRICE_CLOSE, 1); double ma_slow = iMA(InpSymbol, InpTF, 50, 0, MODE_SMA, PRICE_CLOSE, 1); if(type == POSITION_TYPE_BUY) return (ma_fast > ma_slow); else return (ma_fast < ma_slow); } //+------------------------------------------------------------------+ //| Determine current position direction with Villahermosa methodology| //| Returns: 1 for long, -1 for short, 0 for no position | //| Outputs: reason - detailed explanation of position analysis | //+------------------------------------------------------------------+ int PositionDirection(string &reason = "") { if(PositionsTotal() == 0) { reason = "No positions found in account"; return 0; } int villahermosa_score = 0; string analysis_report = ""; int our_positions = 0; int net_direction = 0; double total_profit = 0; double total_exposure = 0; string portfolio_cause = ""; string portfolio_effect = ""; // Get current market context for cause-effect analysis string market_context = AnalyzeMarketContext(); for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == InpSymbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { our_positions++; ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // Get position details for Villahermosa analysis double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); double profit = PositionGetDouble(POSITION_PROFIT); double volume = PositionGetDouble(POSITION_VOLUME); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); datetime open_time = (datetime)PositionGetInteger(POSITION_TIME); long position_age = TimeCurrent() - open_time; total_profit += profit; total_exposure += volume * current_price; // Villahermosa Cause Analysis: Why was this position opened? string entry_cause = VillahermosaEntryCauseAnalysis(open_time, open_price, type); // Villahermosa Effect Analysis: How is this position performing? string effect_analysis = VillahermosaEffectAnalysis(ticket, type, open_price, current_price, profit, position_age); // Calculate Villahermosa validation score (0-100) int position_score = CalculateVillahermosaScore(entry_cause, effect_analysis, profit, position_age); villahermosa_score += position_score; if(type == POSITION_TYPE_BUY) { net_direction += 1; analysis_report += "Long #" + IntegerToString(ticket) + " | Cause: " + entry_cause + " | Effect: " + effect_analysis + " | Score: " + IntegerToString(position_score) + "/100" + " | P/L: " + DoubleToString(profit, 2) + "; "; } else if(type == POSITION_TYPE_SELL) { net_direction -= 1; analysis_report += "Short #" + IntegerToString(ticket) + " | Cause: " + entry_cause + " | Effect: " + effect_analysis + " | Score: " + IntegerToString(position_score) + "/100" + " | P/L: " + DoubleToString(profit, 2) + "; "; } } } if(our_positions == 0) { reason = "No positions with our magic number and symbol"; return 0; } // Calculate average Villahermosa score villahermosa_score /= our_positions; // Determine portfolio cause (why these positions exist) if(net_direction > 0) { portfolio_cause = "Net long bias (" + IntegerToString(net_direction) + " positions) due to bullish market conditions identified " + IntegerToString(our_positions) + " times"; } else if(net_direction < 0) { portfolio_cause = "Net short bias (" + IntegerToString(MathAbs(net_direction)) + " positions) due to bearish market conditions identified " + IntegerToString(our_positions) + " times"; } else { portfolio_cause = "Market neutral position (hedged) due to mixed signals across " + IntegerToString(our_positions) + " positions"; } // Add market context to portfolio cause portfolio_cause += " in " + market_context + " market environment"; // Determine portfolio effect (what impact these positions are having) double profit_percentage = (AccountInfoDouble(ACCOUNT_EQUITY) != 0) ? (total_profit / AccountInfoDouble(ACCOUNT_EQUITY)) * 100 : 0; if(total_profit > 0) { portfolio_effect = "Positions contributing +" + DoubleToString(profit_percentage, 2) + "% to equity with an average quality score of " + IntegerToString(villahermosa_score) + "/100"; } else { portfolio_effect = "Positions resulting in " + DoubleToString(profit_percentage, 2) + "% drawdown with an average quality score of " + IntegerToString(villahermosa_score) + "/100"; } // Add risk exposure to effect analysis double exposure_percentage = (AccountInfoDouble(ACCOUNT_EQUITY) != 0) ? (total_exposure / AccountInfoDouble(ACCOUNT_EQUITY)) * 100 : 0; portfolio_effect += " | Risk exposure: " + DoubleToString(exposure_percentage, 1) + "% of equity"; // Determine overall position direction with Villahermosa context string direction_quality = ""; if(villahermosa_score >= 80) direction_quality = "High Quality"; else if(villahermosa_score >= 60) direction_quality = "Medium Quality"; else direction_quality = "Low Quality"; // Set the comprehensive reason with cause-effect framework reason = "Villahermosa Portfolio Analysis | Cause: " + portfolio_cause + " | Effect: " + portfolio_effect + " | Direction: " + (net_direction > 0 ? "Net Long" : (net_direction < 0 ? "Net Short" : "Neutral")) + " | Quality: " + direction_quality + " | Details: " + analysis_report; // Return net direction (positive for net long, negative for net short) if(net_direction > 0) return 1; if(net_direction < 0) return -1; return 0; // Shouldn't happen if our_positions > 0 } //+------------------------------------------------------------------+ //| Analyze market context for drawdown analysis | //+------------------------------------------------------------------+ string AnalyzeMarketContext() { MqlRates current; ArraySetAsSeries(¤t, true); string context = ""; // Get current market information if(CopyRates(InpSymbol, InpTF, 0, 1, ¤t) > 0) { // Basic trend detection MqlRates prev; if(CopyRates(InpSymbol, InpTF, 1, 1, &prev) > 0) { if(current[0].close > prev[0].close) context += "rising market"; else if(current[0].close < prev[0].close) context += "falling market"; else context += "sideways market"; } // Add volatility context double atr_val[1]; if(CopyBuffer(atr_handle, 0, 0, 1, atr_val) == 1) { double atr_pips = atr_val[0] / SymbolInfoDouble(InpSymbol, SYMBOL_POINT); if(atr_pips > 50) context += " with high volatility"; else if(atr_pips > 30) context += " with moderate volatility"; else context += " with low volatility"; } } else { context = "uncertain market"; } return context; } //+------------------------------------------------------------------+ //| Analyze time context for drawdown analysis | //+------------------------------------------------------------------+ string AnalyzeTimeContext() { MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; string time_context = ""; if(hour >= 8 && hour <= 12) time_context = "London session hours"; else if(hour >= 13 && hour <= 17) time_context = "New York session hours"; else if(hour >= 1 && hour <= 4) time_context = "Asian session hours"; else if(hour >= 21 || hour <= 5) time_context = "low liquidity hours"; else time_context = "standard trading hours"; return time_context; } //+------------------------------------------------------------------+ //| Close all positions with Villahermosa cause-and-effect logic | //+------------------------------------------------------------------+ void CloseAllPositions(string closure_reason = "Manual Closure") { int positions_closed = 0; int positions_maintained = 0; double total_profit = 0; double total_risk_released = 0; string closure_details = ""; string villahermosa_analysis = ""; // Get current market context for cause-effect analysis string market_context = AnalyzeMarketContext(); double current_drawdown = CalculateCurrentDrawdown(); string operation_cause = "Closure operation initiated: " + closure_reason; string operation_effect = ""; string risk_impact = ""; // Analyze portfolio before closure double initial_exposure = CalculatePortfolioExposure(); int initial_positions = PositionsTotal(); for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == InpSymbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { // Get position details for Villahermosa analysis ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); double profit = PositionGetDouble(POSITION_PROFIT); double volume = PositionGetDouble(POSITION_VOLUME); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); datetime open_time = (datetime)PositionGetInteger(POSITION_TIME); long position_age = TimeCurrent() - open_time; // Calculate position risk exposure double position_risk = CalculatePositionRisk(ticket, type, volume, open_price, current_price); // Villahermosa Closure Analysis: Should this position be closed? string closure_cause = VillahermosaClosureCauseAnalysis(ticket, type, open_price, current_price, profit, position_age, closure_reason); // Only close if Villahermosa methodology confirms the closure if(closure_cause != "Do Not Close") { if(trade.PositionClose(ticket)) { positions_closed++; total_profit += profit; total_risk_released += position_risk; // Add cause-effect analysis for this closure string position_effect = AnalyzeClosureEffect(profit, position_risk, position_age); closure_details += "Closed " + (type == POSITION_TYPE_BUY ? "Long" : "Short") + " #" + IntegerToString(ticket) + " | P/L: " + DoubleToString(profit, 2) + " | Risk Released: " + DoubleToString(position_risk, 2) + " | Cause: " + closure_cause + " | Effect: " + position_effect + "; "; if(InpEnableLogging) { WriteLog("Position closed: " + IntegerToString(ticket) + " | P/L: " + DoubleToString(profit, 2) + " | Villahermosa Cause: " + closure_cause + " | Effect: " + position_effect); } } else { WriteLog("Error closing position " + IntegerToString(ticket) + ": " + IntegerToString(trade.ResultRetcode()) + " | Villahermosa Cause: " + closure_cause); } } else { positions_maintained++; string health_status = VillahermosaPositionHealthCheck(ticket, type, open_price, current_price, profit, position_age); villahermosa_analysis += "Position #" + IntegerToString(ticket) + " maintained | Health: " + health_status + "; "; } } } // Calculate operation effects double final_exposure = CalculatePortfolioExposure(); double exposure_reduction = initial_exposure - final_exposure; double exposure_reduction_pct = (initial_exposure > 0) ? (exposure_reduction / initial_exposure) * 100 : 0; // Determine overall operation effect if(positions_closed > 0) { operation_effect = "Released " + DoubleToString(total_risk_released, 2) + " in risk exposure (" + DoubleToString(exposure_reduction_pct, 1) + "% reduction) with " + DoubleToString(total_profit, 2) + " total P/L"; risk_impact = "Risk management effect: " + AnalyzeRiskImpact(exposure_reduction, total_profit); } else { operation_effect = "No positions closed - all positions meet Villahermosa retention criteria"; risk_impact = "Risk profile unchanged - maintaining current exposure of " + DoubleToString(final_exposure, 2); } // Generate comprehensive closure report with cause-effect framework string closure_report = "Villahermosa Closure Operation | Cause: " + operation_cause + " | Effect: " + operation_effect + " | Market Context: " + market_context + " | Current Drawdown: " + DoubleToString(current_drawdown*100, 1) + "%" + " | Positions: " + IntegerToString(positions_closed) + " closed, " + IntegerToString(positions_maintained) + " maintained" + " | Risk Impact: " + risk_impact; if(closure_details != "") closure_report += " | Closure Details: " + closure_details; if(villahermosa_analysis != "") closure_report += " | Maintained Analysis: " + villahermosa_analysis; // Log and alert the closure report if(InpEnableLogging) WriteLog(closure_report); if(InpEnableAlerts && positions_closed > 0) Alert(closure_report); } //+------------------------------------------------------------------+ //| Calculate Position Risk | //+------------------------------------------------------------------+ double CalculatePositionRisk(ulong ticket, ENUM_POSITION_TYPE type, double volume, double open_price, double current_price) { double point = SymbolInfoDouble(InpSymbol, SYMBOL_POINT); double tick_value = SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_VALUE_LOT); double price_diff = MathAbs(current_price - open_price); return (price_diff / point) * tick_value * volume; } //+------------------------------------------------------------------+ //| Calculate portfolio exposure | //+------------------------------------------------------------------+ double CalculatePortfolioExposure() { double total_exposure = 0; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == InpSymbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double volume = PositionGetDouble(POSITION_VOLUME); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); total_exposure += CalculatePositionRisk(ticket, type, volume, open_price, current_price); } } return total_exposure; } //+------------------------------------------------------------------+ //| Analyze closure effect for individual position | //+------------------------------------------------------------------+ string AnalyzeClosureEffect(double profit, double risk_released, long position_age) { double hours_open = position_age / 3600.0; string effect = ""; if(profit > 0) { effect = "Realized gain of " + DoubleToString(profit, 2) + " after " + DoubleToString(hours_open, 1) + " hours, freeing " + DoubleToString(risk_released, 2) + " in risk capital"; } else { effect = "Limited loss to " + DoubleToString(profit, 2) + " after " + DoubleToString(hours_open, 1) + " hours, preventing further risk of " + DoubleToString(risk_released, 2); } return effect; } //+------------------------------------------------------------------+ //| Analyze risk impact of closure operation | //+------------------------------------------------------------------+ string AnalyzeRiskImpact(double exposure_reduction, double total_profit) { string impact = ""; double profit_ratio = (exposure_reduction > 0) ? total_profit / exposure_reduction : 0; if(exposure_reduction > 0) { if(total_profit > 0) { impact = "Positive risk-adjusted outcome: " + DoubleToString(profit_ratio*100, 1) + "% return on risk released"; } else { impact = "Defensive risk reduction: limited loss to " + DoubleToString(total_profit, 2) + " while reducing exposure by " + DoubleToString(exposure_reduction, 2); } } else { impact = "No change in risk exposure - operation completed without modifying risk profile"; } return impact; } //+------------------------------------------------------------------+ //| Open a new position with Villahermosa cause & effect analysis | //+------------------------------------------------------------------+ bool OpenPosition(ENUM_ORDER_TYPE type, double lots, double atr, string trade_cause_param) { // Validate input parameters if(!ValidateATRValue(atr, "OpenPosition")) { WriteLog("Invalid ATR value in OpenPosition: " + DoubleToString(atr)); return false; } if(lots <= 0) { WriteLog("Invalid lot size in OpenPosition: " + DoubleToString(lots)); return false; } // Get current price with error handling double price; if(type == ORDER_TYPE_BUY) { price = SafeSymbolInfoDouble(InpSymbol, SYMBOL_ASK, 0); } else { price = SafeSymbolInfoDouble(InpSymbol, SYMBOL_BID, 0); } if(!ValidatePrice(price, "OpenPosition")) { WriteLog("Invalid price in OpenPosition: " + DoubleToString(price)); return false; } // Calculate stop loss double sl = (type == ORDER_TYPE_BUY) ? price - InpATRStopMult * atr : price + InpATRStopMult * atr; // Validate stop loss if(!ValidatePrice(sl, "Stop Loss calculation")) { WriteLog("Invalid stop loss calculated: " + DoubleToString(sl)); return false; } // Calculate risk-adjusted lot size with Villahermosa methodology double equity = AccountInfoDouble(ACCOUNT_EQUITY); if(equity <= 0) { WriteLog("Invalid equity value: " + DoubleToString(equity)); return false; } double risk_adjusted_lots = RiskCapLots(equity, atr, InpATRStopMult, type, trade_cause_param); // Use the minimum of volatility-targeted and risk-adjusted lots double final_lots = MathMin(lots, risk_adjusted_lots); final_lots = NormalizeLots(final_lots); if(final_lots <= 0) { WriteLog("Invalid final lot size after normalization: " + DoubleToString(final_lots)); return false; } //--- Villahermosa Cause Analysis: Determine the fundamental reason for this trade string trade_cause = VillahermosaTradeCauseAnalysis(type, price); //--- Villahermosa Effect Analysis: Projected outcome based on current market conditions string projected_effect = VillahermosaProjectedEffectAnalysis(type, price, atr); //--- Calculate Villahermosa Quality Score for this trade opportunity int quality_score = CalculateTradeQualityScore(trade_cause, projected_effect); //--- Check margin requirements double margin_required; if(!OrderCalcMargin((ENUM_ORDER_TYPE)type, InpSymbol, final_lots, price, margin_required)) { int error = GetLastError(); WriteLog("Error calculating margin: " + IntegerToString(error) + " - " + ErrorDescription(error)); return false; } if(margin_required > equity * 0.9) { WriteLog("Insufficient margin. Required: " + DoubleToString(margin_required, 2) + ", Equity: " + DoubleToString(equity, 2) + ", Margin usage: " + DoubleToString((margin_required/equity)*100, 2) + "%"); return false; } //--- Villahermosa Validation: Only proceed if trade meets minimum quality threshold if(quality_score < InpMinTradeQualityScore) { WriteLog("Trade rejected due to low Villahermosa quality score: " + IntegerToString(quality_score) + "/100 | Minimum required: " + IntegerToString(InpMinTradeQualityScore) + " | Cause: " + trade_cause + " | Projected Effect: " + projected_effect); return false; } //--- Set trade comment with Villahermosa analysis string comment = StringFormat("Villahermosa: %s | Score: %d/100 | Cause: %s | Effect: %s", (quality_score >= 80) ? "High Quality" : (quality_score >= 60) ? "Medium Quality" : "Low Quality", quality_score, trade_cause, projected_effect); trade.SetComment(comment); //--- Send order with enhanced error handling if(trade.PositionOpen(InpSymbol, type, final_lots, price, sl, 0)) { //--- Log the trade with Villahermosa analysis if(InpEnableAlerts) { string dir = (type == ORDER_TYPE_BUY) ? "BUY" : "SELL"; Alert("Opened ", dir, " position on ", InpSymbol, " at ", DoubleToString(price, 5), ", Lots: ", DoubleToString(final_lots, 2), ", SL: ", DoubleToString(sl, 5), " | Villahermosa Score: ", IntegerToString(quality_score), "/100", " | Cause: ", trade_cause); } if(InpEnableLogging) { WriteLog("Villahermosa Trade opened: " + ((type == ORDER_TYPE_BUY) ? "BUY" : "SELL") + " " + DoubleToString(final_lots, 2) + " lots at " + DoubleToString(price, 5) + ", SL: " + DoubleToString(sl, 5) + " | Score: " + IntegerToString(quality_score) + "/100" + " | Cause: " + trade_cause + " | Projected Effect: " + projected_effect); } // Update daily trade count daily_trade_count++; return true; } else { int error_code = trade.ResultRetcode(); string error_desc = trade.ResultRetcodeDescription(); WriteLog("Error opening position: " + IntegerToString(error_code) + " - " + error_desc + " | Villahermosa Cause: " + trade_cause); // Additional error handling based on error code if(error_code == 10004 || error_code == 10006 || error_code == 10007) // Common trade errors { // Wait a bit and retry if it's a temporary error if(InpEnableTradeRetry) { Sleep(100); if(trade.PositionOpen(InpSymbol, type, final_lots, price, sl, 0)) { WriteLog("Trade retry successful after error: " + IntegerToString(error_code)); daily_trade_count++; return true; } else { WriteLog("Trade retry failed: " + IntegerToString(trade.ResultRetcode()) + " - " + trade.ResultRetcodeDescription()); } } } return false; } } //+------------------------------------------------------------------+ //| Enhanced SymbolInfoDouble with error handling | //+------------------------------------------------------------------+ double SafeSymbolInfoDouble(string symbol, int prop_id, double default_value = 0.0) { double value = SymbolInfoDouble(symbol, prop_id); if(!ValidatePrice(value, "SymbolInfoDouble")) { return default_value; } return value; } //+------------------------------------------------------------------+ //| Enhanced SymbolInfoInteger with error handling | //+------------------------------------------------------------------+ long SafeSymbolInfoInteger(string symbol, int prop_id, long default_value = 0) { long value = SymbolInfoInteger(symbol, prop_id); int error = GetLastError(); if(error != 0) { WriteLog("SymbolInfoInteger error: " + IntegerToString(error) + " - " + ErrorDescription(error)); return default_value; } return value; } //+------------------------------------------------------------------+ //| Update chart with information | //+------------------------------------------------------------------+ void UpdateChartInfo(double dd, double upper, double lower, double exitU, double exitL, double atr) { string comment = StringFormat("EURUSD Donchian Strategy\n" + "Magic: %d\n" + "Equity High: %.2f\n" + "Drawdown: %.2f%%\n" + "Donchian Upper: %.5f\n" + "Donchian Lower: %.5f\n" + "Exit Upper: %.5f\n" + "Exit Lower: %.5f\n" + "ATR: %.5f\n" + "Trades Today: %d/%d\n" + "Trading Paused: %s\n" + "Time: %s", InpMagicNumber, equity_high, dd*100, upper, lower, exitU, exitL, atr, daily_trade_count, InpMaxTradesPerDay, trading_paused ? "Yes" : "No", TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS)); Comment(comment); } //+------------------------------------------------------------------+ //| Trail stop loss using ATR with Villahermosa's method | //+------------------------------------------------------------------+ void TrailATR(int dir, double atr) { // Make sure we have a valid ATR value if(!ValidateATRValue(atr, "TrailATR")) { // If the passed ATR is invalid, try to get a fresh one double atr_val[1]; if(SafeCopyBuffer(atr_handle, 0, 0, 1, atr_val) < 1) { WriteLog("Error getting ATR for trailing: " + IntegerToString(last_indicator_error)); return; } if(!ValidateATRValue(atr_val[0], "TrailATR fallback")) { return; } atr = atr_val[0]; } double point = SymbolInfoDouble(InpSymbol, SYMBOL_POINT); double buffer = InpTrailBuffer * point; int trails_executed = 0; string trail_details = ""; string market_context = AnalyzeMarketContext(); for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == InpSymbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { double current_sl = PositionGetDouble(POSITION_SL); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = (dir > 0) ? SymbolInfoDouble(InpSymbol, SYMBOL_BID) : SymbolInfoDouble(InpSymbol, SYMBOL_ASK); double profit = PositionGetDouble(POSITION_PROFIT); double new_sl; string trail_cause = ""; string trail_effect = ""; string trail_risk = ""; if(dir > 0) // Long position { double bid = SymbolInfoDouble(InpSymbol, SYMBOL_BID); double profit_pips = (bid - open_price) / point; // Calculate required profit threshold to start trailing if(bid - open_price >= InpTrailStartATR * atr) { new_sl = bid - current_stop_multiplier * atr; // Add buffer and ensure stop only moves upward if(new_sl > current_sl + buffer || current_sl == 0) { // Determine cause for trailing trail_cause = "Long position gained " + DoubleToString(profit_pips, 1) + " pips (" + DoubleToString(profit, 2) + " profit), exceeding " + DoubleToString(InpTrailStartATR, 1) + "x ATR threshold in " + market_context; // Calculate effect of trailing double locked_profit = (new_sl - open_price) / point; trail_effect = "Stop loss trailed to " + DoubleToString(new_sl, 5) + ", locking in " + DoubleToString(locked_profit, 1) + " pips of profit while allowing " + DoubleToString(current_stop_multiplier, 1) + "x ATR for further upside"; // Assess risk implications trail_risk = "Risk management: Protects " + DoubleToString(locked_profit, 1) + " pips gain while maintaining exposure to potential " + DoubleToString(current_stop_multiplier * atr / point, 1) + " pips upside"; if(trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP))) { trails_executed++; trail_details += "Long #" + IntegerToString(ticket) + " | SL: " + DoubleToString(new_sl, 5) + " | Cause: " + trail_cause + " | Effect: " + trail_effect + "; "; if(InpEnableLogging) { WriteLog("Trailed SL for long position: " + DoubleToString(new_sl, 5) + " | Cause: " + trail_cause + " | Effect: " + trail_effect + " | Risk: " + trail_risk); } } else { WriteLog("Error trailing long position " + IntegerToString(ticket) + ": " + IntegerToString(trade.ResultRetcode()) + " | Cause: " + trail_cause); } } else { // Log why trailing wasn't executed (buffer condition) if(InpEnableLogging) { trail_cause = "Price moved but new SL (" + DoubleToString(new_sl, 5) + ") doesn't exceed current SL (" + DoubleToString(current_sl, 5) + ") by buffer (" + DoubleToString(buffer/point, 1) + " pips)"; trail_effect = "Maintaining current stop loss to avoid excessive trading"; WriteLog("No trailing executed for long position " + IntegerToString(ticket) + " | Cause: " + trail_cause + " | Effect: " + trail_effect); } } } else { // Log why trailing wasn't executed (profit threshold) if(InpEnableLogging) { trail_cause = "Profit of " + DoubleToString(profit_pips, 1) + " pips below " + DoubleToString(InpTrailStartATR, 1) + "x ATR threshold (" + DoubleToString(InpTrailStartATR * atr / point, 1) + " pips) required for trailing"; trail_effect = "Waiting for sufficient profit before initiating trailing stop"; WriteLog("No trailing executed for long position " + IntegerToString(ticket) + " | Cause: " + trail_cause + " | Effect: " + trail_effect); } } } else // Short position { double ask = SymbolInfoDouble(InpSymbol, SYMBOL_ASK); double profit_pips = (open_price - ask) / point; // Calculate required profit threshold to start trailing if(open_price - ask >= InpTrailStartATR * atr) { new_sl = ask + current_stop_multiplier * atr; // Add buffer and ensure stop only moves downward if(new_sl < current_sl - buffer || current_sl == 0) { // Determine cause for trailing trail_cause = "Short position gained " + DoubleToString(profit_pips, 1) + " pips (" + DoubleToString(profit, 2) + " profit), exceeding " + DoubleToString(InpTrailStartATR, 1) + "x ATR threshold in " + market_context; // Calculate effect of trailing double locked_profit = (open_price - new_sl) / point; trail_effect = "Stop loss trailed to " + DoubleToString(new_sl, 5) + ", locking in " + DoubleToString(locked_profit, 1) + " pips of profit while allowing " + DoubleToString(current_stop_multiplier, 1) + "x ATR for further downside"; // Assess risk implications trail_risk = "Risk management: Protects " + DoubleToString(locked_profit, 1) + " pips gain while maintaining exposure to potential " + DoubleToString(current_stop_multiplier * atr / point, 1) + " pips downside"; if(trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP))) { trails_executed++; trail_details += "Short #" + IntegerToString(ticket) + " | SL: " + DoubleToString(new_sl, 5) + " | Cause: " + trail_cause + " | Effect: " + trail_effect + "; "; if(InpEnableLogging) { WriteLog("Trailed SL for short position: " + DoubleToString(new_sl, 5) + " | Cause: " + trail_cause + " | Effect: " + trail_effect + " | Risk: " + trail_risk); } } else { WriteLog("Error trailing short position " + IntegerToString(ticket) + ": " + IntegerToString(trade.ResultRetcode()) + " | Cause: " + trail_cause); } } else { // Log why trailing wasn't executed (buffer condition) if(InpEnableLogging) { trail_cause = "Price moved but new SL (" + DoubleToString(new_sl, 5) + ") doesn't exceed current SL (" + DoubleToString(current_sl, 5) + ") by buffer (" + DoubleToString(buffer/point, 1) + " pips)"; trail_effect = "Maintaining current stop loss to avoid excessive trading"; WriteLog("No trailing executed for short position " + IntegerToString(ticket) + " | Cause: " + trail_cause + " | Effect: " + trail_effect); } } } else { // Log why trailing wasn't executed (profit threshold) if(InpEnableLogging) { trail_cause = "Profit of " + DoubleToString(profit_pips, 1) + " pips below " + DoubleToString(InpTrailStartATR, 1) + "x ATR threshold (" + DoubleToString(InpTrailStartATR * atr / point, 1) + " pips) required for trailing"; trail_effect = "Waiting for sufficient profit before initiating trailing stop"; WriteLog("No trailing executed for short position " + IntegerToString(ticket) + " | Cause: " + trail_cause + " | Effect: " + trail_effect); } } } } } // Generate comprehensive trail report if(trails_executed > 0 && InpEnableLogging) { string trail_report = "Villahermosa Trailing Operation | Market Context: " + market_context + " | Positions Trailed: " + IntegerToString(trails_executed) + " | Details: " + trail_details; WriteLog(trail_report); } } //+------------------------------------------------------------------+ //| Analyze market context for trailing decisions | //+------------------------------------------------------------------+ string AnalyzeTrailingContext(double atr) { string context = ""; double volatility_ratio = CalculateMarketVolatility(); // Add volatility context if(volatility_ratio > 2.0) context = "extremely volatile market (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; else if(volatility_ratio > 1.5) context = "highly volatile market (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; else if(volatility_ratio < 0.5) context = "low volatility market (ATR " + DoubleToString(volatility_ratio, 1) + "x normal)"; else context = "normal volatility market"; // Add session context MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; if((hour >= 8 && hour <= 12) || (hour >= 14 && hour <= 17)) context += " during high liquidity session"; else if(hour >= 1 && hour <= 4) context += " during Asian session"; else if(hour >= 21 || hour <= 5) context += " during low liquidity session"; return context; } //+------------------------------------------------------------------+ //| Villahermosa Trailing Analysis | //| Analyzes whether to trail stop loss with cause & effect | //+------------------------------------------------------------------+ string VillahermosaTrailingAnalysis(ulong ticket, ENUM_POSITION_TYPE type, double open_price, double current_price, double profit, long position_age, double atr) { string analysis = "Villahermosa Trailing Analysis: "; string trailing_cause = ""; string trailing_effect = ""; string risk_assessment = ""; // Calculate key metrics double point = SymbolInfoDouble(InpSymbol, SYMBOL_POINT); double price_change = MathAbs(current_price - open_price); double price_change_pips = price_change / point; double hours_open = position_age / 3600.0; // Calculate profit percentage double profit_percentage = 0; double account_equity = AccountInfoDouble(ACCOUNT_EQUITY); if(account_equity != 0) profit_percentage = (profit / account_equity) * 100; // 1. Profit-based trailing analysis if(profit > 0) { trailing_cause = "Position profitable (" + DoubleToString(profit, 2) + " / " + DoubleToString(profit_percentage, 2) + "%) with " + DoubleToString(price_change_pips, 1) + " pips movement"; if(profit_percentage > 1.0) { trailing_effect = "Strong profit justifies trailing stop to lock in gains"; risk_assessment = "Locking in " + DoubleToString(profit_percentage, 2) + "% gain while allowing " + DoubleToString(InpATRStopMult, 1) + "x ATR for further upside"; } else if(profit_percentage > 0.5) { trailing_effect = "Moderate profit suggests trailing stop consideration"; risk_assessment = "Protecting " + DoubleToString(profit_percentage, 2) + "% gain with " + DoubleToString(InpATRStopMult, 1) + "x ATR buffer"; } else { trailing_effect = "Minimal profit - maintain original stop loss"; risk_assessment = "Insufficient profit to justify trailing stop adjustment"; } } else { trailing_cause = "Position unprofitable (" + DoubleToString(profit, 2) + " / " + DoubleToString(profit_percentage, 2) + "%) with " + DoubleToString(price_change_pips, 1) + " pips adverse movement"; trailing_effect = "No trailing stop adjustment recommended for unprofitable position"; risk_assessment = "Maintaining original stop loss to limit further losses"; } // 2. Time-based analysis string time_analysis = ""; if(hours_open > 48) { time_analysis = "Extended position duration (" + DoubleToString(hours_open, 1) + " hours) increases trailing stop urgency"; } else if(hours_open > 24) { time_analysis = "Moderate position duration (" + DoubleToString(hours_open, 1) + " hours) supports trailing stop consideration"; } else { time_analysis = "Recent position (" + DoubleToString(hours_open, 1) + " hours) - trailing stop less critical"; } // 3. Volatility-based analysis string volatility_analysis = ""; double atr_pips = atr / point; double atr_values[20]; if(CopyBuffer(atr_handle, 0, 0, 20, atr_values) >= 20) { double atr_sum = 0; for(int i = 0; i < 20; i++) atr_sum += atr_values[i]; double atr_avg = atr_sum / 20; double atr_ratio = atr / atr_avg; if(atr_ratio > 1.5) { volatility_analysis = "High volatility (" + DoubleToString(atr_ratio, 2) + "x average) suggests wider trailing stop"; } else if(atr_ratio < 0.7) { volatility_analysis = "Low volatility (" + DoubleToString(atr_ratio, 2) + "x average) allows tighter trailing stop"; } else { volatility_analysis = "Normal volatility (" + DoubleToString(atr_ratio, 2) + "x average) - standard trailing stop appropriate"; } } // Compile final analysis analysis += trailing_cause + " | Effect: " + trailing_effect + " | Risk: " + risk_assessment + " | Time: " + time_analysis + " | Volatility: " + volatility_analysis; return analysis; } //+------------------------------------------------------------------+ //| Villahermosa Post-Trade Analysis | //+------------------------------------------------------------------+ string VillahermosaPostTradeAnalysis(ulong deal_ticket) { string analysis = "Post-Trade Analysis: "; // Get deal details double profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); double volume = HistoryDealGetDouble(deal_ticket, DEAL_VOLUME); string symbol = HistoryDealGetString(deal_ticket, DEAL_SYMBOL); datetime time = (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME); // Calculate trade performance metrics double account_equity = AccountInfoDouble(ACCOUNT_EQUITY); double return_on_equity = (account_equity != 0) ? (profit / account_equity) * 100 : 0; // Determine trade outcome string outcome = (profit > 0) ? "Profitable" : (profit < 0) ? "Unprofitable" : "Break-even"; // Analyze trade quality string quality_analysis = ""; if(MathAbs(return_on_equity) > 2.0) quality_analysis = "High impact trade (" + DoubleToString(return_on_equity, 2) + "% ROE)"; else if(MathAbs(return_on_equity) > 0.5) quality_analysis = "Moderate impact trade (" + DoubleToString(return_on_equity, 2) + "% ROE)"; else quality_analysis = "Low impact trade (" + DoubleToString(return_on_equity, 2) + "% ROE)"; // Compile analysis analysis += outcome + " | " + quality_analysis + " | Volume: " + DoubleToString(volume, 2) + " | Symbol: " + symbol + " | Time: " + TimeToString(time); return analysis; } //+------------------------------------------------------------------+ //| Helper function to extract string value from JSON | //+------------------------------------------------------------------+ string GetJsonString(CJAsonValue* node, string defaultValue = "") { if(node == NULL) return defaultValue; if(node.GetType() == J_STRING) return node.ToString(); else if(node.GetType() == J_NUMBER) return IntegerToString(node.ToInt()); else if(node.GetType() == J_DOUBLE) return DoubleToString(node.ToDouble()); return defaultValue; } //+------------------------------------------------------------------+ //| Extract value from JSON string (simple implementation) | //+------------------------------------------------------------------+ string ExtractJsonValue(string json, string key) { string pattern = "\"" + key + "\":\"(.*?)\""; int start = StringFind(json, pattern); if(start < 0) { // Try with numeric value pattern = "\"" + key + "\":(\\d+)"; start = StringFind(json, pattern); if(start < 0) return ""; } start += StringLen(key) + 3; // Move past key and ":" int end = StringFind(json, "\"", start); if(end < 0) end = StringFind(json, ",", start); if(end < 0) end = StringFind(json, "}", start); if(end < 0) return ""; return StringSubstr(json, start, end - start); } //+------------------------------------------------------------------+ //| Get trading session context | //+------------------------------------------------------------------+ string GetTradingSessionContext() { MqlDateTime current_time; TimeCurrent(current_time); int hour = current_time.hour; if(hour >= 0 && hour < 5) return "Asian Session (00:00-04:59) - Typically lower volatility"; else if(hour >= 5 && hour < 8) return "Asian/London Overlap (05:00-07:59) - Increasing volatility"; else if(hour >= 8 && hour < 13) return "London Session (08:00-12:59) - High volatility"; else if(hour >= 13 && hour < 16) return "London/NY Overlap (13:00-15:59) - Highest volatility"; else if(hour >= 16 && hour < 21) return "New York Session (16:00-20:59) - High volatility"; else return "Night Session (21:00-23:59) - Lower volatility, reduced liquidity"; } //+------------------------------------------------------------------+