//+------------------------------------------------------------------+
//|                                   VolatilityScaledModel.mqh      |
//+------------------------------------------------------------------+

#ifndef VOLATILITY_SCALED_MODEL_MQH
#define VOLATILITY_SCALED_MODEL_MQH

#include "RiskTypes.mqh"
#include "LotConverter.mqh"

//+------------------------------------------------------------------+
//| CVolatilityScaledModel                                           |
//+------------------------------------------------------------------+
class CVolatilityScaledModel
  {
private:
   CLotConverter     m_converter;     // shared money-per-point and normalization helper
   int               m_atr_handle;    // indicator handle for the ATR used to derive stop distance
   string            m_atr_symbol;    // symbol the current handle was created for
   ENUM_TIMEFRAMES   m_atr_timeframe; // timeframe the current handle was created for

   bool              EnsureHandle(const string symbol,const ENUM_TIMEFRAMES timeframe,const int atr_period);

public:
                     CVolatilityScaledModel(void);
                    ~CVolatilityScaledModel(void);

   CSizingResult     Calculate(const string symbol,
                               const ENUM_ORDER_TYPE order_type,
                               const ENUM_TIMEFRAMES timeframe,
                               const int atr_period,
                               const double atr_multiplier,
                               const double risk_pct);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CVolatilityScaledModel::CVolatilityScaledModel(void)
  {
   m_atr_handle    = INVALID_HANDLE;
   m_atr_symbol    = "";
   m_atr_timeframe = PERIOD_CURRENT;
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CVolatilityScaledModel::~CVolatilityScaledModel(void)
  {
   if(m_atr_handle != INVALID_HANDLE)
      ::IndicatorRelease(m_atr_handle);
  }

//+------------------------------------------------------------------+
//| EnsureHandle                                                     |
//+------------------------------------------------------------------+
bool CVolatilityScaledModel::EnsureHandle(const string symbol,const ENUM_TIMEFRAMES timeframe,const int atr_period)
  {
   if(m_atr_handle != INVALID_HANDLE && m_atr_symbol == symbol && m_atr_timeframe == timeframe)
      return(true);

   if(m_atr_handle != INVALID_HANDLE)
     {
      ::IndicatorRelease(m_atr_handle);
      m_atr_handle = INVALID_HANDLE;
     }

   m_atr_handle = ::iATR(symbol,timeframe,atr_period);

   if(m_atr_handle == INVALID_HANDLE)
      return(false);

   m_atr_symbol    = symbol;
   m_atr_timeframe = timeframe;

   return(true);
  }

//+------------------------------------------------------------------+
//| Calculate                                                        |
//+------------------------------------------------------------------+
CSizingResult CVolatilityScaledModel::Calculate(const string symbol,
      const ENUM_ORDER_TYPE order_type,
      const ENUM_TIMEFRAMES timeframe,
      const int atr_period,
      const double atr_multiplier,
      const double risk_pct)
  {
   CSizingResult result;
   result.model_used = RISK_MODEL_VOLATILITY_SCALED;

   if(risk_pct <= 0.0)
     {
      result.success = false;
      result.reason  = "risk_pct must be positive";
      return(result);
     }

   if(atr_multiplier <= 0.0)
     {
      result.success = false;
      result.reason  = "atr_multiplier must be positive";
      return(result);
     }

   if(!EnsureHandle(symbol,timeframe,atr_period))
     {
      result.success = false;
      result.reason  = "unable to create ATR indicator handle";
      return(result);
     }

   double atr_buffer[];
   ::ArraySetAsSeries(atr_buffer,true);

//--- read index 1, the most recently completed bar's ATR value, rather than index 0,
//--- which represents the still-forming current bar and would change on every tick
   if(::CopyBuffer(m_atr_handle,0,1,1,atr_buffer) <= 0)
     {
      result.success = false;
      result.reason  = "unable to read ATR buffer, indicator may still be calculating";
      return(result);
     }

   double atr_price_distance = atr_buffer[0];

   if(atr_price_distance <= 0.0)
     {
      result.success = false;
      result.reason  = "ATR returned a non-positive value";
      return(result);
     }

   double point_size = ::SymbolInfoDouble(symbol,SYMBOL_POINT);

   if(point_size <= 0.0)
     {
      result.success = false;
      result.reason  = "unable to read a valid point size for " + symbol;
      return(result);
     }

   double atr_points  = atr_price_distance / point_size;
   double stop_points = atr_points * atr_multiplier;

   result.stop_points = stop_points;

   double balance     = ::AccountInfoDouble(ACCOUNT_BALANCE);
   double risk_amount = balance * (risk_pct / 100.0);

   double actual_risk_amount = 0.0;
   double lots = m_converter.LotsForRisk(symbol,order_type,risk_amount,stop_points,actual_risk_amount);

   if(lots <= 0.0)
     {
      result.success = false;
      result.reason  = "unable to compute a valid lot size for the requested risk";
      return(result);
     }

   result.success             = true;
   result.lots                = lots;
   result.risk_amount         = risk_amount;
   result.actual_risk_amount  = actual_risk_amount;
   result.scaling_factor      = 1.0;
   result.reason              = "volatility-scaled sizing, ATR=" + DoubleToString(atr_points,1) +
                                "pts x" + DoubleToString(atr_multiplier,2) +
                                " -> stop=" + DoubleToString(stop_points,1) + "pts";

   return(result);
  }

#endif // VOLATILITY_SCALED_MODEL_MQH
//+------------------------------------------------------------------+