"""3절 — 제일 작은 백테스트 한 번.

BTCUSDT 1일봉 89개(확정분)를 Binance 공개 API 에서 받아
SIM venue 하나 · 전략 하나로 돌린다.
"""
import json
import pathlib

import pandas as pd
from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.backtest.engine import BacktestEngineConfig
from nautilus_trader.config import LoggingConfig
from nautilus_trader.model import Bar, BarType, Money, Price, Quantity
from nautilus_trader.model.currencies import USDT
from nautilus_trader.model.enums import AccountType, OmsType, OrderSide
from nautilus_trader.test_kit.providers import TestInstrumentProvider
from nautilus_trader.trading.strategy import Strategy

HERE = pathlib.Path(__file__).parent
BAR_TYPE = "BTCUSDT.BINANCE-1-DAY-LAST-EXTERNAL"


def load_bars(instrument, bar_type_str=BAR_TYPE, path="data/btcusdt-1d-90.json", drop_last=True):
    raw = json.loads((HERE / path).read_text())
    if drop_last:
        raw = raw[:-1]  # 마지막 봉은 아직 진행 중 (close_time > now)
    bt = BarType.from_str(bar_type_str)
    bars = []
    for k in raw:
        close_ns = int(k[6]) * 1_000_000 + 999_999  # close_time(ms) → ns
        bars.append(
            Bar(
                bar_type=bt,
                open=Price.from_str(f"{float(k[1]):.2f}"),
                high=Price.from_str(f"{float(k[2]):.2f}"),
                low=Price.from_str(f"{float(k[3]):.2f}"),
                close=Price.from_str(f"{float(k[4]):.2f}"),
                volume=Quantity.from_str(f"{float(k[5]):.6f}"),
                ts_event=close_ns,
                ts_init=close_ns,
            )
        )
    return bars


class Breakout(Strategy):
    """N일 고가 돌파에 들어가고 M일 저가 이탈에 나온다. 지표는 직접 센다."""

    def __init__(self, instrument_id, bar_type, n=20, m=10, qty="0.010000"):
        super().__init__()
        self.instrument_id = instrument_id
        self.bar_type = BarType.from_str(bar_type)
        self.n, self.m = n, m
        self.qty = Quantity.from_str(qty)
        self.highs, self.lows = [], []
        self.decisions = []

    def on_start(self):
        self.subscribe_bars(self.bar_type)

    def on_bar(self, bar: Bar):
        h, l, c = float(bar.high), float(bar.low), float(bar.close)
        if len(self.highs) >= self.n:
            long_pos = self.portfolio.is_net_long(self.instrument_id)
            hi_n = max(self.highs[-self.n:])
            lo_m = min(self.lows[-self.m:])
            if not long_pos and c > hi_n:
                self.submit_order(self.order_factory.market(self.instrument_id, OrderSide.BUY, self.qty))
                self.decisions.append((bar.ts_event, "BUY", c, hi_n))
            elif long_pos and c < lo_m:
                self.submit_order(self.order_factory.market(self.instrument_id, OrderSide.SELL, self.qty))
                self.decisions.append((bar.ts_event, "SELL", c, lo_m))
        self.highs.append(h)
        self.lows.append(l)


def build(fill_model=None, liquidity_consumption=False, log_level="WARNING"):
    eng = BacktestEngine(BacktestEngineConfig(
        trader_id="BACKTESTER-001",
        logging=LoggingConfig(log_level=log_level, bypass_logging=(log_level == "OFF")),
    ))
    inst = TestInstrumentProvider.btcusdt_binance()
    kw = {}
    if fill_model is not None:
        kw["fill_model"] = fill_model
    eng.add_venue(
        venue=inst.id.venue,
        oms_type=OmsType.NETTING,
        account_type=AccountType.CASH,
        base_currency=None,
        starting_balances=[Money(100_000, USDT), Money(0, inst.base_currency)],
        **kw,
    )
    eng.add_instrument(inst)
    eng.add_data(load_bars(inst))
    return eng, inst


if __name__ == "__main__":
    eng, inst = build()
    strat = Breakout(inst.id, BAR_TYPE)
    eng.add_strategy(strat)
    eng.run()

    print("\n===== 계좌 =====")
    print(eng.trader.generate_account_report(inst.id.venue).tail(3).to_string())
    fills = eng.trader.generate_order_fills_report()
    print("\n===== 체결 =====")
    if not fills.empty:
        cols = [c for c in ["client_order_id", "side", "type", "quantity", "avg_px", "commission", "ts_last"] if c in fills.columns]
        print(fills[cols].to_string())
    pos = eng.trader.generate_positions_report()
    print("\n===== 포지션 =====")
    if not pos.empty:
        cols = [c for c in ["side", "quantity", "avg_px_open", "avg_px_close", "realized_pnl", "realized_return"] if c in pos.columns]
        print(pos[cols].to_string())
    print("\niterations =", eng.iteration)
    eng.dispose()
