fix(paper): ETH 5m allineato al tick + hardening GUI/compose
Bug principale: in scripts/run_paper_trading.py il fetch usava
end = now.replace(minute=0,...), troncando sempre all'ora. ETH è
dichiarato timeframe=5m (commit 23b7273) ma di fatto veniva
valutato 1 volta ogni 60 min — 502 poll del run 39e027df hanno
prodotto solo 43 evaluazioni/asset, tutte a HH:00. Il commento
in load_assets segnala esplicitamente che a 1h la strategia
perde -33% su 7y: regressione vs backtest.
Fix: helper _align_end_to_timeframe(now, timeframe) snappa end
al boundary nativo dell'asset. Mappa 1m/5m/15m/30m/1h/4h/1d.
Test regression in src/strategy_crypto/tests con 9 casi.
Hardening accessorio incluso nello stesso commit:
- docker-compose.yml: state/ in RW per strategy-crypto-gui
(SQLite WAL richiede SHM writable anche da reader).
- multi_swarm_core/dashboard/nicegui_app.py: ui.timer ora
deactivate on_disconnect su 3 pagine (index/convergence/genomes)
per evitare leak di timer dopo client disconnect.
- strategy_crypto/frontend/data.py: retry 5s su sqlite.connect
per cold-start race quando GUI parte prima del paper writer.
- state/validation-hardened-001.json: output WFA tooling
multi-fold del run phase1-hardened-001.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -88,8 +88,8 @@ services:
|
|||||||
<<: *swarm-env
|
<<: *swarm-env
|
||||||
DASHBOARD_ROOT_PATH: /strategy_crypto_gui
|
DASHBOARD_ROOT_PATH: /strategy_crypto_gui
|
||||||
volumes:
|
volumes:
|
||||||
# Dashboard legge solo strategy_crypto.db: state/ in read-only (WAL: vedi nota)
|
# RW richiesto: SQLite WAL mode richiede write-access dal reader per SHM.
|
||||||
- ./state:/app/state:ro
|
- ./state:/app/state
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- python
|
- python
|
||||||
- -m
|
- -m
|
||||||
|
|||||||
@@ -33,6 +33,36 @@ from strategy_crypto.backend import PaperExecutor, PaperRepository, Portfolio
|
|||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# Mapping timeframe stringa Cerbero -> minuti del bar. Le strategie tradano
|
||||||
|
# sul "bar appena chiuso", quindi end deve essere snappato al boundary del
|
||||||
|
# loro timeframe (NON sempre al top dell'ora) per evitare la regressione in
|
||||||
|
# cui ETH 5m veniva valutato una volta sola ogni 60 min.
|
||||||
|
_TIMEFRAME_MINUTES: dict[str, int] = {
|
||||||
|
"1m": 1,
|
||||||
|
"5m": 5,
|
||||||
|
"15m": 15,
|
||||||
|
"30m": 30,
|
||||||
|
"1h": 60,
|
||||||
|
"4h": 240,
|
||||||
|
"1d": 1440,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _align_end_to_timeframe(now: datetime, timeframe: str) -> datetime:
|
||||||
|
"""Snap ``now`` al boundary del bar timeframe (UTC, naive seconds).
|
||||||
|
|
||||||
|
Es.: now=14:37:42, tf="5m" -> 14:35:00
|
||||||
|
now=14:37:42, tf="1h" -> 14:00:00
|
||||||
|
now=14:00:00, tf="1h" -> 14:00:00
|
||||||
|
"""
|
||||||
|
bar_min = _TIMEFRAME_MINUTES[timeframe]
|
||||||
|
aligned = now.replace(second=0, microsecond=0)
|
||||||
|
if bar_min >= 1440:
|
||||||
|
return aligned.replace(hour=0, minute=0)
|
||||||
|
total_min = aligned.hour * 60 + aligned.minute
|
||||||
|
snapped = (total_min // bar_min) * bar_min
|
||||||
|
return aligned.replace(hour=snapped // 60, minute=snapped % 60)
|
||||||
|
|
||||||
|
|
||||||
def _default_strategies_dir() -> Path:
|
def _default_strategies_dir() -> Path:
|
||||||
"""Cartella JSON shippata col package strategy_crypto."""
|
"""Cartella JSON shippata col package strategy_crypto."""
|
||||||
@@ -131,9 +161,12 @@ def main() -> None:
|
|||||||
now = datetime.now(UTC)
|
now = datetime.now(UTC)
|
||||||
last_prices: dict[str, float] = {}
|
last_prices: dict[str, float] = {}
|
||||||
for asset, executor in zip(assets, executors, strict=True):
|
for asset, executor in zip(assets, executors, strict=True):
|
||||||
# fetch OHLCV most recent lookback bars
|
# fetch OHLCV most recent lookback bars: end snappato al timeframe
|
||||||
end = now.replace(minute=0, second=0, microsecond=0)
|
# dell'asset, non sempre all'ora (altrimenti ETH 5m veniva valutato
|
||||||
start = end - timedelta(hours=args.lookback_bars + 1)
|
# solo ogni 60 min, regressione vs backtest tunato 5m).
|
||||||
|
bar_min = _TIMEFRAME_MINUTES[asset.timeframe]
|
||||||
|
end = _align_end_to_timeframe(now, asset.timeframe)
|
||||||
|
start = end - timedelta(minutes=bar_min * (args.lookback_bars + 1))
|
||||||
req = OHLCVRequest(
|
req = OHLCVRequest(
|
||||||
symbol=asset.symbol,
|
symbol=asset.symbol,
|
||||||
timeframe=asset.timeframe,
|
timeframe=asset.timeframe,
|
||||||
|
|||||||
@@ -263,7 +263,8 @@ def index() -> None:
|
|||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
select.on_value_change(on_select_change)
|
select.on_value_change(on_select_change)
|
||||||
ui.timer(REFRESH_INTERVAL_S, refresh)
|
_timer = ui.timer(REFRESH_INTERVAL_S, refresh)
|
||||||
|
ui.context.client.on_disconnect(_timer.deactivate)
|
||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
|
|
||||||
@@ -353,7 +354,8 @@ def convergence() -> None:
|
|||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
select.on_value_change(on_select_change)
|
select.on_value_change(on_select_change)
|
||||||
ui.timer(REFRESH_INTERVAL_S, refresh)
|
_timer = ui.timer(REFRESH_INTERVAL_S, refresh)
|
||||||
|
ui.context.client.on_disconnect(_timer.deactivate)
|
||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
|
|
||||||
@@ -535,7 +537,8 @@ def genomes() -> None:
|
|||||||
select.on_value_change(on_select_change)
|
select.on_value_change(on_select_change)
|
||||||
top_k_select.on_value_change(lambda _: refresh())
|
top_k_select.on_value_change(lambda _: refresh())
|
||||||
top_table.on("selection", on_row_selected)
|
top_table.on("selection", on_row_selected)
|
||||||
ui.timer(REFRESH_INTERVAL_S, refresh)
|
_timer = ui.timer(REFRESH_INTERVAL_S, refresh)
|
||||||
|
ui.context.client.on_disconnect(_timer.deactivate)
|
||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -14,9 +15,18 @@ import pandas as pd # type: ignore[import-untyped]
|
|||||||
|
|
||||||
|
|
||||||
def _paper_conn(db_path: str | Path) -> sqlite3.Connection:
|
def _paper_conn(db_path: str | Path) -> sqlite3.Connection:
|
||||||
conn = sqlite3.connect(str(db_path))
|
# Cold-start race: GUI può avviarsi prima che il paper writer crei il file.
|
||||||
|
db_path_str = str(db_path)
|
||||||
|
deadline = time.monotonic() + 5.0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(db_path_str, timeout=5.0)
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
return conn
|
return conn
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
if time.monotonic() >= deadline:
|
||||||
|
raise
|
||||||
|
time.sleep(1.0)
|
||||||
|
|
||||||
|
|
||||||
def paper_runs_df(db_path: str | Path) -> pd.DataFrame:
|
def paper_runs_df(db_path: str | Path) -> pd.DataFrame:
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
"""Regression guard: end-of-window snap deve seguire il timeframe dell'asset.
|
||||||
|
|
||||||
|
Bug originale (scripts/run_paper_trading.py): ``end = now.replace(minute=0,...)``
|
||||||
|
snappava sempre all'ora; ETH 5m veniva quindi valutato 1 volta ogni 60 min
|
||||||
|
invece di ogni 5 min, riducendo la fedelta' al backtest tunato 5m.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import sys
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
_REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
_RUNNER_PATH = _REPO_ROOT / "scripts" / "run_paper_trading.py"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_runner_module():
|
||||||
|
spec = importlib.util.spec_from_file_location("run_paper_trading", _RUNNER_PATH)
|
||||||
|
assert spec is not None and spec.loader is not None
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules["run_paper_trading"] = module
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def runner():
|
||||||
|
return _load_runner_module()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"now, tf, expected",
|
||||||
|
[
|
||||||
|
# 5m: snap al boundary di 5 min, NON all'ora
|
||||||
|
(datetime(2026, 5, 18, 14, 37, 42, tzinfo=UTC), "5m", datetime(2026, 5, 18, 14, 35, tzinfo=UTC)),
|
||||||
|
(datetime(2026, 5, 18, 14, 35, 0, tzinfo=UTC), "5m", datetime(2026, 5, 18, 14, 35, tzinfo=UTC)),
|
||||||
|
(datetime(2026, 5, 18, 14, 34, 59, tzinfo=UTC), "5m", datetime(2026, 5, 18, 14, 30, tzinfo=UTC)),
|
||||||
|
# 1h: comportamento storico preservato
|
||||||
|
(datetime(2026, 5, 18, 14, 37, 42, tzinfo=UTC), "1h", datetime(2026, 5, 18, 14, 0, tzinfo=UTC)),
|
||||||
|
(datetime(2026, 5, 18, 14, 0, 0, tzinfo=UTC), "1h", datetime(2026, 5, 18, 14, 0, tzinfo=UTC)),
|
||||||
|
# 15m / 4h
|
||||||
|
(datetime(2026, 5, 18, 14, 22, 0, tzinfo=UTC), "15m", datetime(2026, 5, 18, 14, 15, tzinfo=UTC)),
|
||||||
|
(datetime(2026, 5, 18, 14, 22, 0, tzinfo=UTC), "4h", datetime(2026, 5, 18, 12, 0, tzinfo=UTC)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_align_end_to_timeframe(runner, now, tf, expected) -> None:
|
||||||
|
assert runner._align_end_to_timeframe(now, tf) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_align_end_5m_advances_every_5_minutes(runner) -> None:
|
||||||
|
"""Bug-regression: chiamate consecutive a 5 min di distanza devono
|
||||||
|
produrre end DIVERSI per tf=5m (prima del fix erano identici)."""
|
||||||
|
a = datetime(2026, 5, 18, 14, 30, 0, tzinfo=UTC)
|
||||||
|
b = datetime(2026, 5, 18, 14, 35, 0, tzinfo=UTC)
|
||||||
|
c = datetime(2026, 5, 18, 14, 40, 0, tzinfo=UTC)
|
||||||
|
ends = {runner._align_end_to_timeframe(t, "5m") for t in (a, b, c)}
|
||||||
|
assert len(ends) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_align_end_1h_stable_within_hour(runner) -> None:
|
||||||
|
"""Per tf=1h, chiamate dentro la stessa ora devono dare lo stesso end."""
|
||||||
|
ends = {
|
||||||
|
runner._align_end_to_timeframe(datetime(2026, 5, 18, 14, m, 0, tzinfo=UTC), "1h")
|
||||||
|
for m in (0, 15, 30, 45, 59)
|
||||||
|
}
|
||||||
|
assert ends == {datetime(2026, 5, 18, 14, 0, tzinfo=UTC)}
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
{
|
||||||
|
"run_id": "7f65bd1832d94c638b588aab02fb223e",
|
||||||
|
"run_name": "phase1-hardened-001",
|
||||||
|
"n_folds": 4,
|
||||||
|
"top_k_requested": 5,
|
||||||
|
"top_k_evaluated": 5,
|
||||||
|
"symbol": "BTC-PERPETUAL",
|
||||||
|
"timeframe": "1h",
|
||||||
|
"start": "2018-09-01T00:00:00+00:00",
|
||||||
|
"end": "2026-01-01T00:00:00+00:00",
|
||||||
|
"ohlcv_bars": 64297,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"genome_id": "9cf506b83bec55f6",
|
||||||
|
"fitness_is": 0.2847465321576384,
|
||||||
|
"sharpe_is": 0.6809931251900289,
|
||||||
|
"folds": [
|
||||||
|
{
|
||||||
|
"fold": 0,
|
||||||
|
"fitness": 0.4454407113532186,
|
||||||
|
"sharpe": 0.940612398713799,
|
||||||
|
"dsr": 0.09856838950479485,
|
||||||
|
"dsr_pvalue": 0.9014316104952051,
|
||||||
|
"return": 0.12691347502077277,
|
||||||
|
"max_dd": 0.08467873586477132,
|
||||||
|
"n_trades": 50,
|
||||||
|
"test_start": "2022-05-02 12:00:00+00:00",
|
||||||
|
"test_end": "2023-04-02 08:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 1,
|
||||||
|
"fitness": 0.3348901947844796,
|
||||||
|
"sharpe": 0.6215567075501345,
|
||||||
|
"dsr": 0.05615871498414856,
|
||||||
|
"dsr_pvalue": 0.9438412850158514,
|
||||||
|
"return": 0.1669550204052912,
|
||||||
|
"max_dd": 0.2425926649805225,
|
||||||
|
"n_trades": 60,
|
||||||
|
"test_start": "2023-04-02 09:00:00+00:00",
|
||||||
|
"test_end": "2024-03-02 05:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 2,
|
||||||
|
"fitness": 0.08496628060413243,
|
||||||
|
"sharpe": -0.291593157960215,
|
||||||
|
"dsr": 0.006828013272159182,
|
||||||
|
"dsr_pvalue": 0.9931719867278408,
|
||||||
|
"return": -0.06496567446731383,
|
||||||
|
"max_dd": 0.1933746053658072,
|
||||||
|
"n_trades": 72,
|
||||||
|
"test_start": "2024-03-02 06:00:00+00:00",
|
||||||
|
"test_end": "2025-01-31 02:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 3,
|
||||||
|
"fitness": 0.10547685784422405,
|
||||||
|
"sharpe": -0.04385303190774091,
|
||||||
|
"dsr": 0.013045759744378084,
|
||||||
|
"dsr_pvalue": 0.986954240255622,
|
||||||
|
"return": -0.003908970230222186,
|
||||||
|
"max_dd": 0.05825300658307936,
|
||||||
|
"n_trades": 31,
|
||||||
|
"test_start": "2025-01-31 03:00:00+00:00",
|
||||||
|
"test_end": "2025-12-31 23:00:00+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fitness_oos_mean": 0.24269351114651366,
|
||||||
|
"fitness_oos_min": 0.08496628060413243,
|
||||||
|
"fitness_oos_max": 0.4454407113532186,
|
||||||
|
"fitness_oos_std": 0.15273583802070415,
|
||||||
|
"sharpe_oos_mean": 0.3066807290989944,
|
||||||
|
"sharpe_oos_min": -0.291593157960215,
|
||||||
|
"robust_score": 0.08496628060413243
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"genome_id": "9f1f7ffa40b4900a",
|
||||||
|
"fitness_is": 0.2653851546757454,
|
||||||
|
"sharpe_is": 0.7183647852569522,
|
||||||
|
"folds": [
|
||||||
|
{
|
||||||
|
"fold": 0,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2022-05-02 12:00:00+00:00",
|
||||||
|
"test_end": "2023-04-02 08:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 1,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2023-04-02 09:00:00+00:00",
|
||||||
|
"test_end": "2024-03-02 05:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 2,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2024-03-02 06:00:00+00:00",
|
||||||
|
"test_end": "2025-01-31 02:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 3,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2025-01-31 03:00:00+00:00",
|
||||||
|
"test_end": "2025-12-31 23:00:00+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fitness_oos_mean": 0.0,
|
||||||
|
"fitness_oos_min": 0.0,
|
||||||
|
"fitness_oos_max": 0.0,
|
||||||
|
"fitness_oos_std": 0.0,
|
||||||
|
"sharpe_oos_mean": 0.0,
|
||||||
|
"sharpe_oos_min": 0.0,
|
||||||
|
"robust_score": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"genome_id": "aff702f86c30e30f",
|
||||||
|
"fitness_is": 0.22070023288031862,
|
||||||
|
"sharpe_is": 0.49220575419744833,
|
||||||
|
"folds": [
|
||||||
|
{
|
||||||
|
"fold": 0,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2022-05-02 12:00:00+00:00",
|
||||||
|
"test_end": "2023-04-02 08:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 1,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": -1.3160615555061568,
|
||||||
|
"dsr": 2.0480110912755528e-14,
|
||||||
|
"dsr_pvalue": 0.9999999999999796,
|
||||||
|
"return": -0.004197667464114874,
|
||||||
|
"max_dd": 0.004197667464114874,
|
||||||
|
"n_trades": 1,
|
||||||
|
"test_start": "2023-04-02 09:00:00+00:00",
|
||||||
|
"test_end": "2024-03-02 05:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 2,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2024-03-02 06:00:00+00:00",
|
||||||
|
"test_end": "2025-01-31 02:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 3,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2025-01-31 03:00:00+00:00",
|
||||||
|
"test_end": "2025-12-31 23:00:00+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fitness_oos_mean": 0.0,
|
||||||
|
"fitness_oos_min": 0.0,
|
||||||
|
"fitness_oos_max": 0.0,
|
||||||
|
"fitness_oos_std": 0.0,
|
||||||
|
"sharpe_oos_mean": -0.3290153888765392,
|
||||||
|
"sharpe_oos_min": -1.3160615555061568,
|
||||||
|
"robust_score": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"genome_id": "43236904205071d5",
|
||||||
|
"fitness_is": 0.22070023288031862,
|
||||||
|
"sharpe_is": 0.49220575419744833,
|
||||||
|
"folds": [
|
||||||
|
{
|
||||||
|
"fold": 0,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2022-05-02 12:00:00+00:00",
|
||||||
|
"test_end": "2023-04-02 08:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 1,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": -1.3160615555061568,
|
||||||
|
"dsr": 2.0480110912755528e-14,
|
||||||
|
"dsr_pvalue": 0.9999999999999796,
|
||||||
|
"return": -0.004197667464114874,
|
||||||
|
"max_dd": 0.004197667464114874,
|
||||||
|
"n_trades": 1,
|
||||||
|
"test_start": "2023-04-02 09:00:00+00:00",
|
||||||
|
"test_end": "2024-03-02 05:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 2,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2024-03-02 06:00:00+00:00",
|
||||||
|
"test_end": "2025-01-31 02:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 3,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 0.0,
|
||||||
|
"dsr": 0.0,
|
||||||
|
"dsr_pvalue": 1.0,
|
||||||
|
"return": 0.0,
|
||||||
|
"max_dd": 0.0,
|
||||||
|
"n_trades": 0,
|
||||||
|
"test_start": "2025-01-31 03:00:00+00:00",
|
||||||
|
"test_end": "2025-12-31 23:00:00+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fitness_oos_mean": 0.0,
|
||||||
|
"fitness_oos_min": 0.0,
|
||||||
|
"fitness_oos_max": 0.0,
|
||||||
|
"fitness_oos_std": 0.0,
|
||||||
|
"sharpe_oos_mean": -0.3290153888765392,
|
||||||
|
"sharpe_oos_min": -1.3160615555061568,
|
||||||
|
"robust_score": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"genome_id": "78a445d3043e0771",
|
||||||
|
"fitness_is": 0.20950868586433583,
|
||||||
|
"sharpe_is": 0.2940379509455447,
|
||||||
|
"folds": [
|
||||||
|
{
|
||||||
|
"fold": 0,
|
||||||
|
"fitness": 0.07182313189843528,
|
||||||
|
"sharpe": -0.5971649832941454,
|
||||||
|
"dsr": 0.0029614586254508167,
|
||||||
|
"dsr_pvalue": 0.9970385413745492,
|
||||||
|
"return": -0.10657245144370586,
|
||||||
|
"max_dd": 0.17076725093180228,
|
||||||
|
"n_trades": 14,
|
||||||
|
"test_start": "2022-05-02 12:00:00+00:00",
|
||||||
|
"test_end": "2023-04-02 08:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 1,
|
||||||
|
"fitness": 0.04914620786914511,
|
||||||
|
"sharpe": -0.7350195766937807,
|
||||||
|
"dsr": 0.0007433210570883239,
|
||||||
|
"dsr_pvalue": 0.9992566789429117,
|
||||||
|
"return": -0.03472119511680272,
|
||||||
|
"max_dd": 0.060780295013844174,
|
||||||
|
"n_trades": 20,
|
||||||
|
"test_start": "2023-04-02 09:00:00+00:00",
|
||||||
|
"test_end": "2024-03-02 05:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 2,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": 1.2071345821102195,
|
||||||
|
"dsr": 0.14855640183813817,
|
||||||
|
"dsr_pvalue": 0.8514435981618618,
|
||||||
|
"return": 0.12296871148346566,
|
||||||
|
"max_dd": 0.05065940776643054,
|
||||||
|
"n_trades": 9,
|
||||||
|
"test_start": "2024-03-02 06:00:00+00:00",
|
||||||
|
"test_end": "2025-01-31 02:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fold": 3,
|
||||||
|
"fitness": 0.0,
|
||||||
|
"sharpe": -1.6874561123211371,
|
||||||
|
"dsr": 9.510326955509528e-06,
|
||||||
|
"dsr_pvalue": 0.9999904896730445,
|
||||||
|
"return": -0.03197955364502458,
|
||||||
|
"max_dd": 0.032239270519921044,
|
||||||
|
"n_trades": 7,
|
||||||
|
"test_start": "2025-01-31 03:00:00+00:00",
|
||||||
|
"test_end": "2025-12-31 23:00:00+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fitness_oos_mean": 0.0302423349418951,
|
||||||
|
"fitness_oos_min": 0.0,
|
||||||
|
"fitness_oos_max": 0.07182313189843528,
|
||||||
|
"fitness_oos_std": 0.03128704497338385,
|
||||||
|
"sharpe_oos_mean": -0.4531265225497109,
|
||||||
|
"sharpe_oos_min": -1.6874561123211371,
|
||||||
|
"robust_score": 0.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user