2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| TechnicalAnalysis.mqh |
|
2025-07-30 13:32:29 +01:00
|
|
|
//| Technical Analysis Module v1.1 |
|
|
|
|
//| Added: ATR validation and fallback |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
2025-07-30 13:32:29 +01:00
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
#ifndef TECHNICAL_ANALYSIS_MQH
|
|
|
|
#define TECHNICAL_ANALYSIS_MQH
|
|
|
|
|
|
|
|
#include "DataTypes.mqh"
|
|
|
|
#include <Math/Stat/Math.mqh>
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Technical Analysis Class |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
class CTechnicalAnalysis
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
//--- Configuration
|
|
|
|
TechnicalConfig m_config;
|
|
|
|
|
|
|
|
//--- Indicator handles
|
|
|
|
int m_atr_handle;
|
|
|
|
int m_adx_handle;
|
|
|
|
int m_rsi_handle;
|
|
|
|
int m_ma200_handle;
|
|
|
|
|
|
|
|
//--- Cached levels
|
|
|
|
TechnicalLevel m_sr_levels[100];
|
|
|
|
int m_sr_count;
|
|
|
|
TechnicalLevel m_pivot_levels[10];
|
|
|
|
int m_pivot_count;
|
|
|
|
datetime m_last_calc_time;
|
|
|
|
|
|
|
|
//--- Helper methods
|
|
|
|
void CalculateSupportResistance();
|
|
|
|
void CalculatePivotPoints();
|
|
|
|
void CalculateFibonacciLevels();
|
|
|
|
void IdentifyMarketStructure();
|
|
|
|
void FindSwingPoints(double &highs[], double &lows[],
|
|
|
|
int &high_bars[], int &low_bars[]);
|
2025-07-22 16:37:29 +01:00
|
|
|
double CalculateLevelStrength(double price, int min_touches,
|
2025-07-20 22:38:24 +01:00
|
|
|
datetime last_touch);
|
|
|
|
bool IsLevelValid(double price, const TechnicalLevel &levels[],
|
|
|
|
int count, double tolerance);
|
|
|
|
void SortLevelsByStrength(TechnicalLevel &levels[], int count);
|
|
|
|
|
|
|
|
public:
|
|
|
|
CTechnicalAnalysis();
|
|
|
|
~CTechnicalAnalysis();
|
|
|
|
|
|
|
|
//--- Initialization
|
|
|
|
bool Initialize(const TechnicalConfig &config);
|
|
|
|
|
|
|
|
//--- Main analysis method
|
|
|
|
MarketConditions AnalyzeMarket();
|
|
|
|
|
|
|
|
//--- Specific analysis methods
|
|
|
|
void UpdateTechnicalLevels();
|
|
|
|
int GetNearestLevels(double price, TechnicalLevel &above[],
|
|
|
|
TechnicalLevel &below[], int max_count);
|
|
|
|
double GetTrendStrength();
|
|
|
|
ENUM_MARKET_CONDITION GetMarketCondition();
|
|
|
|
|
|
|
|
//--- Indicator values
|
|
|
|
double GetATR(int period = 14);
|
|
|
|
double GetADX();
|
|
|
|
double GetRSI();
|
|
|
|
double GetMA200();
|
|
|
|
|
|
|
|
//--- Level finders
|
|
|
|
double GetNearestSupport(double price);
|
|
|
|
double GetNearestResistance(double price);
|
|
|
|
double GetStrongestLevel(double price, double range);
|
|
|
|
|
|
|
|
//--- Pattern recognition
|
|
|
|
bool IsAtSupport(double price, double tolerance = 0);
|
|
|
|
bool IsAtResistance(double price, double tolerance = 0);
|
|
|
|
bool IsBreakout(double price, int lookback = 20);
|
|
|
|
bool IsFakeout(double price, int lookback = 5);
|
|
|
|
};
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Constructor |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
CTechnicalAnalysis::CTechnicalAnalysis()
|
|
|
|
{
|
|
|
|
m_atr_handle = INVALID_HANDLE;
|
|
|
|
m_adx_handle = INVALID_HANDLE;
|
|
|
|
m_rsi_handle = INVALID_HANDLE;
|
|
|
|
m_ma200_handle = INVALID_HANDLE;
|
|
|
|
m_sr_count = 0;
|
|
|
|
m_pivot_count = 0;
|
|
|
|
m_last_calc_time = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Destructor |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
CTechnicalAnalysis::~CTechnicalAnalysis()
|
|
|
|
{
|
|
|
|
if(m_atr_handle != INVALID_HANDLE) IndicatorRelease(m_atr_handle);
|
|
|
|
if(m_adx_handle != INVALID_HANDLE) IndicatorRelease(m_adx_handle);
|
|
|
|
if(m_rsi_handle != INVALID_HANDLE) IndicatorRelease(m_rsi_handle);
|
|
|
|
if(m_ma200_handle != INVALID_HANDLE) IndicatorRelease(m_ma200_handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Initialize technical analysis |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CTechnicalAnalysis::Initialize(const TechnicalConfig &config)
|
|
|
|
{
|
|
|
|
m_config = config;
|
|
|
|
|
|
|
|
//--- Create indicator handles
|
|
|
|
m_atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
|
|
|
|
m_adx_handle = iADX(_Symbol, PERIOD_CURRENT, 14);
|
|
|
|
m_rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
|
|
|
|
m_ma200_handle = iMA(_Symbol, PERIOD_CURRENT, 200, 0, MODE_SMA, PRICE_CLOSE);
|
|
|
|
|
|
|
|
if(m_atr_handle == INVALID_HANDLE || m_adx_handle == INVALID_HANDLE ||
|
|
|
|
m_rsi_handle == INVALID_HANDLE || m_ma200_handle == INVALID_HANDLE)
|
|
|
|
{
|
|
|
|
Print("TechnicalAnalysis: Failed to create indicator handles");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//--- Initial calculation of levels
|
|
|
|
UpdateTechnicalLevels();
|
|
|
|
|
|
|
|
Print("TechnicalAnalysis initialized: Lookback=", m_config.lookback);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-07-30 19:33:01 +01:00
|
|
|
//| Analyze market conditions - FIXED VERSION |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
MarketConditions CTechnicalAnalysis::AnalyzeMarket()
|
|
|
|
{
|
|
|
|
MarketConditions conditions;
|
|
|
|
|
|
|
|
//--- Update technical levels if needed
|
|
|
|
if(TimeCurrent() - m_last_calc_time > 300) // Update every 5 minutes
|
|
|
|
{
|
|
|
|
UpdateTechnicalLevels();
|
|
|
|
}
|
|
|
|
|
2025-07-30 19:33:01 +01:00
|
|
|
//--- Get basic indicators with validation
|
2025-07-20 22:38:24 +01:00
|
|
|
conditions.volatility = GetATR();
|
2025-07-30 19:33:01 +01:00
|
|
|
|
|
|
|
// Critical fix: Ensure volatility is ALWAYS valid
|
|
|
|
if(conditions.volatility <= 0 || conditions.volatility == EMPTY_VALUE || !MathIsValidNumber(conditions.volatility))
|
2025-07-30 13:32:29 +01:00
|
|
|
{
|
2025-07-30 19:33:01 +01:00
|
|
|
Print("TechnicalAnalysis: Invalid ATR value (", conditions.volatility, "), calculating fallback");
|
|
|
|
|
|
|
|
// Try manual calculation
|
|
|
|
double sum = 0;
|
|
|
|
int period = 14;
|
|
|
|
bool calc_success = true;
|
|
|
|
|
|
|
|
for(int i = 1; i <= period; i++)
|
|
|
|
{
|
|
|
|
double high = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double low = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double close_prev = iClose(_Symbol, PERIOD_CURRENT, i + 1);
|
|
|
|
|
|
|
|
if(high > 0 && low > 0 && close_prev > 0)
|
|
|
|
{
|
|
|
|
double tr = MathMax(high - low, MathMax(MathAbs(high - close_prev), MathAbs(low - close_prev)));
|
|
|
|
sum += tr;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
calc_success = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2025-07-30 13:32:29 +01:00
|
|
|
|
2025-07-30 19:33:01 +01:00
|
|
|
if(calc_success && sum > 0)
|
|
|
|
{
|
|
|
|
conditions.volatility = sum / period;
|
|
|
|
Print("TechnicalAnalysis: Manual ATR calculation: ", conditions.volatility);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Final fallback based on price
|
|
|
|
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
if(current_price > 0)
|
|
|
|
{
|
|
|
|
conditions.volatility = current_price * 0.001; // 0.1% of price
|
|
|
|
|
|
|
|
// Minimum 20 pips for forex
|
|
|
|
double min_volatility = 20 * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
if(conditions.volatility < min_volatility)
|
|
|
|
conditions.volatility = min_volatility;
|
|
|
|
|
|
|
|
Print("TechnicalAnalysis: Using price-based fallback ATR: ", conditions.volatility);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Absolute last resort
|
|
|
|
conditions.volatility = 50 * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
Print("TechnicalAnalysis: Using absolute fallback: 50 points");
|
|
|
|
}
|
|
|
|
}
|
2025-07-30 13:32:29 +01:00
|
|
|
}
|
2025-07-30 19:33:01 +01:00
|
|
|
|
|
|
|
// Log the final volatility for debugging
|
|
|
|
Print("TechnicalAnalysis: Final volatility = ", conditions.volatility, " (", conditions.volatility / _Point, " points)");
|
|
|
|
|
2025-07-20 22:38:24 +01:00
|
|
|
conditions.trend_strength = GetADX();
|
|
|
|
conditions.momentum = GetRSI();
|
|
|
|
conditions.spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
|
|
|
|
//--- Determine market condition
|
|
|
|
conditions.condition = GetMarketCondition();
|
|
|
|
|
|
|
|
//--- Check for news (simplified - would need news calendar integration)
|
|
|
|
conditions.news_event = false;
|
|
|
|
|
|
|
|
//--- Calculate liquidity score (based on volume)
|
|
|
|
long volume = iVolume(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
long avg_volume = 0;
|
|
|
|
for(int i = 1; i <= 20; i++)
|
|
|
|
avg_volume += iVolume(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
avg_volume /= 20;
|
|
|
|
conditions.liquidity = (avg_volume > 0) ? (double)volume / avg_volume * 100 : 100;
|
|
|
|
|
|
|
|
//--- Copy S/R levels
|
|
|
|
conditions.sr_count = MathMin(m_sr_count, 20);
|
|
|
|
for(int i = 0; i < conditions.sr_count; i++)
|
|
|
|
{
|
|
|
|
conditions.sr_levels[i] = m_sr_levels[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return conditions;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Update all technical levels |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CTechnicalAnalysis::UpdateTechnicalLevels()
|
|
|
|
{
|
|
|
|
if(m_config.use_sr)
|
|
|
|
CalculateSupportResistance();
|
|
|
|
|
|
|
|
if(m_config.use_pivot)
|
|
|
|
CalculatePivotPoints();
|
|
|
|
|
|
|
|
if(m_config.use_fib)
|
|
|
|
CalculateFibonacciLevels();
|
|
|
|
|
|
|
|
//--- Sort levels by strength
|
|
|
|
if(m_sr_count > 0)
|
|
|
|
SortLevelsByStrength(m_sr_levels, m_sr_count);
|
|
|
|
|
|
|
|
m_last_calc_time = TimeCurrent();
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate support and resistance levels |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CTechnicalAnalysis::CalculateSupportResistance()
|
|
|
|
{
|
|
|
|
m_sr_count = 0;
|
|
|
|
|
|
|
|
//--- Find swing highs and lows
|
|
|
|
double highs[], lows[];
|
|
|
|
int high_bars[], low_bars[];
|
|
|
|
ArrayResize(highs, m_config.lookback);
|
|
|
|
ArrayResize(lows, m_config.lookback);
|
|
|
|
ArrayResize(high_bars, m_config.lookback);
|
|
|
|
ArrayResize(low_bars, m_config.lookback);
|
|
|
|
|
|
|
|
FindSwingPoints(highs, lows, high_bars, low_bars);
|
|
|
|
|
|
|
|
//--- Process swing highs as resistance
|
|
|
|
for(int i = 0; i < ArraySize(highs); i++)
|
|
|
|
{
|
|
|
|
if(highs[i] == 0) continue;
|
|
|
|
|
|
|
|
//--- Check if level already exists
|
|
|
|
if(!IsLevelValid(highs[i], m_sr_levels, m_sr_count, m_config.level_tolerance))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
//--- Count touches
|
|
|
|
int touches = 0;
|
|
|
|
datetime last_touch = 0;
|
|
|
|
|
|
|
|
for(int j = 0; j < m_config.lookback; j++)
|
|
|
|
{
|
|
|
|
double high = iHigh(_Symbol, PERIOD_CURRENT, j);
|
|
|
|
double low = iLow(_Symbol, PERIOD_CURRENT, j);
|
|
|
|
|
|
|
|
if(MathAbs(high - highs[i]) <= m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT) ||
|
|
|
|
MathAbs(low - highs[i]) <= m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT))
|
|
|
|
{
|
|
|
|
touches++;
|
|
|
|
if(last_touch == 0)
|
|
|
|
last_touch = iTime(_Symbol, PERIOD_CURRENT, j);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(touches >= m_config.min_touches && m_sr_count < 100)
|
|
|
|
{
|
|
|
|
m_sr_levels[m_sr_count].price = highs[i];
|
|
|
|
m_sr_levels[m_sr_count].type = "resistance";
|
|
|
|
m_sr_levels[m_sr_count].strength = CalculateLevelStrength(highs[i], touches, last_touch);
|
|
|
|
m_sr_levels[m_sr_count].touches = touches;
|
|
|
|
m_sr_levels[m_sr_count].last_touch = last_touch;
|
|
|
|
m_sr_levels[m_sr_count].zone_width = m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
m_sr_levels[m_sr_count].description = StringFormat("Resistance %.5f (%d touches)", highs[i], touches);
|
|
|
|
m_sr_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//--- Process swing lows as support
|
|
|
|
for(int i = 0; i < ArraySize(lows); i++)
|
|
|
|
{
|
|
|
|
if(lows[i] == 0) continue;
|
|
|
|
|
|
|
|
//--- Check if level already exists
|
|
|
|
if(!IsLevelValid(lows[i], m_sr_levels, m_sr_count, m_config.level_tolerance))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
//--- Count touches
|
|
|
|
int touches = 0;
|
|
|
|
datetime last_touch = 0;
|
|
|
|
|
|
|
|
for(int j = 0; j < m_config.lookback; j++)
|
|
|
|
{
|
|
|
|
double high = iHigh(_Symbol, PERIOD_CURRENT, j);
|
|
|
|
double low = iLow(_Symbol, PERIOD_CURRENT, j);
|
|
|
|
|
|
|
|
if(MathAbs(high - lows[i]) <= m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT) ||
|
|
|
|
MathAbs(low - lows[i]) <= m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT))
|
|
|
|
{
|
|
|
|
touches++;
|
|
|
|
if(last_touch == 0)
|
|
|
|
last_touch = iTime(_Symbol, PERIOD_CURRENT, j);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(touches >= m_config.min_touches && m_sr_count < 100)
|
|
|
|
{
|
|
|
|
m_sr_levels[m_sr_count].price = lows[i];
|
|
|
|
m_sr_levels[m_sr_count].type = "support";
|
|
|
|
m_sr_levels[m_sr_count].strength = CalculateLevelStrength(lows[i], touches, last_touch);
|
|
|
|
m_sr_levels[m_sr_count].touches = touches;
|
|
|
|
m_sr_levels[m_sr_count].last_touch = last_touch;
|
|
|
|
m_sr_levels[m_sr_count].zone_width = m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
m_sr_levels[m_sr_count].description = StringFormat("Support %.5f (%d touches)", lows[i], touches);
|
|
|
|
m_sr_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate pivot points |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CTechnicalAnalysis::CalculatePivotPoints()
|
|
|
|
{
|
|
|
|
m_pivot_count = 0;
|
|
|
|
|
|
|
|
//--- Get daily OHLC
|
|
|
|
double daily_high = iHigh(_Symbol, PERIOD_D1, 1);
|
|
|
|
double daily_low = iLow(_Symbol, PERIOD_D1, 1);
|
|
|
|
double daily_close = iClose(_Symbol, PERIOD_D1, 1);
|
|
|
|
|
|
|
|
//--- Calculate pivot point
|
|
|
|
double pivot = (daily_high + daily_low + daily_close) / 3;
|
|
|
|
|
|
|
|
//--- Calculate support and resistance levels
|
|
|
|
double r1 = 2 * pivot - daily_low;
|
|
|
|
double s1 = 2 * pivot - daily_high;
|
|
|
|
double r2 = pivot + (daily_high - daily_low);
|
|
|
|
double s2 = pivot - (daily_high - daily_low);
|
|
|
|
double r3 = r1 + (daily_high - daily_low);
|
|
|
|
double s3 = s1 - (daily_high - daily_low);
|
|
|
|
|
|
|
|
//--- Add to technical levels if not already in S/R
|
|
|
|
if(IsLevelValid(pivot, m_sr_levels, m_sr_count, m_config.level_tolerance) && m_sr_count < 100)
|
|
|
|
{
|
|
|
|
m_sr_levels[m_sr_count].price = pivot;
|
|
|
|
m_sr_levels[m_sr_count].type = "pivot";
|
|
|
|
m_sr_levels[m_sr_count].strength = 75;
|
|
|
|
m_sr_levels[m_sr_count].touches = 0;
|
|
|
|
m_sr_levels[m_sr_count].last_touch = TimeCurrent();
|
|
|
|
m_sr_levels[m_sr_count].zone_width = m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
m_sr_levels[m_sr_count].description = "Daily Pivot";
|
|
|
|
m_sr_count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//--- Add R1, S1, etc. similarly...
|
|
|
|
double levels[] = {r1, r2, r3, s1, s2, s3};
|
|
|
|
string names[] = {"R1", "R2", "R3", "S1", "S2", "S3"};
|
|
|
|
string types[] = {"resistance", "resistance", "resistance", "support", "support", "support"};
|
|
|
|
|
|
|
|
for(int i = 0; i < 6; i++)
|
|
|
|
{
|
|
|
|
if(IsLevelValid(levels[i], m_sr_levels, m_sr_count, m_config.level_tolerance) && m_sr_count < 100)
|
|
|
|
{
|
|
|
|
m_sr_levels[m_sr_count].price = levels[i];
|
|
|
|
m_sr_levels[m_sr_count].type = types[i];
|
|
|
|
m_sr_levels[m_sr_count].strength = 60 - i * 5; // R1/S1 stronger than R3/S3
|
|
|
|
m_sr_levels[m_sr_count].touches = 0;
|
|
|
|
m_sr_levels[m_sr_count].last_touch = TimeCurrent();
|
|
|
|
m_sr_levels[m_sr_count].zone_width = m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
m_sr_levels[m_sr_count].description = "Pivot " + names[i];
|
|
|
|
m_sr_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate Fibonacci levels |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CTechnicalAnalysis::CalculateFibonacciLevels()
|
|
|
|
{
|
|
|
|
//--- Find recent significant high and low
|
|
|
|
int highest_bar = iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, m_config.lookback, 0);
|
|
|
|
int lowest_bar = iLowest(_Symbol, PERIOD_CURRENT, MODE_LOW, m_config.lookback, 0);
|
|
|
|
|
|
|
|
if(highest_bar < 0 || lowest_bar < 0) return;
|
|
|
|
|
|
|
|
double swing_high = iHigh(_Symbol, PERIOD_CURRENT, highest_bar);
|
|
|
|
double swing_low = iLow(_Symbol, PERIOD_CURRENT, lowest_bar);
|
|
|
|
double range = swing_high - swing_low;
|
|
|
|
|
|
|
|
//--- Fibonacci ratios
|
|
|
|
double fib_levels[] = {0.236, 0.382, 0.5, 0.618, 0.786};
|
|
|
|
string fib_names[] = {"23.6%", "38.2%", "50.0%", "61.8%", "78.6%"};
|
|
|
|
|
|
|
|
//--- Determine if uptrend or downtrend
|
|
|
|
bool uptrend = highest_bar < lowest_bar;
|
|
|
|
|
|
|
|
for(int i = 0; i < 5; i++)
|
|
|
|
{
|
|
|
|
double fib_price;
|
|
|
|
string fib_type;
|
|
|
|
|
|
|
|
if(uptrend)
|
|
|
|
{
|
|
|
|
//--- Retracement levels in uptrend are support
|
|
|
|
fib_price = swing_low + range * fib_levels[i];
|
|
|
|
fib_type = "support";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//--- Retracement levels in downtrend are resistance
|
|
|
|
fib_price = swing_high - range * fib_levels[i];
|
|
|
|
fib_type = "resistance";
|
|
|
|
}
|
|
|
|
|
|
|
|
if(IsLevelValid(fib_price, m_sr_levels, m_sr_count, m_config.level_tolerance) && m_sr_count < 100)
|
|
|
|
{
|
|
|
|
m_sr_levels[m_sr_count].price = fib_price;
|
|
|
|
m_sr_levels[m_sr_count].type = fib_type;
|
|
|
|
m_sr_levels[m_sr_count].strength = 50 + (i == 2 ? 10 : 0); // 50% level stronger
|
|
|
|
m_sr_levels[m_sr_count].touches = 0;
|
|
|
|
m_sr_levels[m_sr_count].last_touch = TimeCurrent();
|
|
|
|
m_sr_levels[m_sr_count].zone_width = m_config.level_tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
m_sr_levels[m_sr_count].description = "Fib " + fib_names[i];
|
|
|
|
m_sr_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Find swing points |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CTechnicalAnalysis::FindSwingPoints(double &highs[], double &lows[],
|
|
|
|
int &high_bars[], int &low_bars[])
|
|
|
|
{
|
|
|
|
int swing_period = 5; // Look for 5-bar swings
|
|
|
|
int found_highs = 0, found_lows = 0;
|
|
|
|
|
|
|
|
for(int i = swing_period; i < m_config.lookback - swing_period; i++)
|
|
|
|
{
|
|
|
|
bool is_high = true, is_low = true;
|
|
|
|
double current_high = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double current_low = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
|
|
|
|
//--- Check if it's a swing high
|
|
|
|
for(int j = 1; j <= swing_period; j++)
|
|
|
|
{
|
|
|
|
if(iHigh(_Symbol, PERIOD_CURRENT, i - j) >= current_high ||
|
|
|
|
iHigh(_Symbol, PERIOD_CURRENT, i + j) >= current_high)
|
|
|
|
{
|
|
|
|
is_high = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//--- Check if it's a swing low
|
|
|
|
for(int j = 1; j <= swing_period; j++)
|
|
|
|
{
|
|
|
|
if(iLow(_Symbol, PERIOD_CURRENT, i - j) <= current_low ||
|
|
|
|
iLow(_Symbol, PERIOD_CURRENT, i + j) <= current_low)
|
|
|
|
{
|
|
|
|
is_low = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(is_high && found_highs < ArraySize(highs))
|
|
|
|
{
|
|
|
|
highs[found_highs] = current_high;
|
|
|
|
high_bars[found_highs] = i;
|
|
|
|
found_highs++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(is_low && found_lows < ArraySize(lows))
|
|
|
|
{
|
|
|
|
lows[found_lows] = current_low;
|
|
|
|
low_bars[found_lows] = i;
|
|
|
|
found_lows++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Calculate level strength |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CTechnicalAnalysis::CalculateLevelStrength(double price, int touches, datetime last_touch)
|
|
|
|
{
|
|
|
|
double strength = 0;
|
|
|
|
|
|
|
|
//--- Base strength from touches
|
|
|
|
strength = MathMin(touches * 10, 50); // Max 50 points from touches
|
|
|
|
|
|
|
|
//--- Recency bonus
|
|
|
|
int bars_since_touch = iBarShift(_Symbol, PERIOD_CURRENT, last_touch);
|
|
|
|
if(bars_since_touch < 50)
|
|
|
|
strength += 20;
|
|
|
|
else if(bars_since_touch < 100)
|
|
|
|
strength += 10;
|
|
|
|
|
|
|
|
//--- Round number bonus
|
|
|
|
double normalized = price / SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
if(MathMod(normalized, 100) < 1) // Round to 100 points
|
|
|
|
strength += 15;
|
|
|
|
else if(MathMod(normalized, 50) < 1) // Round to 50 points
|
|
|
|
strength += 10;
|
|
|
|
else if(MathMod(normalized, 10) < 1) // Round to 10 points
|
|
|
|
strength += 5;
|
|
|
|
|
|
|
|
return MathMin(strength, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if level is valid (not duplicate) |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CTechnicalAnalysis::IsLevelValid(double price, const TechnicalLevel &levels[],
|
|
|
|
int count, double tolerance)
|
|
|
|
{
|
|
|
|
double tol = tolerance * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
|
|
|
|
for(int i = 0; i < count; i++)
|
|
|
|
{
|
|
|
|
if(MathAbs(price - levels[i].price) <= tol)
|
|
|
|
return false; // Too close to existing level
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Sort levels by strength |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
void CTechnicalAnalysis::SortLevelsByStrength(TechnicalLevel &levels[], int count)
|
|
|
|
{
|
|
|
|
//--- Simple bubble sort
|
|
|
|
for(int i = 0; i < count - 1; i++)
|
|
|
|
{
|
|
|
|
for(int j = 0; j < count - i - 1; j++)
|
|
|
|
{
|
|
|
|
if(levels[j].strength < levels[j + 1].strength)
|
|
|
|
{
|
|
|
|
TechnicalLevel temp = levels[j];
|
|
|
|
levels[j] = levels[j + 1];
|
|
|
|
levels[j + 1] = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get market condition |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
ENUM_MARKET_CONDITION CTechnicalAnalysis::GetMarketCondition()
|
|
|
|
{
|
|
|
|
double adx = GetADX();
|
|
|
|
double ma200 = GetMA200();
|
|
|
|
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
|
|
|
|
//--- Check for trending market
|
|
|
|
if(adx > 25)
|
|
|
|
{
|
|
|
|
if(current_price > ma200)
|
|
|
|
return MARKET_TRENDING_UP;
|
|
|
|
else
|
|
|
|
return MARKET_TRENDING_DOWN;
|
|
|
|
}
|
|
|
|
//--- Check for breakout
|
|
|
|
else if(IsBreakout(current_price))
|
|
|
|
{
|
|
|
|
return MARKET_BREAKOUT;
|
|
|
|
}
|
|
|
|
//--- Check volatility
|
|
|
|
else
|
|
|
|
{
|
|
|
|
double atr = GetATR();
|
|
|
|
double avg_atr = 0;
|
|
|
|
double atr_buffer[20];
|
|
|
|
|
|
|
|
if(CopyBuffer(m_atr_handle, 0, 0, 20, atr_buffer) == 20)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < 20; i++)
|
|
|
|
avg_atr += atr_buffer[i];
|
|
|
|
avg_atr /= 20;
|
|
|
|
|
|
|
|
if(atr > avg_atr * 1.5)
|
|
|
|
return MARKET_VOLATILE;
|
|
|
|
else if(atr < avg_atr * 0.5)
|
|
|
|
return MARKET_QUIET;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return MARKET_RANGING;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-07-30 19:33:01 +01:00
|
|
|
//| Get ATR value - FIXED VERSION |
|
2025-07-20 22:38:24 +01:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CTechnicalAnalysis::GetATR(int period)
|
|
|
|
{
|
|
|
|
double buffer[1];
|
2025-07-30 19:33:01 +01:00
|
|
|
if(CopyBuffer(m_atr_handle, 0, 0, 1, buffer) > 0 && buffer[0] > 0 && buffer[0] != EMPTY_VALUE)
|
|
|
|
{
|
2025-07-20 22:38:24 +01:00
|
|
|
return buffer[0];
|
2025-07-30 19:33:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback calculation if handle fails
|
|
|
|
Print("TechnicalAnalysis: ATR handle failed, calculating manually");
|
|
|
|
|
|
|
|
double sum = 0;
|
|
|
|
for(int i = 1; i <= period; i++)
|
|
|
|
{
|
|
|
|
double high = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double low = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double close_prev = iClose(_Symbol, PERIOD_CURRENT, i + 1);
|
|
|
|
|
|
|
|
double tr = MathMax(high - low, MathMax(MathAbs(high - close_prev), MathAbs(low - close_prev)));
|
|
|
|
sum += tr;
|
|
|
|
}
|
|
|
|
|
|
|
|
double atr = sum / period;
|
|
|
|
|
|
|
|
// Validate result
|
|
|
|
if(atr <= 0 || !MathIsValidNumber(atr))
|
|
|
|
{
|
|
|
|
// Last resort: use 0.1% of current price
|
|
|
|
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
atr = price * 0.001;
|
|
|
|
Print("TechnicalAnalysis: Using price-based ATR fallback: ", atr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return atr;
|
2025-07-20 22:38:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get ADX value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CTechnicalAnalysis::GetADX()
|
|
|
|
{
|
|
|
|
double buffer[1];
|
|
|
|
if(CopyBuffer(m_adx_handle, 0, 0, 1, buffer) > 0)
|
|
|
|
return buffer[0];
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get RSI value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CTechnicalAnalysis::GetRSI()
|
|
|
|
{
|
|
|
|
double buffer[1];
|
|
|
|
if(CopyBuffer(m_rsi_handle, 0, 0, 1, buffer) > 0)
|
|
|
|
return buffer[0];
|
|
|
|
return 50; // Neutral
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get MA200 value |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CTechnicalAnalysis::GetMA200()
|
|
|
|
{
|
|
|
|
double buffer[1];
|
|
|
|
if(CopyBuffer(m_ma200_handle, 0, 0, 1, buffer) > 0)
|
|
|
|
return buffer[0];
|
|
|
|
return SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get trend strength |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CTechnicalAnalysis::GetTrendStrength()
|
|
|
|
{
|
|
|
|
return GetADX();
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get nearest support |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CTechnicalAnalysis::GetNearestSupport(double price)
|
|
|
|
{
|
|
|
|
double nearest = 0;
|
|
|
|
double min_distance = DBL_MAX;
|
|
|
|
|
|
|
|
for(int i = 0; i < m_sr_count; i++)
|
|
|
|
{
|
|
|
|
if(m_sr_levels[i].type == "support" && m_sr_levels[i].price < price)
|
|
|
|
{
|
|
|
|
double distance = price - m_sr_levels[i].price;
|
|
|
|
if(distance < min_distance)
|
|
|
|
{
|
|
|
|
min_distance = distance;
|
|
|
|
nearest = m_sr_levels[i].price;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nearest;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get nearest resistance |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
double CTechnicalAnalysis::GetNearestResistance(double price)
|
|
|
|
{
|
|
|
|
double nearest = 0;
|
|
|
|
double min_distance = DBL_MAX;
|
|
|
|
|
|
|
|
for(int i = 0; i < m_sr_count; i++)
|
|
|
|
{
|
|
|
|
if(m_sr_levels[i].type == "resistance" && m_sr_levels[i].price > price)
|
|
|
|
{
|
|
|
|
double distance = m_sr_levels[i].price - price;
|
|
|
|
if(distance < min_distance)
|
|
|
|
{
|
|
|
|
min_distance = distance;
|
|
|
|
nearest = m_sr_levels[i].price;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nearest;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if price is at support |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CTechnicalAnalysis::IsAtSupport(double price, double tolerance)
|
|
|
|
{
|
|
|
|
if(tolerance == 0)
|
|
|
|
tolerance = GetATR() * 0.1; // 10% of ATR
|
|
|
|
|
|
|
|
for(int i = 0; i < m_sr_count; i++)
|
|
|
|
{
|
|
|
|
if(m_sr_levels[i].type == "support")
|
|
|
|
{
|
|
|
|
if(MathAbs(price - m_sr_levels[i].price) <= tolerance)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if price is at resistance |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CTechnicalAnalysis::IsAtResistance(double price, double tolerance)
|
|
|
|
{
|
|
|
|
if(tolerance == 0)
|
|
|
|
tolerance = GetATR() * 0.1; // 10% of ATR
|
|
|
|
|
|
|
|
for(int i = 0; i < m_sr_count; i++)
|
|
|
|
{
|
|
|
|
if(m_sr_levels[i].type == "resistance")
|
|
|
|
{
|
|
|
|
if(MathAbs(price - m_sr_levels[i].price) <= tolerance)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Check if breakout occurred |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CTechnicalAnalysis::IsBreakout(double price, int lookback)
|
|
|
|
{
|
|
|
|
//--- Check if price broke above recent resistance
|
|
|
|
double highest = 0;
|
|
|
|
for(int i = 1; i <= lookback; i++)
|
|
|
|
{
|
|
|
|
double high = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
if(high > highest)
|
|
|
|
highest = high;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(price > highest && (price - highest) > GetATR() * 0.5)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
//--- Check if price broke below recent support
|
|
|
|
double lowest = DBL_MAX;
|
|
|
|
for(int i = 1; i <= lookback; i++)
|
|
|
|
{
|
|
|
|
double low = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
if(low < lowest)
|
|
|
|
lowest = low;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(price < lowest && (lowest - price) > GetATR() * 0.5)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
//| Get nearest levels above and below price |
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
int CTechnicalAnalysis::GetNearestLevels(double price, TechnicalLevel &above[],
|
|
|
|
TechnicalLevel &below[], int max_count)
|
|
|
|
{
|
|
|
|
int above_count = 0, below_count = 0;
|
|
|
|
|
|
|
|
//--- Find levels above and below
|
|
|
|
for(int i = 0; i < m_sr_count; i++)
|
|
|
|
{
|
|
|
|
if(m_sr_levels[i].price > price && above_count < max_count)
|
|
|
|
{
|
|
|
|
above[above_count] = m_sr_levels[i];
|
|
|
|
above_count++;
|
|
|
|
}
|
|
|
|
else if(m_sr_levels[i].price < price && below_count < max_count)
|
|
|
|
{
|
|
|
|
below[below_count] = m_sr_levels[i];
|
|
|
|
below_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//--- Sort by distance
|
|
|
|
for(int i = 0; i < above_count - 1; i++)
|
|
|
|
{
|
|
|
|
for(int j = i + 1; j < above_count; j++)
|
|
|
|
{
|
|
|
|
if(above[i].price > above[j].price)
|
|
|
|
{
|
|
|
|
TechnicalLevel temp = above[i];
|
|
|
|
above[i] = above[j];
|
|
|
|
above[j] = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i = 0; i < below_count - 1; i++)
|
|
|
|
{
|
|
|
|
for(int j = i + 1; j < below_count; j++)
|
|
|
|
{
|
|
|
|
if(below[i].price < below[j].price)
|
|
|
|
{
|
|
|
|
TechnicalLevel temp = below[i];
|
|
|
|
below[i] = below[j];
|
|
|
|
below[j] = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return MathMax(above_count, below_count);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // TECHNICAL_ANALYSIS_MQH
|
|
|
|
//+------------------------------------------------------------------+
|