Skip to content

Commit b4de584

Browse files
Merge branch 'master' into dependabot/npm_and_yarn/marked-gfm-heading-id-4.1.3
2 parents d0066bd + ba60dcb commit b4de584

File tree

111 files changed

+47778
-801
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+47778
-801
lines changed

.github/scripts/export_csv.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import argparse
1414
from datetime import datetime
1515
import os
16-
import sys
17-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
16+
from globals_utils import setup_imports
17+
18+
# Setup imports
19+
setup_imports(__file__)
1820
from utils import load_trades_index
1921

2022

.github/scripts/generate_analytics.py

Lines changed: 169 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
import os
1818
from datetime import datetime
1919
from typing import Dict, List, Tuple
20-
import sys
21-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
20+
from globals_utils import setup_imports, save_json_file, ensure_directory
21+
22+
# Setup imports
23+
setup_imports(__file__)
2224
from utils import load_trades_index, load_account_config
2325

2426
# Constants
@@ -319,6 +321,161 @@ def calculate_kelly_criterion(trades: List[Dict]) -> float:
319321
return round(kelly * 100, 1)
320322

321323

324+
def calculate_sharpe_ratio(trades: List[Dict], risk_free_rate: float = 0.0) -> float:
325+
"""
326+
Calculate Sharpe Ratio - risk-adjusted returns
327+
328+
Sharpe Ratio = (Average Return - Risk Free Rate) / Standard Deviation of Returns
329+
330+
Args:
331+
trades: List of trade dictionaries
332+
risk_free_rate: Risk-free rate (default 0% for simplicity)
333+
334+
Returns:
335+
float: Sharpe ratio
336+
"""
337+
if not trades or len(trades) < 2:
338+
return 0.0
339+
340+
# Get returns as percentages
341+
returns = [t.get("pnl_percent", 0) for t in trades]
342+
343+
# Calculate average return
344+
avg_return = sum(returns) / len(returns)
345+
346+
# Calculate standard deviation
347+
variance = sum((r - avg_return) ** 2 for r in returns) / len(returns)
348+
std_dev = variance ** 0.5
349+
350+
if std_dev == 0:
351+
return 0.0
352+
353+
sharpe = (avg_return - risk_free_rate) / std_dev
354+
355+
return round(sharpe, 2)
356+
357+
358+
def calculate_r_multiple_distribution(trades: List[Dict]) -> Dict:
359+
"""
360+
Calculate R-Multiple distribution - returns in risk units
361+
362+
R-Multiple = (Exit Price - Entry Price) / (Entry Price - Stop Loss)
363+
This shows how many times your initial risk you made or lost
364+
365+
Args:
366+
trades: List of trade dictionaries
367+
368+
Returns:
369+
Dict: R-multiple distribution data
370+
"""
371+
if not trades:
372+
return {
373+
"labels": [],
374+
"data": [],
375+
"avg_r_multiple": 0,
376+
"median_r_multiple": 0
377+
}
378+
379+
r_multiples = []
380+
381+
for trade in trades:
382+
entry_price = trade.get("entry_price", 0)
383+
exit_price = trade.get("exit_price", 0)
384+
stop_loss = trade.get("stop_loss", 0)
385+
direction = trade.get("direction", "LONG")
386+
387+
if entry_price == 0 or stop_loss == 0:
388+
continue
389+
390+
# Calculate risk (distance from entry to stop)
391+
if direction == "LONG":
392+
risk = entry_price - stop_loss
393+
gain = exit_price - entry_price
394+
else: # SHORT
395+
risk = stop_loss - entry_price
396+
gain = entry_price - exit_price
397+
398+
if risk <= 0:
399+
continue
400+
401+
# R-multiple is gain divided by risk
402+
r_multiple = gain / risk
403+
r_multiples.append(r_multiple)
404+
405+
if not r_multiples:
406+
return {
407+
"labels": [],
408+
"data": [],
409+
"avg_r_multiple": 0,
410+
"median_r_multiple": 0
411+
}
412+
413+
# Create histogram buckets
414+
buckets = {
415+
"< -2R": 0,
416+
"-2R to -1R": 0,
417+
"-1R to 0R": 0,
418+
"0R to 1R": 0,
419+
"1R to 2R": 0,
420+
"2R to 3R": 0,
421+
"> 3R": 0
422+
}
423+
424+
for r in r_multiples:
425+
if r < -2:
426+
buckets["< -2R"] += 1
427+
elif r < -1:
428+
buckets["-2R to -1R"] += 1
429+
elif r < 0:
430+
buckets["-1R to 0R"] += 1
431+
elif r < 1:
432+
buckets["0R to 1R"] += 1
433+
elif r < 2:
434+
buckets["1R to 2R"] += 1
435+
elif r < 3:
436+
buckets["2R to 3R"] += 1
437+
else:
438+
buckets["> 3R"] += 1
439+
440+
# Calculate statistics
441+
avg_r = sum(r_multiples) / len(r_multiples)
442+
sorted_r = sorted(r_multiples)
443+
median_r = (
444+
sorted_r[len(sorted_r) // 2]
445+
if len(sorted_r) % 2 == 1
446+
else (sorted_r[len(sorted_r) // 2 - 1] + sorted_r[len(sorted_r) // 2]) / 2
447+
)
448+
449+
return {
450+
"labels": list(buckets.keys()),
451+
"data": list(buckets.values()),
452+
"avg_r_multiple": round(avg_r, 2),
453+
"median_r_multiple": round(median_r, 2)
454+
}
455+
456+
457+
def calculate_mae_mfe_analysis(trades: List[Dict]) -> Dict:
458+
"""
459+
Calculate MAE (Mean Adverse Excursion) and MFE (Mean Favorable Excursion)
460+
461+
Note: This requires intraday data collection which is not currently available.
462+
For now, we'll return a placeholder indicating this feature requires additional data.
463+
464+
Args:
465+
trades: List of trade dictionaries
466+
467+
Returns:
468+
Dict: MAE/MFE analysis placeholder
469+
"""
470+
return {
471+
"available": False,
472+
"message": "MAE/MFE analysis requires intraday price data collection. This feature will be available once intraday high/low prices are tracked for each trade.",
473+
"mae_avg": 0,
474+
"mfe_avg": 0,
475+
"note": "To enable this metric, add 'intraday_high' and 'intraday_low' fields to trade entries."
476+
}
477+
478+
322479
def aggregate_by_tag(trades: List[Dict], tag_field: str) -> Dict:
323480
"""
324481
Aggregate statistics by a tag field (strategy, setup, etc.)
@@ -463,6 +620,11 @@ def main():
463620
)
464621
kelly = calculate_kelly_criterion(sorted_trades)
465622

623+
# Calculate advanced analytics
624+
sharpe_ratio = calculate_sharpe_ratio(sorted_trades)
625+
r_multiple_dist = calculate_r_multiple_distribution(sorted_trades)
626+
mae_mfe = calculate_mae_mfe_analysis(sorted_trades)
627+
466628
# Calculate percentage-based returns metrics
467629
returns_metrics = calculate_returns_metrics(
468630
sorted_trades,
@@ -484,6 +646,9 @@ def main():
484646
"max_drawdown": max_drawdown,
485647
"max_drawdown_percent": returns_metrics["max_drawdown_percent"],
486648
"kelly_criterion": kelly,
649+
"sharpe_ratio": sharpe_ratio,
650+
"r_multiple_distribution": r_multiple_dist,
651+
"mae_mfe_analysis": mae_mfe,
487652
"by_strategy": by_strategy,
488653
"by_setup": by_setup,
489654
"by_session": by_session,
@@ -504,11 +669,10 @@ def main():
504669
}
505670

506671
# Save analytics data
507-
os.makedirs("index.directory/assets/charts", exist_ok=True)
672+
ensure_directory("index.directory/assets/charts")
508673
output_file = "index.directory/assets/charts/analytics-data.json"
509674

510-
with open(output_file, "w", encoding="utf-8") as f:
511-
json.dump(analytics, f, indent=2)
675+
save_json_file(output_file, analytics)
512676

513677
print(f"Analytics written to {output_file}")
514678
print(f"Expectancy: ${analytics['expectancy']}")

.github/scripts/generate_books_index.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
"""
77

88
import os
9-
import json
109
from pathlib import Path
1110
from datetime import datetime
11+
from globals_utils import save_json_file
1212

1313

1414
def extract_book_title(filename):
@@ -94,8 +94,7 @@ def main():
9494

9595
# Write JSON index
9696
output_file = "index.directory/books-index.json"
97-
with open(output_file, "w", encoding="utf-8") as f:
98-
json.dump(output, f, indent=2, ensure_ascii=False)
97+
save_json_file(output_file, output)
9998

10099
print(f"Books index written to {output_file}")
101100
print(f"Total books: {output['total_count']}")

0 commit comments

Comments
 (0)