Skip to content

Commit c681fdc

Browse files
Merge pull request #181 from akfamily/dev
fix(optimize): 修复JSON序列化中时间戳和特殊数值的处理
2 parents 93a95dc + de7ac45 commit c681fdc

5 files changed

Lines changed: 59 additions & 3 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "akquant"
3-
version = "0.1.92"
3+
version = "0.1.93"
44
edition = "2024"
55
description = "High-performance quantitative trading framework based on Rust and Python"
66
license = "MIT"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "akquant"
7-
version = "0.1.92"
7+
version = "0.1.93"
88
description = "High-performance quantitative trading framework based on Rust and Python"
99
readme = "README.md"
1010
license = {text = "MIT License"}

python/akquant/optimize.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import threading
1212
import time
1313
from dataclasses import dataclass
14+
from datetime import date, datetime, timedelta
15+
from datetime import time as datetime_time
1416
from typing import Any, Dict, List, Mapping, Optional, Sequence, Type, Union, cast
1517

1618
import numpy as np
@@ -162,10 +164,26 @@ class JSONEncoder(json.JSONEncoder):
162164

163165
def default(self, obj: Any) -> Any:
164166
"""Encode object."""
167+
if obj is pd.NaT:
168+
return None
169+
if isinstance(obj, pd.Timestamp):
170+
if pd.isna(obj):
171+
return None
172+
return obj.isoformat()
173+
if isinstance(obj, pd.Timedelta):
174+
return obj.total_seconds()
175+
if isinstance(obj, (datetime, date, datetime_time)):
176+
return obj.isoformat()
177+
if isinstance(obj, timedelta):
178+
return obj.total_seconds()
165179
if isinstance(obj, (np.integer, np.int64, np.int32)):
166180
return int(obj)
167181
elif isinstance(obj, (np.floating, np.float64, np.float32)):
182+
if np.isnan(obj) or np.isinf(obj):
183+
return None
168184
return float(obj)
185+
elif isinstance(obj, np.bool_):
186+
return bool(obj)
169187
elif isinstance(obj, np.ndarray):
170188
return obj.tolist()
171189
return super().default(obj)

tests/test_engine.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import time
22
import warnings
33
from datetime import date, datetime, timezone
4+
from pathlib import Path
45
from typing import Any, cast
56

67
import akquant
@@ -3693,6 +3694,43 @@ def test_run_grid_search_single_worker_accepts_camelcase_execution_mode() -> Non
36933694
)
36943695

36953696

3697+
def test_run_grid_search_db_path_serializes_timestamp_metrics(
3698+
tmp_path: Path,
3699+
) -> None:
3700+
"""Grid search cache should serialize Timestamp metrics into JSON strings."""
3701+
import json
3702+
import sqlite3
3703+
3704+
symbol = "OPT_DB_TS_SERIALIZE"
3705+
data = _build_benchmark_data(n=40, symbol=symbol)
3706+
db_path = tmp_path / "walk_forward_cache.db"
3707+
3708+
results = akquant.run_grid_search(
3709+
strategy=NoopStrategy,
3710+
param_grid={"dummy": [1]},
3711+
data=data,
3712+
symbol=symbol,
3713+
max_workers=1,
3714+
return_df=True,
3715+
show_progress=False,
3716+
db_path=str(db_path),
3717+
)
3718+
3719+
assert isinstance(results, pd.DataFrame)
3720+
assert len(results) == 1
3721+
3722+
with sqlite3.connect(db_path) as conn:
3723+
row = conn.execute(
3724+
"SELECT metrics_json FROM optimization_results WHERE strategy_name = ?",
3725+
(NoopStrategy.__name__,),
3726+
).fetchone()
3727+
3728+
assert row is not None
3729+
metrics = json.loads(cast(str, row[0]))
3730+
assert isinstance(metrics.get("start_time"), str)
3731+
assert isinstance(metrics.get("end_time"), str)
3732+
3733+
36963734
def test_run_backtest_expiry_date_str_is_rejected() -> None:
36973735
"""expiry_date should reject string input."""
36983736

0 commit comments

Comments
 (0)