//+------------------------------------------------------------------+
//| FailureGuard.mqh - 破綻回避ガード追加モジュール (2026-05-29)     |
//|                                                                  |
//| 13カテゴリ40+ EAの破綻分析 (analysis/EA_FAILURE_ANALYSIS_*.md)   |
//| から導出された「破綻を予防する」実機監視ガード。                  |
//|                                                                  |
//| 既存の CommonRiskGuard.mqh と併用すること:                       |
//|   #include <CommonRiskGuard.mqh>                                 |
//|   #include <FailureGuard.mqh>                                    |
//|                                                                  |
//| OnInit() で FG_Init() を呼ぶ                                     |
//| OnTick() のエントリ判定前に FG_CanTrade() で確認                 |
//| ポジション開設後に FG_OnPositionOpened(lot) を呼ぶ              |
//+------------------------------------------------------------------+
#ifndef __FAILURE_GUARD_MQH__
#define __FAILURE_GUARD_MQH__

//+------------------------------------------------------------------+
//| 破綻パターン対策入力パラメーター                                  |
//+------------------------------------------------------------------+
input group "=== Failure Guard (破綻パターン対策) ==="

// パターン3 対策: ナンピン/グリッド爆発防止 (ナンピンEAのみ有効化推奨)
input bool   FG_PreventNanpinExplosion = false; // ナンピン累計lot制限 (ナンピンEAのみtrue)
input double FG_MaxTotalLotForNanpin  = 10.0;   // ナンピン総lot上限 (緩和: 通常EAではトリガしない)
input double FG_NanpinHardEquityFloor = 0.0;    // 残高Floor=0で無効 (通常EAでは誤発動)

// パターン1 対策: 短期最適化幻 → 実機運用での DD ratio 監視 (慎重EAのみ推奨)
input bool   FG_MonitorDDRatio        = false;  // 含み損監視 (慎重運用EAのみtrue)
input double FG_MaxOpenDDPct          = 20.0;   // 含み損X%超で警告 (緩和)
input double FG_HardStopDDPct         = 40.0;   // 含み損X%超で全閉鎖 (緩和)

// パターン4 対策: 高勝率EAの統計的逸脱検出
input bool   FG_DetectWinrateAnomaly  = true;   // 勝率異常検出
input double FG_AlertWinrateAbove     = 85.0;   // 勝率X%超 = ナンピン疑い (運用警告)
input int    FG_AnomalyMinTrades      = 30;     // X取引以上で勝率判定発動

// パターン5 対策: 取引頻度監視 (実装失敗・戦略期間外)
input bool   FG_MonitorTradeFreq      = true;   // 取引頻度監視
input int    FG_MinTradesPerMonth     = 2;      // 月X取引未満で警告 (戦略期間外可能性)
input int    FG_MaxTradesPerDay       = 50;     // 1日X取引超 = グリッド暴走の可能性

// 緊急 Auto Withdrawal 推奨 (破綻前提カテゴリ向け)
input bool   FG_WarnDailyWithdraw     = false;  // 残高X倍超で「毎日出金」推奨警告
input double FG_WarnBalanceMultiplier = 1.5;    // 初期残高のX倍超で警告開始

//+------------------------------------------------------------------+
//| 内部状態                                                          |
//+------------------------------------------------------------------+
static double   __fg_initialBalance     = 0;
static datetime __fg_startTime          = 0;
static int      __fg_winCount           = 0;
static int      __fg_lossCount          = 0;
static int      __fg_dailyTradeCount    = 0;
static datetime __fg_lastTradeDay       = 0;
static bool     __fg_emergencyTriggered = false;
static datetime __fg_lastWithdrawAlert  = 0;
static datetime __fg_lastFreqAlert      = 0;

//+------------------------------------------------------------------+
//| 初期化                                                            |
//+------------------------------------------------------------------+
void FG_Init()
{
    __fg_initialBalance = AccountInfoDouble(ACCOUNT_BALANCE);
    __fg_startTime = TimeCurrent();
    __fg_winCount = 0;
    __fg_lossCount = 0;
    __fg_dailyTradeCount = 0;
    __fg_emergencyTriggered = false;
    Print("[FG] FailureGuard v1 initialized. Initial balance=", __fg_initialBalance);
}

//+------------------------------------------------------------------+
//| 現在の全保有ポジションの累計 lot                                  |
//+------------------------------------------------------------------+
double FG_GetMyTotalLot(ulong magic)
{
    double total = 0;
    for(int i = 0; i < PositionsTotal(); i++)
    {
        if(PositionGetTicket(i) <= 0) continue;
        if(PositionGetInteger(POSITION_MAGIC) != (long)magic) continue;
        total += PositionGetDouble(POSITION_VOLUME);
    }
    return total;
}

//+------------------------------------------------------------------+
//| 現在の含み損 (USD)                                               |
//+------------------------------------------------------------------+
double FG_GetMyOpenPnL(ulong magic)
{
    double pnl = 0;
    for(int i = 0; i < PositionsTotal(); i++)
    {
        if(PositionGetTicket(i) <= 0) continue;
        if(PositionGetInteger(POSITION_MAGIC) != (long)magic) continue;
        pnl += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
    }
    return pnl;
}

//+------------------------------------------------------------------+
//| エントリ可能か判定                                                |
//+------------------------------------------------------------------+
bool FG_CanTrade(ulong magic)
{
    if(__fg_emergencyTriggered) return false;

    // 1. 含み損 monitoring
    if(FG_MonitorDDRatio)
    {
        double openPnL = FG_GetMyOpenPnL(magic);
        if(openPnL < 0)
        {
            double bal = AccountInfoDouble(ACCOUNT_BALANCE);
            double ddPct = MathAbs(openPnL) / bal * 100.0;

            if(ddPct >= FG_HardStopDDPct)
            {
                Print("[FG HARD STOP] Open DD=", DoubleToString(ddPct, 2),
                      "% >= ", FG_HardStopDDPct, "%. Closing all my positions.");
                FG_CloseMyAllPositions(magic);
                __fg_emergencyTriggered = true;
                return false;
            }
            if(ddPct >= FG_MaxOpenDDPct)
            {
                if(TimeCurrent() - __fg_lastFreqAlert > 3600)  // 1h cooldown
                {
                    Print("[FG WARN] Open DD=", DoubleToString(ddPct, 2),
                          "% >= ", FG_MaxOpenDDPct, "%. New entries blocked.");
                    __fg_lastFreqAlert = TimeCurrent();
                }
                return false;
            }
        }
    }

    // 2. ナンピン累計lot 制限
    if(FG_PreventNanpinExplosion)
    {
        double totalLot = FG_GetMyTotalLot(magic);
        if(totalLot >= FG_MaxTotalLotForNanpin)
        {
            Print("[FG NANPIN GUARD] Total lot=", totalLot,
                  " >= ", FG_MaxTotalLotForNanpin, ". Blocking new entries.");
            return false;
        }
    }

    // 3. 残高Floor 守り
    if(FG_NanpinHardEquityFloor > 0)
    {
        double eq = AccountInfoDouble(ACCOUNT_EQUITY);
        double bal = __fg_initialBalance;
        double pct = eq / bal * 100.0;
        if(pct < FG_NanpinHardEquityFloor)
        {
            Print("[FG EQUITY FLOOR] Equity=", DoubleToString(pct, 1),
                  "% < ", FG_NanpinHardEquityFloor, "%. Emergency close.");
            FG_CloseMyAllPositions(magic);
            __fg_emergencyTriggered = true;
            return false;
        }
    }

    // 4. 1日取引回数上限
    if(FG_MonitorTradeFreq && FG_MaxTradesPerDay > 0)
    {
        datetime today = iTime(_Symbol, PERIOD_D1, 0);
        if(today != __fg_lastTradeDay)
        {
            __fg_lastTradeDay = today;
            __fg_dailyTradeCount = 0;
        }
        if(__fg_dailyTradeCount >= FG_MaxTradesPerDay)
        {
            return false;
        }
    }

    return true;
}

//+------------------------------------------------------------------+
//| ポジション開設後の処理                                            |
//+------------------------------------------------------------------+
void FG_OnPositionOpened(double lot)
{
    __fg_dailyTradeCount++;

    // 残高警告 (出金推奨)
    if(FG_WarnDailyWithdraw)
    {
        double bal = AccountInfoDouble(ACCOUNT_BALANCE);
        if(bal >= __fg_initialBalance * FG_WarnBalanceMultiplier)
        {
            if(TimeCurrent() - __fg_lastWithdrawAlert > 86400)  // 1日1回
            {
                Print("[FG WITHDRAW REC] Balance=$", bal,
                      " (>", FG_WarnBalanceMultiplier,
                      "x initial). Consider daily withdrawal to preserve gains.");
                __fg_lastWithdrawAlert = TimeCurrent();
            }
        }
    }
}

//+------------------------------------------------------------------+
//| トレード結果 (Win/Loss) を記録                                   |
//+------------------------------------------------------------------+
void FG_OnTradeClosed(double profit)
{
    if(profit > 0) __fg_winCount++;
    else if(profit < 0) __fg_lossCount++;

    // 勝率異常検出
    if(FG_DetectWinrateAnomaly)
    {
        int total = __fg_winCount + __fg_lossCount;
        if(total >= FG_AnomalyMinTrades)
        {
            double wr = (double)__fg_winCount / total * 100.0;
            if(wr >= FG_AlertWinrateAbove)
            {
                Print("[FG ANOMALY] WR=", DoubleToString(wr, 1),
                      "% >= ", FG_AlertWinrateAbove,
                      "%. Nanpin/grid suspect: small wins frequent, large loss imminent.");
            }
        }
    }
}

//+------------------------------------------------------------------+
//| 取引頻度警告 (バックグラウンドで定期チェック)                     |
//+------------------------------------------------------------------+
void FG_CheckTradeFrequency()
{
    if(!FG_MonitorTradeFreq) return;
    if(__fg_startTime == 0) return;

    double daysRun = (double)(TimeCurrent() - __fg_startTime) / 86400.0;
    if(daysRun < 30) return;  // 30日未満は判定スキップ

    int totalTrades = __fg_winCount + __fg_lossCount;
    double tradesPerMonth = totalTrades / (daysRun / 30.0);

    if(tradesPerMonth < FG_MinTradesPerMonth)
    {
        if(TimeCurrent() - __fg_lastFreqAlert > 86400)
        {
            Print("[FG LOW FREQ] ", DoubleToString(tradesPerMonth, 1),
                  " trades/month < ", FG_MinTradesPerMonth,
                  ". Strategy may be out of regime, or implementation issue.");
            __fg_lastFreqAlert = TimeCurrent();
        }
    }
}

//+------------------------------------------------------------------+
//| 自Magicの全ポジション閉鎖                                         |
//+------------------------------------------------------------------+
void FG_CloseMyAllPositions(ulong magic)
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(ticket <= 0) continue;
        if(PositionGetInteger(POSITION_MAGIC) != (long)magic) continue;

        MqlTradeRequest req;
        MqlTradeResult res;
        ZeroMemory(req);
        ZeroMemory(res);
        req.action = TRADE_ACTION_DEAL;
        req.position = ticket;
        req.symbol = PositionGetString(POSITION_SYMBOL);
        req.volume = PositionGetDouble(POSITION_VOLUME);
        req.type = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
        req.price = (req.type == ORDER_TYPE_SELL) ? SymbolInfoDouble(req.symbol, SYMBOL_BID) : SymbolInfoDouble(req.symbol, SYMBOL_ASK);
        req.deviation = 20;
        req.magic = magic;
        req.comment = "FG_EMERGENCY";
        OrderSend(req, res);
    }
}

//+------------------------------------------------------------------+
//| 解除 (緊急停止を手動解除)                                        |
//+------------------------------------------------------------------+
void FG_ResetEmergency()
{
    __fg_emergencyTriggered = false;
    Print("[FG] Emergency flag reset.");
}

#endif // __FAILURE_GUARD_MQH__
