refactor(smart_executor): split monolithic executor into clean service modules
- Extract 7 focused services from smart_executor.py: - trade_common: constants, timezone, logging, dry-run state - file_utils: file locking + atomic JSON helpers - smart_executor_parser: argparse + legacy argument compatibility - execution_state: decision deduplication (executions.json) - portfolio_service: positions.json + exchange reconciliation - exchange_service: ccxt wrapper, balances, order prep - trade_execution: buy/sell/rebalance/hold actions - Turn smart_executor.py into a thin backward-compatible facade - Fix critical dry-run bug: module-level DRY_RUN copy caused real orders in dry-run mode; replace with mutable dict + is_dry_run() function - Fix dry-run polluting positions.json: skip save_positions() when dry-run - Fix rebalance dry-run budget: use sell_order cost instead of real balance - Add full legacy CLI compatibility for old --decision HOLD --dry-run style
This commit is contained in:
39
src/coinhunter/services/execution_state.py
Normal file
39
src/coinhunter/services/execution_state.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""Execution state helpers (decision deduplication, executions.json)."""
|
||||
import hashlib
|
||||
|
||||
from ..runtime import get_runtime_paths
|
||||
from .file_utils import load_json_locked, save_json_locked
|
||||
from .trade_common import bj_now_iso
|
||||
|
||||
PATHS = get_runtime_paths()
|
||||
EXECUTIONS_FILE = PATHS.executions_file
|
||||
EXECUTIONS_LOCK = PATHS.executions_lock
|
||||
|
||||
|
||||
def default_decision_id(action: str, argv_tail: list[str]) -> str:
|
||||
from datetime import datetime
|
||||
from .trade_common import CST
|
||||
|
||||
now = datetime.now(CST)
|
||||
bucket_min = (now.minute // 15) * 15
|
||||
bucket = now.strftime(f"%Y%m%dT%H{bucket_min:02d}")
|
||||
raw = f"{bucket}|{action}|{'|'.join(argv_tail)}"
|
||||
return hashlib.sha1(raw.encode()).hexdigest()[:16]
|
||||
|
||||
|
||||
def load_executions() -> dict:
|
||||
return load_json_locked(EXECUTIONS_FILE, EXECUTIONS_LOCK, {"executions": {}}).get("executions", {})
|
||||
|
||||
|
||||
def save_executions(executions: dict):
|
||||
save_json_locked(EXECUTIONS_FILE, EXECUTIONS_LOCK, {"executions": executions})
|
||||
|
||||
|
||||
def record_execution_state(decision_id: str, payload: dict):
|
||||
executions = load_executions()
|
||||
executions[decision_id] = payload
|
||||
save_executions(executions)
|
||||
|
||||
|
||||
def get_execution_state(decision_id: str):
|
||||
return load_executions().get(decision_id)
|
||||
Reference in New Issue
Block a user