Files
openclaw/agents/stock/workspace/sim/variants.py
T
hyowons 69ef9c09e8 auto: 일일 백업 2026-06-09 02:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 02:00:01 +09:00

110 lines
3.4 KiB
Python

"""병렬 페이퍼 변이(variant) 관리 — 각 변이는 {이름, 파라미터} + 자체 가상계좌.
정의: state/sim/variants.json = [{"id":"v1","name":"...","params":{KEY:VAL,...}}]
계좌: state/sim/variants/<id>/{portfolio.json, trades.jsonl}
비교: state/sim/variants_compare.json (웹 비교 탭)
"""
from __future__ import annotations
import json
from . import config
VARIANTS_PATH = config.STATE_DIR / 'variants.json'
VARIANTS_DIR = config.STATE_DIR / 'variants'
COMPARE_PATH = config.STATE_DIR / 'variants_compare.json'
def load_variants() -> list[dict]:
try:
return json.loads(VARIANTS_PATH.read_text())
except Exception:
return []
def save_variants(variants: list[dict]):
config.STATE_DIR.mkdir(parents=True, exist_ok=True)
VARIANTS_PATH.write_text(json.dumps(variants, ensure_ascii=False, indent=2))
def variant_paths(vid: str):
d = VARIANTS_DIR / vid
return d / 'portfolio.json', d / 'trades.jsonl'
def _label(params: dict) -> str:
short = {'RR_RATIO': 'RR', 'STOP_ATR_MULT': 'S', 'SMA_LONG': 'SMA',
'PULLBACK_ATR_MULT': 'P', 'VOLUME_BREAKOUT_MULT': 'V',
'TURNOVER_OVERHEAT_MULT': 'OH'}
return '·'.join(f'{short.get(k, k)}{v:g}' for k, v in params.items())
def seed_from_sweep(n: int = 3) -> list[dict]:
"""backtest_results.json 검증 상위 n개 → 변이로 등록 (기존 계좌는 유지)."""
try:
res = json.loads((config.STATE_DIR / 'backtest_results.json').read_text())
except Exception:
return []
existing = {v['id']: v for v in load_variants()}
variants = list(existing.values())
seen_params = {json.dumps(v['params'], sort_keys=True) for v in variants}
rank = 0
for r in res.get('results', []):
if rank >= n:
break
params = r['params']
key = json.dumps(params, sort_keys=True)
if key in seen_params:
continue
rank += 1
vid = f'v{len(variants)+1}'
variants.append({'id': vid, 'name': f'#{rank} {_label(params)}', 'params': params})
seen_params.add(key)
save_variants(variants)
return variants
def add_variant(params: dict) -> dict | None:
"""단일 params 조합을 변이로 추가 (백테스트 행 → 비교군). 이미 있으면 None."""
variants = load_variants()
seen = {json.dumps(v['params'], sort_keys=True) for v in variants}
if json.dumps(params, sort_keys=True) in seen:
return None
nums = [int(v['id'][1:]) for v in variants if v['id'][1:].isdigit()]
vid = f'v{(max(nums) + 1) if nums else 1}'
v = {'id': vid, 'name': _label(params), 'params': params}
variants.append(v)
save_variants(variants)
return v
def remove_variant(vid: str):
"""변이 1개 삭제 (정의 + 계좌)."""
import shutil
save_variants([v for v in load_variants() if v['id'] != vid])
d = VARIANTS_DIR / vid
if d.exists():
shutil.rmtree(d)
def reset_variant(vid: str):
"""변이 1개 성적만 초기화 (정의 유지 — 계좌 디렉터리 삭제 후 다음 스캔에 재생성)."""
import shutil
d = VARIANTS_DIR / vid
if d.exists():
shutil.rmtree(d)
def reset_all():
"""모든 변이 계좌 초기화 (variants.json 정의는 유지)."""
import shutil
if VARIANTS_DIR.exists():
shutil.rmtree(VARIANTS_DIR)
def clear():
"""변이 정의·계좌 전부 삭제."""
reset_all()
if VARIANTS_PATH.exists():
VARIANTS_PATH.unlink()