feat(cli): enter --forced-close-at 시간 기반 자동청산 옵션 (issue #17, BAT-024)
trex-core enter 에 --forced-close-at <ISO8601>(절대시간) 옵션 추가. 지정 시 buy intent payload 의 기존 forced_close_at 에 매핑(계약·도메인· forced-close 루프 REQ-037 불변, CLI 노출 갭만 보완). 잘못된 형식은 제출 전 거부, 미지정 시 미포함(하위호환). 상대시간 옵션은 미채택. REQ-032 change-req(CHG-20260609-01/02) → BAT-024(batch-lite) → Implemented. 단위 353 passed, ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -179,8 +179,19 @@ def _intent_guard(args, cfg, summary: dict, action: str, confirm) -> int | None:
|
||||
|
||||
|
||||
def _cmd_enter(args, cfg, rpc, confirm) -> int:
|
||||
# REQ-032 AC-6: validate the absolute auto-close timestamp BEFORE submitting,
|
||||
# so a malformed value is rejected at the CLI instead of failing admission.
|
||||
forced_close_at = getattr(args, "forced_close_at", None)
|
||||
if forced_close_at is not None:
|
||||
try:
|
||||
forced_close_at = datetime.fromisoformat(forced_close_at).isoformat()
|
||||
except ValueError:
|
||||
_fail(f"invalid --forced-close-at (expected ISO8601): {args.forced_close_at!r}",
|
||||
args.json)
|
||||
return 1
|
||||
summary = {"portfolio_id": args.portfolio_id, "qty": args.qty,
|
||||
"allocated_capital": args.capital, "entry_price": args.entry_price}
|
||||
"allocated_capital": args.capital, "entry_price": args.entry_price,
|
||||
"forced_close_at": forced_close_at}
|
||||
short = _intent_guard(args, cfg, summary, "enter(buy)", confirm)
|
||||
if short is not None:
|
||||
return short
|
||||
@@ -192,6 +203,9 @@ def _cmd_enter(args, cfg, rpc, confirm) -> int:
|
||||
"sell_strategy_id": args.sell_strategy_id,
|
||||
"stop_loss_price": args.stop_loss, "take_profit_price": args.take_profit,
|
||||
}
|
||||
# REQ-032 AC-5/6: only carry forced_close_at when specified (backward compat).
|
||||
if forced_close_at is not None:
|
||||
payload["forced_close_at"] = forced_close_at
|
||||
resp = rpc(topology.intent_queue(_resolve_instance(args)), payload,
|
||||
correlation_id=payload["intent_id"], timeout=args.timeout)
|
||||
return _report_intent(resp, args.json)
|
||||
@@ -274,6 +288,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
p_enter.add_argument("--strategy-id", dest="strategy_id", default=None)
|
||||
p_enter.add_argument("--stop-loss", dest="stop_loss", type=float, default=None)
|
||||
p_enter.add_argument("--take-profit", dest="take_profit", type=float, default=None)
|
||||
p_enter.add_argument(
|
||||
"--forced-close-at", dest="forced_close_at", default=None,
|
||||
help="absolute auto-close timestamp (ISO8601, e.g. 2026-06-09T15:20:00+09:00) "
|
||||
"→ buy intent forced_close_at",
|
||||
)
|
||||
_add_intent_guards(p_enter)
|
||||
_add_common(p_enter)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user