Revise strategy: TTM+ATM+leverage option pick, % exit, perp leverage/margin.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,10 +14,12 @@ router = APIRouter(prefix="/api/settings", tags=["settings"])
|
||||
|
||||
KEYS = (
|
||||
"fee_rate",
|
||||
"exit_move_points",
|
||||
"exit_move_pct",
|
||||
"rest_seconds",
|
||||
"max_rounds",
|
||||
"initial_equity",
|
||||
"leverage",
|
||||
"min_option_hours",
|
||||
"min_option_leverage",
|
||||
"perp_qty_eth",
|
||||
"option_qty_eth",
|
||||
)
|
||||
@@ -25,10 +27,12 @@ KEYS = (
|
||||
|
||||
class StrategySettingsBody(BaseModel):
|
||||
fee_rate: float | None = Field(default=None, ge=0, le=0.05)
|
||||
exit_move_points: float | None = Field(default=None, ge=1, le=500)
|
||||
exit_move_pct: float | None = Field(default=None, ge=0.1, le=50)
|
||||
rest_seconds: int | None = Field(default=None, ge=0, le=3600)
|
||||
max_rounds: int | None = Field(default=None, ge=1, le=20)
|
||||
initial_equity: float | None = Field(default=None, ge=1000)
|
||||
leverage: float | None = Field(default=None, ge=1, le=125)
|
||||
min_option_hours: float | None = Field(default=None, ge=1, le=720)
|
||||
min_option_leverage: float | None = Field(default=None, ge=1, le=10000)
|
||||
perp_qty_eth: float | None = Field(default=None, ge=0.01, le=100)
|
||||
option_qty_eth: float | None = Field(default=None, ge=0.01, le=100)
|
||||
|
||||
@@ -38,18 +42,24 @@ def _read_settings() -> dict:
|
||||
s = get_settings()
|
||||
return {
|
||||
"fee_rate": float(db.get_setting("fee_rate", str(s.fee_rate)) or s.fee_rate),
|
||||
"exit_move_points": float(
|
||||
db.get_setting("exit_move_points", str(s.exit_move_points)) or s.exit_move_points
|
||||
"exit_move_pct": float(
|
||||
db.get_setting("exit_move_pct", str(s.exit_move_pct)) or s.exit_move_pct
|
||||
),
|
||||
"rest_seconds": int(
|
||||
float(db.get_setting("rest_seconds", str(s.rest_seconds)) or s.rest_seconds)
|
||||
),
|
||||
"max_rounds": int(
|
||||
float(db.get_setting("max_rounds", str(s.max_rounds)) or s.max_rounds)
|
||||
),
|
||||
"initial_equity": float(
|
||||
db.get_setting("initial_equity", str(s.initial_equity)) or s.initial_equity
|
||||
),
|
||||
"leverage": float(db.get_setting("leverage", str(s.leverage)) or s.leverage),
|
||||
"min_option_hours": float(
|
||||
db.get_setting("min_option_hours", str(s.min_option_hours))
|
||||
or s.min_option_hours
|
||||
),
|
||||
"min_option_leverage": float(
|
||||
db.get_setting("min_option_leverage", str(s.min_option_leverage))
|
||||
or s.min_option_leverage
|
||||
),
|
||||
"perp_qty_eth": float(
|
||||
db.get_setting("perp_qty_eth", str(s.perp_qty_eth)) or s.perp_qty_eth
|
||||
),
|
||||
|
||||
+40
-24
@@ -7,10 +7,10 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from ..market import get_gateway
|
||||
from ..models.db import get_db
|
||||
from ..sim.ledger import Ledger
|
||||
from ..sim.matcher import Matcher
|
||||
from ..strategy.clock import window_key
|
||||
from ..strategy.group import next_group_id
|
||||
from ..strategy.signal import decide
|
||||
from .auth import require_user
|
||||
|
||||
router = APIRouter(prefix="/api/sim", tags=["sim"])
|
||||
@@ -38,37 +38,45 @@ async def sim_open_group(
|
||||
body: ManualOpenBody | None = None,
|
||||
) -> dict:
|
||||
gw = get_gateway()
|
||||
# 开仓前按现价强制重选 ATM,避免沿用启动时的旧行权价
|
||||
try:
|
||||
await gw.ensure_atm_async(force=True)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=503, detail=f"ATM 对齐失败: {e}") from e
|
||||
snap = gw.snapshot()
|
||||
if not snap.pair or not snap.call or not snap.put:
|
||||
raise HTTPException(status_code=503, detail="行情未就绪")
|
||||
pick = await gw.pick_for_open_async()
|
||||
if pick is None:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="无合格期权:请检查剩余时长(≥设置小时)与杠杆(现价/卖一)",
|
||||
)
|
||||
|
||||
force = (body.force_option_side if body else None) or None
|
||||
if force in ("call", "put"):
|
||||
option_side = force
|
||||
perp_side = "short" if force == "call" else "long"
|
||||
bias = "manual_" + force
|
||||
option_ask = pick.call_ask if force == "call" else pick.put_ask
|
||||
from ..config import get_settings
|
||||
from ..market.instruments import option_leverage
|
||||
from ..sim.ledger import Ledger as Led
|
||||
|
||||
s = get_settings()
|
||||
min_lev = Led().get_setting_float("min_option_leverage", s.min_option_leverage)
|
||||
lev = option_leverage(pick.underlying_px, option_ask)
|
||||
if lev is None or lev < min_lev:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=f"强制方向杠杆不足: {lev or 0:.1f} < {min_lev:.0f}",
|
||||
)
|
||||
else:
|
||||
sig = decide(snap.call.ask, snap.put.ask)
|
||||
if sig is None:
|
||||
raise HTTPException(status_code=409, detail="Call/Put 卖一相等,跳过")
|
||||
option_side = sig.option_side
|
||||
perp_side = sig.perp_side
|
||||
bias = sig.bias
|
||||
option_side = pick.option_side
|
||||
perp_side = pick.perp_side
|
||||
bias = pick.bias
|
||||
|
||||
option_inst = (
|
||||
snap.pair.call_inst_id if option_side == "call" else snap.pair.put_inst_id
|
||||
pick.pair.call_inst_id if option_side == "call" else pick.pair.put_inst_id
|
||||
)
|
||||
entry_idx = snap.index_px or (snap.perp.mark_px if snap.perp else None)
|
||||
if entry_idx is None:
|
||||
raise HTTPException(status_code=503, detail="无指数/标记价")
|
||||
|
||||
wkey = window_key()
|
||||
db = get_db()
|
||||
count = len(db.fetchall("SELECT group_id FROM groups WHERE group_id LIKE ?", (f"G-{wkey}-%",)))
|
||||
count = len(
|
||||
db.fetchall("SELECT group_id FROM groups WHERE group_id LIKE ?", (f"G-{wkey}-%",))
|
||||
)
|
||||
gid = next_group_id(count)
|
||||
r = Matcher().open_group(
|
||||
group_id=gid,
|
||||
@@ -76,13 +84,21 @@ async def sim_open_group(
|
||||
option_side=option_side,
|
||||
perp_side=perp_side,
|
||||
option_inst_id=option_inst,
|
||||
entry_index_px=float(entry_idx),
|
||||
strike=snap.pair.strike,
|
||||
expiry_ymd=snap.pair.expiry_ymd,
|
||||
entry_index_px=float(pick.underlying_px),
|
||||
strike=pick.pair.strike,
|
||||
expiry_ymd=pick.pair.expiry_ymd,
|
||||
)
|
||||
if not r.ok:
|
||||
raise HTTPException(status_code=400, detail=r.detail)
|
||||
return {"ok": True, **(r.data or {}), "detail": r.detail}
|
||||
return {
|
||||
"ok": True,
|
||||
**(r.data or {}),
|
||||
"detail": r.detail,
|
||||
"option_leverage": pick.option_leverage,
|
||||
"hours_left": pick.hours_left,
|
||||
"expiry_ymd": pick.pair.expiry_ymd,
|
||||
"strike": pick.pair.strike,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/close-group")
|
||||
|
||||
Reference in New Issue
Block a user