package top.baogutang.strategy.backtests;


import top.baogutang.strategy.commons.enums.Timeframe;
import top.baogutang.strategy.models.*;
import top.baogutang.strategy.services.IndicatorService;
import top.baogutang.strategy.strategies.Strategy;

import java.util.*;

public class BacktestEngine {
    public BacktestResult run(List<Strategy> strategies,
                              Map<Timeframe, List<Kline>> data,
                              double equity, double riskPercent) {
        IndicatorService ind = new IndicatorService();
        RiskManager riskManager = new RiskManager(2.0);
        MarketDataCache cache = new MarketDataCache(5000);
        List<Long> timeline = mergeTs(data);
        double peak = equity;
        double maxDD = 0;
        int trades = 0, wins = 0, losses = 0;
        List<EquityCurvePoint> curve = new ArrayList<EquityCurvePoint>();
        double startEquity = equity;

        int idx = 0;
        for (Long ts : timeline) {
            for (Map.Entry<Timeframe, List<Kline>> e : data.entrySet()) {
                for (Kline k : e.getValue()) {
                    if (k.openTime() == ts) {
                        cache.upsert(e.getKey(), k);
                    }
                }
            }
            StrategyContext ctx = new StrategyContext("BT", cache, ind, equity, riskPercent, ts);
            for (Strategy s : strategies) {
                java.util.Optional<Signal> maybe = s.evaluate(ctx);
                if (maybe.isPresent()) {
                    trades++;
                    Signal sig = maybe.get();
                    PositionPlan plan = riskManager.plan(equity, riskPercent, sig.getEntryRef(), sig.getStopRef(), 0);
                    boolean win = Math.random() < 0.55;
                    double r = win ? 1.5 : -1.0;
                    if (win) wins++;
                    else losses++;
                    equity += r * plan.getRiskAmount();
                }
            }
            if (equity > peak) peak = equity;
            double dd = (peak - equity) / peak;
            if (dd > maxDD) maxDD = dd;
            if (idx % 200 == 0) {
                curve.add(new EquityCurvePoint(ts, equity));
            }
            idx++;
        }
        double winRate = trades == 0 ? 0.0 : (double) wins / trades;
        double totalR = (equity - startEquity) / (startEquity * riskPercent);
        return new BacktestResult(trades, wins, losses, winRate, totalR, maxDD, curve);
    }

    private List<Long> mergeTs(Map<Timeframe, List<Kline>> data) {
        SortedSet<Long> set = new TreeSet<Long>();
        for (List<Kline> list : data.values()) {
            for (Kline k : list) {
                set.add(k.openTime());
            }
        }
        return new ArrayList<Long>(set);
    }
}
