Skip to content

Commit 0b6a42c

Browse files
Fix analytics and charts drawdown calculations
Co-authored-by: statikfintechllc <200911899+statikfintechllc@users.noreply.github.com>
1 parent 9d636c1 commit 0b6a42c

File tree

2 files changed

+257
-15
lines changed

2 files changed

+257
-15
lines changed

.github/scripts/generate_analytics.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def calculate_returns_metrics(trades: List[Dict], starting_balance: float, depos
4545
"total_return_percent": 0.0,
4646
"avg_return_percent": 0.0,
4747
"max_drawdown_percent": 0.0,
48+
"inception_max_dd_percent": 0.0,
4849
"avg_risk_percent": 0.0,
4950
"avg_position_size_percent": 0.0
5051
}
@@ -76,15 +77,27 @@ def calculate_returns_metrics(trades: List[Dict], starting_balance: float, depos
7677

7778
peak = 0
7879
max_drawdown_dollars = 0
80+
peak_equity = initial_capital # Track peak equity for proper % calculation
81+
7982
for value in cumulative_pnl:
8083
if value > peak:
8184
peak = value
85+
peak_equity = initial_capital + peak # Update peak equity
8286
drawdown = value - peak
8387
if drawdown < max_drawdown_dollars:
8488
max_drawdown_dollars = drawdown
8589

86-
# Max drawdown % = (Max DD in $) / (Starting Balance + Deposits) * 100
87-
max_drawdown_percent = (max_drawdown_dollars / initial_capital * 100) if initial_capital > 0 else 0
90+
# Max drawdown % = (Max DD in $) / (Peak Equity) * 100
91+
# Using peak equity as denominator (classic drawdown definition)
92+
max_drawdown_percent = (max_drawdown_dollars / peak_equity * 100) if peak_equity > 0 else 0
93+
94+
# Inception drawdown % = min(0, min((equity_t - initial) / initial)) * 100
95+
# This measures drawdown from initial capital
96+
inception_max_dd_percent = 0
97+
for value in cumulative_pnl:
98+
equity_t = initial_capital + value
99+
dd_percent = ((equity_t - initial_capital) / initial_capital * 100) if initial_capital > 0 else 0
100+
inception_max_dd_percent = min(inception_max_dd_percent, dd_percent)
88101

89102
# Average risk per trade (as % of account at time of trade)
90103
# This requires tracking account balance at each trade
@@ -116,6 +129,7 @@ def calculate_returns_metrics(trades: List[Dict], starting_balance: float, depos
116129
"total_return_percent": round(total_return_percent, 2),
117130
"avg_return_percent": round(avg_return_percent, 4),
118131
"max_drawdown_percent": round(max_drawdown_percent, 2),
132+
"inception_max_dd_percent": round(inception_max_dd_percent, 2),
119133
"avg_risk_percent": round(avg_risk_percent, 3),
120134
"avg_position_size_percent": round(avg_position_size_percent, 2)
121135
}
@@ -594,6 +608,10 @@ def main():
594608
"by_setup": {},
595609
"by_session": {},
596610
"drawdown_series": {"labels": [], "values": []},
611+
"drawdowns": {
612+
"classic_percent": 0,
613+
"inception_percent": 0
614+
},
597615
"account": {
598616
"starting_balance": starting_balance,
599617
"total_deposits": total_deposits,
@@ -644,7 +662,6 @@ def main():
644662
"max_win_streak": max_win_streak,
645663
"max_loss_streak": max_loss_streak,
646664
"max_drawdown": max_drawdown,
647-
"max_drawdown_percent": returns_metrics["max_drawdown_percent"],
648665
"kelly_criterion": kelly,
649666
"sharpe_ratio": sharpe_ratio,
650667
"r_multiple_distribution": r_multiple_dist,
@@ -653,6 +670,10 @@ def main():
653670
"by_setup": by_setup,
654671
"by_session": by_session,
655672
"drawdown_series": drawdown_series,
673+
"drawdowns": {
674+
"classic_percent": returns_metrics["max_drawdown_percent"],
675+
"inception_percent": returns_metrics["inception_max_dd_percent"]
676+
},
656677
"returns": {
657678
"total_return_percent": returns_metrics["total_return_percent"],
658679
"avg_return_percent": returns_metrics["avg_return_percent"],

.github/scripts/generate_charts.py

Lines changed: 233 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,29 @@
3939
]
4040

4141

42-
def generate_equity_curve_data(trades):
42+
def generate_equity_curve_data(trades, account_config):
4343
"""
4444
Generate equity curve data from trades
45+
46+
Shows true equity (initial_capital + cumulative P&L) instead of just cumulative P&L
47+
Color is green if final equity > initial, red otherwise
4548
4649
Args:
4750
trades (list): List of trade dictionaries sorted by date
51+
account_config (dict): Account configuration with starting balance, deposits, withdrawals
4852
4953
Returns:
5054
dict: Chart.js compatible data structure
5155
"""
56+
# Get initial capital
57+
starting_balance = account_config.get("starting_balance", 0)
58+
deposits = account_config.get("deposits", [])
59+
withdrawals = account_config.get("withdrawals", [])
60+
61+
total_deposits = sum(d.get("amount", 0) for d in deposits)
62+
total_withdrawals = sum(w.get("amount", 0) for w in withdrawals)
63+
initial_capital = starting_balance + total_deposits - total_withdrawals
64+
5265
if not trades:
5366
return {
5467
"labels": [],
@@ -68,14 +81,14 @@ def generate_equity_curve_data(trades):
6881
trades, key=lambda t: t.get("exit_date", t.get("entry_date", ""))
6982
)
7083

71-
# Calculate cumulative P&L
84+
# Calculate cumulative equity (initial_capital + cumulative P&L)
7285
labels = []
73-
cumulative_pnl = []
74-
running_total = 0
86+
equity_values = []
87+
running_pnl = 0
7588

7689
for trade in sorted_trades:
7790
pnl = trade.get("pnl_usd", 0)
78-
running_total += pnl
91+
running_pnl += pnl
7992

8093
# Use exit date for the equity point
8194
date_str = trade.get("exit_date", trade.get("entry_date", ""))
@@ -85,22 +98,220 @@ def generate_equity_curve_data(trades):
8598
except (ValueError, TypeError):
8699
labels.append(date_str)
87100

88-
cumulative_pnl.append(round(running_total, 2))
101+
# True equity = initial capital + cumulative P&L
102+
equity = initial_capital + running_pnl
103+
equity_values.append(round(equity, 2))
104+
105+
# Determine color based on final vs initial
106+
final_equity = equity_values[-1] if equity_values else initial_capital
107+
if final_equity >= initial_capital:
108+
border_color = "#00ff88" # Green
109+
background_color = "rgba(0, 255, 136, 0.1)"
110+
point_color = "#00ff88"
111+
else:
112+
border_color = "#ff4757" # Red
113+
background_color = "rgba(255, 71, 87, 0.1)"
114+
point_color = "#ff4757"
89115

90116
# Chart.js format
91117
chartjs_data = {
92118
"labels": labels,
93119
"datasets": [
94120
{
95121
"label": "Equity Curve",
96-
"data": cumulative_pnl,
97-
"borderColor": "#00ff88",
98-
"backgroundColor": "rgba(0, 255, 136, 0.1)",
122+
"data": equity_values,
123+
"borderColor": border_color,
124+
"backgroundColor": background_color,
99125
"fill": True,
100126
"tension": 0.4,
101127
"pointRadius": 4,
102128
"pointHoverRadius": 6,
103-
"pointBackgroundColor": "#00ff88",
129+
"pointBackgroundColor": point_color,
130+
"pointBorderColor": "#0a0e27",
131+
"pointBorderWidth": 2,
132+
}
133+
],
134+
}
135+
136+
return chartjs_data
137+
138+
139+
def generate_drawdown_over_time_data(trades, account_config):
140+
"""
141+
Generate classic drawdown over time data as percentage from peak
142+
143+
Converts the drawdown series from dollars to percentage of peak equity
144+
Red border/fill below 0 to indicate drawdowns
145+
146+
Args:
147+
trades (list): List of trade dictionaries sorted by date
148+
account_config (dict): Account configuration with starting balance, deposits, withdrawals
149+
150+
Returns:
151+
dict: Chart.js compatible data structure with drawdown % series
152+
"""
153+
# Get initial capital
154+
starting_balance = account_config.get("starting_balance", 0)
155+
deposits = account_config.get("deposits", [])
156+
withdrawals = account_config.get("withdrawals", [])
157+
158+
total_deposits = sum(d.get("amount", 0) for d in deposits)
159+
total_withdrawals = sum(w.get("amount", 0) for w in withdrawals)
160+
initial_capital = starting_balance + total_deposits - total_withdrawals
161+
162+
if not trades:
163+
return {
164+
"labels": [],
165+
"datasets": [
166+
{
167+
"label": "Drawdown %",
168+
"data": [],
169+
"borderColor": "#ff4757",
170+
"backgroundColor": "rgba(255, 71, 87, 0.2)",
171+
"fill": True,
172+
"tension": 0.4,
173+
}
174+
],
175+
}
176+
177+
# Sort trades by exit date
178+
sorted_trades = sorted(
179+
trades, key=lambda t: t.get("exit_date", t.get("entry_date", ""))
180+
)
181+
182+
# Calculate drawdown % from peak
183+
labels = []
184+
drawdown_percentages = []
185+
running_pnl = 0
186+
peak = 0
187+
peak_equity = initial_capital
188+
189+
for trade in sorted_trades:
190+
pnl = trade.get("pnl_usd", 0)
191+
running_pnl += pnl
192+
193+
# Update peak
194+
if running_pnl > peak:
195+
peak = running_pnl
196+
peak_equity = initial_capital + peak
197+
198+
# Calculate drawdown from peak
199+
drawdown_dollars = running_pnl - peak
200+
drawdown_percent = (drawdown_dollars / peak_equity * 100) if peak_equity > 0 else 0
201+
202+
# Date label
203+
date_str = trade.get("exit_date", trade.get("entry_date", ""))
204+
try:
205+
date_obj = datetime.fromisoformat(str(date_str).split("T")[0])
206+
labels.append(date_obj.strftime("%m/%d"))
207+
except:
208+
labels.append(date_str)
209+
210+
drawdown_percentages.append(round(drawdown_percent, 2))
211+
212+
# Chart.js format
213+
chartjs_data = {
214+
"labels": labels,
215+
"datasets": [
216+
{
217+
"label": "Drawdown %",
218+
"data": drawdown_percentages,
219+
"borderColor": "#ff4757",
220+
"backgroundColor": "rgba(255, 71, 87, 0.2)",
221+
"fill": True,
222+
"tension": 0.4,
223+
"pointRadius": 3,
224+
"pointHoverRadius": 5,
225+
"pointBackgroundColor": "#ff4757",
226+
"pointBorderColor": "#0a0e27",
227+
"pointBorderWidth": 2,
228+
}
229+
],
230+
}
231+
232+
return chartjs_data
233+
234+
235+
def generate_inception_drawdown_data(trades, account_config):
236+
"""
237+
Generate inception drawdown data as percentage from initial capital
238+
239+
Shows drawdown from initial investment rather than from peak
240+
Red border/fill below 0 to indicate drawdowns
241+
242+
Args:
243+
trades (list): List of trade dictionaries sorted by date
244+
account_config (dict): Account configuration with starting balance, deposits, withdrawals
245+
246+
Returns:
247+
dict: Chart.js compatible data structure with inception drawdown % series
248+
"""
249+
# Get initial capital
250+
starting_balance = account_config.get("starting_balance", 0)
251+
deposits = account_config.get("deposits", [])
252+
withdrawals = account_config.get("withdrawals", [])
253+
254+
total_deposits = sum(d.get("amount", 0) for d in deposits)
255+
total_withdrawals = sum(w.get("amount", 0) for w in withdrawals)
256+
initial_capital = starting_balance + total_deposits - total_withdrawals
257+
258+
if not trades:
259+
return {
260+
"labels": [],
261+
"datasets": [
262+
{
263+
"label": "Inception Drawdown %",
264+
"data": [],
265+
"borderColor": "#ff4757",
266+
"backgroundColor": "rgba(255, 71, 87, 0.2)",
267+
"fill": True,
268+
"tension": 0.4,
269+
}
270+
],
271+
}
272+
273+
# Sort trades by exit date
274+
sorted_trades = sorted(
275+
trades, key=lambda t: t.get("exit_date", t.get("entry_date", ""))
276+
)
277+
278+
# Calculate inception drawdown %
279+
labels = []
280+
inception_drawdown_percentages = []
281+
running_pnl = 0
282+
283+
for trade in sorted_trades:
284+
pnl = trade.get("pnl_usd", 0)
285+
running_pnl += pnl
286+
287+
# Calculate equity and drawdown from initial
288+
equity_t = initial_capital + running_pnl
289+
dd_percent = ((equity_t - initial_capital) / initial_capital * 100) if initial_capital > 0 else 0
290+
291+
# Date label
292+
date_str = trade.get("exit_date", trade.get("entry_date", ""))
293+
try:
294+
date_obj = datetime.fromisoformat(str(date_str).split("T")[0])
295+
labels.append(date_obj.strftime("%m/%d"))
296+
except:
297+
labels.append(date_str)
298+
299+
inception_drawdown_percentages.append(round(dd_percent, 2))
300+
301+
# Chart.js format
302+
chartjs_data = {
303+
"labels": labels,
304+
"datasets": [
305+
{
306+
"label": "Inception Drawdown %",
307+
"data": inception_drawdown_percentages,
308+
"borderColor": "#ff4757",
309+
"backgroundColor": "rgba(255, 71, 87, 0.2)",
310+
"fill": True,
311+
"tension": 0.4,
312+
"pointRadius": 3,
313+
"pointHoverRadius": 5,
314+
"pointBackgroundColor": "#ff4757",
104315
"pointBorderColor": "#0a0e27",
105316
"pointBorderWidth": 2,
106317
}
@@ -1210,7 +1421,7 @@ def main():
12101421
print("Generating Chart.js data files...")
12111422

12121423
# 1. Equity Curve
1213-
equity_data = generate_equity_curve_data(trades)
1424+
equity_data = generate_equity_curve_data(trades, account_config)
12141425
save_json_file("index.directory/assets/charts/equity-curve-data.json", equity_data)
12151426
print(" ✓ Equity curve data saved")
12161427

@@ -1234,7 +1445,17 @@ def main():
12341445
save_json_file("index.directory/assets/charts/time-of-day-performance-data.json", time_of_day_data)
12351446
print(" ✓ Time of day performance data saved")
12361447

1237-
# 6. Portfolio Value Charts (all timeframes)
1448+
# 6. Drawdown Over Time
1449+
drawdown_data = generate_drawdown_over_time_data(trades, account_config)
1450+
save_json_file("index.directory/assets/charts/drawdown-over-time-data.json", drawdown_data)
1451+
print(" ✓ Drawdown over time data saved")
1452+
1453+
# 7. Inception Drawdown
1454+
inception_dd_data = generate_inception_drawdown_data(trades, account_config)
1455+
save_json_file("index.directory/assets/charts/inception-drawdown-data.json", inception_dd_data)
1456+
print(" ✓ Inception drawdown data saved")
1457+
1458+
# 8. Portfolio Value Charts (all timeframes)
12381459
print("\nGenerating Portfolio Value charts...")
12391460
generate_portfolio_value_charts(trades, account_config)
12401461

0 commit comments

Comments
 (0)