File size: 5,058 Bytes
68025ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import numpy as np
from config import INITIAL_CAPITAL, TRADE_FEE


class Portfolio:
    def __init__(self, initial_capital: float = INITIAL_CAPITAL):
        self.initial_capital = initial_capital
        self.cash = initial_capital
        self.position = 0.0  # units of asset held
        self.trades = []
        self.equity_history = []  # [{date, value, price, action}]
        self.peak_value = initial_capital

    def apply_decision(self, decision: dict, price: float, date: str):
        action = decision.get("action", "HOLD")
        size = float(decision.get("size", 0.5))
        size = max(0.0, min(1.0, size))

        trade_executed = False
        trade_value = 0.0

        if action == "BUY" and self.cash > 0:
            spend = self.cash * size
            fee = spend * TRADE_FEE
            net_spend = spend - fee
            units = net_spend / price
            self.position += units
            self.cash -= spend
            trade_executed = True
            trade_value = spend
            self.trades.append({
                "date": date,
                "action": "BUY",
                "price": price,
                "units": units,
                "value": spend,
                "fee": fee,
            })

        elif action == "SELL" and self.position > 0:
            sell_units = self.position * size
            gross = sell_units * price
            fee = gross * TRADE_FEE
            net = gross - fee
            self.position -= sell_units
            self.cash += net
            trade_executed = True
            trade_value = gross
            self.trades.append({
                "date": date,
                "action": "SELL",
                "price": price,
                "units": sell_units,
                "value": gross,
                "fee": fee,
            })

        total_value = self.cash + self.position * price
        self.peak_value = max(self.peak_value, total_value)

        self.equity_history.append({
            "date": date,
            "value": round(total_value, 2),
            "cash": round(self.cash, 2),
            "position": self.position,
            "price": price,
            "action": action,
            "trade_executed": trade_executed,
            "trade_value": round(trade_value, 2),
        })

    def current_value(self, price: float) -> float:
        return self.cash + self.position * price

    def drawdown(self, price: float) -> float:
        current = self.current_value(price)
        if self.peak_value == 0:
            return 0.0
        return (self.peak_value - current) / self.peak_value

    def snapshot(self, price: float) -> dict:
        total = self.current_value(price)
        return {
            "cash": round(self.cash, 2),
            "position": self.position,
            "total_value": round(total, 2),
            "drawdown": round(self.drawdown(price), 4),
        }


def compute_metrics(equity_history: list, initial_capital: float, hodl_final: float) -> dict:
    if not equity_history:
        return {}

    values = [e["value"] for e in equity_history]
    dates = [e["date"] for e in equity_history]

    # Daily returns
    returns = []
    for i in range(1, len(values)):
        r = (values[i] - values[i - 1]) / values[i - 1] if values[i - 1] != 0 else 0
        returns.append(r)

    returns_arr = np.array(returns)
    final_value = values[-1]

    # Cumulative Return
    cumulative_return = (final_value - initial_capital) / initial_capital

    # Sharpe Ratio (annualized, risk-free = 0)
    if len(returns_arr) > 1 and returns_arr.std() > 0:
        sharpe = (returns_arr.mean() / returns_arr.std()) * np.sqrt(252)
    else:
        sharpe = 0.0

    # Sortino Ratio
    downside = returns_arr[returns_arr < 0]
    if len(downside) > 0 and downside.std() > 0:
        sortino = (returns_arr.mean() / downside.std()) * np.sqrt(252)
    else:
        sortino = 0.0

    # Max Drawdown
    peak = initial_capital
    max_dd = 0.0
    for v in values:
        if v > peak:
            peak = v
        dd = (peak - v) / peak if peak > 0 else 0
        max_dd = max(max_dd, dd)

    # Win Rate
    winning_trades = sum(1 for e in equity_history if e.get("trade_executed") and e.get("action") == "BUY")
    num_trades = sum(1 for e in equity_history if e.get("trade_executed"))
    win_rate = winning_trades / num_trades if num_trades > 0 else 0.0

    # vs HODL
    hodl_return = (hodl_final - initial_capital) / initial_capital
    alpha = cumulative_return - hodl_return

    return {
        "cumulative_return": round(cumulative_return, 4),
        "sharpe_ratio": round(sharpe, 4),
        "sortino_ratio": round(sortino, 4),
        "max_drawdown": round(max_dd, 4),
        "win_rate": round(win_rate, 4),
        "num_trades": num_trades,
        "final_value": round(final_value, 2),
        "hodl_return": round(hodl_return, 4),
        "alpha": round(alpha, 4),
        "start_date": dates[0] if dates else "",
        "end_date": dates[-1] if dates else "",
        "num_days": len(values),
    }