fix(rotation): 回测导出JSON序列化NaN/Inf清洗
- simple_rotation.py: 新增 _sanitize_json() 递归替换 NaN/Inf 为 None, 确保 json.dump 生成合法 JSON(避免前端解析失败) - .env: 注释掉群2钉钉配置(暂不使用)
This commit is contained in:
4
.env
4
.env
@@ -8,8 +8,8 @@ DINGTALK_WEBHOOK_1=https://oapi.dingtalk.com/robot/send?access_token=fb70c1561d8
|
|||||||
DINGTALK_SECRET_1=SEC1ae7cd2f1a6f9da3611af37da3e7d954c1e8533fc073c6c8cc5e5af3b6e5926b
|
DINGTALK_SECRET_1=SEC1ae7cd2f1a6f9da3611af37da3e7d954c1e8533fc073c6c8cc5e5af3b6e5926b
|
||||||
|
|
||||||
# 钉钉机器人配置 - 群2
|
# 钉钉机器人配置 - 群2
|
||||||
DINGTALK_WEBHOOK_2=https://oapi.dingtalk.com/robot/send?access_token=87c7abfcdd69b699c32da4e4f5981cd2ca6b0445474fc6ffb36f2ed0f6262fbb
|
# DINGTALK_WEBHOOK_2=https://oapi.dingtalk.com/robot/send?access_token=87c7abfcdd69b699c32da4e4f5981cd2ca6b0445474fc6ffb36f2ed0f6262fbb
|
||||||
DINGTALK_SECRET_2=SECf3d6b43f2f8a87ab91feffd052e71ec314fbf57a1842e483fe07af3c0a0e5aa6
|
# DINGTALK_SECRET_2=SECf3d6b43f2f8a87ab91feffd052e71ec314fbf57a1842e483fe07af3c0a0e5aa6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,22 @@ def _http_get(url: str, params: dict = None, timeout: int = 120) -> requests.Res
|
|||||||
return _session.get(url, params=params, timeout=timeout)
|
return _session.get(url, params=params, timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize_json(obj):
|
||||||
|
"""Recursively replace NaN/Inf with None in-place so json.dump produces valid JSON"""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
for k, v in obj.items():
|
||||||
|
if isinstance(v, float) and (math.isnan(v) or math.isinf(v)):
|
||||||
|
obj[k] = None
|
||||||
|
elif isinstance(v, (dict, list)):
|
||||||
|
_sanitize_json(v)
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
for i, v in enumerate(obj):
|
||||||
|
if isinstance(v, float) and (math.isnan(v) or math.isinf(v)):
|
||||||
|
obj[i] = None
|
||||||
|
elif isinstance(v, (dict, list)):
|
||||||
|
_sanitize_json(v)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Pure functions: momentum
|
# Pure functions: momentum
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -888,6 +904,7 @@ class SimpleRotationStrategy:
|
|||||||
},
|
},
|
||||||
'days': days_out,
|
'days': days_out,
|
||||||
}
|
}
|
||||||
|
_sanitize_json(detail)
|
||||||
with open(detail_path, 'w', encoding='utf-8') as f:
|
with open(detail_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump(detail, f, ensure_ascii=False, indent=2)
|
json.dump(detail, f, ensure_ascii=False, indent=2)
|
||||||
print(f" + Detail: {detail_path} ({len(days_out)} days)")
|
print(f" + Detail: {detail_path} ({len(days_out)} days)")
|
||||||
@@ -895,6 +912,7 @@ class SimpleRotationStrategy:
|
|||||||
# Metrics JSON
|
# Metrics JSON
|
||||||
metrics = self._compute_metrics(sum(1 for r in self.daily_records if r['is_rebalance']))
|
metrics = self._compute_metrics(sum(1 for r in self.daily_records if r['is_rebalance']))
|
||||||
metrics_path = output_dir / 'simple_rotation_metrics.json'
|
metrics_path = output_dir / 'simple_rotation_metrics.json'
|
||||||
|
_sanitize_json(metrics)
|
||||||
with open(metrics_path, 'w', encoding='utf-8') as f:
|
with open(metrics_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump(metrics, f, ensure_ascii=False, indent=2)
|
json.dump(metrics, f, ensure_ascii=False, indent=2)
|
||||||
print(f" + Metrics: {metrics_path}")
|
print(f" + Metrics: {metrics_path}")
|
||||||
|
|||||||
Reference in New Issue
Block a user