Move settings hints into rules; add OO amplitude filter toggle default off.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -322,6 +322,19 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di
|
||||
"oo_amplitude_hours": _pick(
|
||||
"oo_amplitude_hours", float(settings.oo_amplitude_hours)
|
||||
),
|
||||
"oo_amplitude_filter_enabled": (
|
||||
str(
|
||||
st.get("oo_amplitude_filter_enabled")
|
||||
if "oo_amplitude_filter_enabled" in st
|
||||
else db.get_setting(
|
||||
"oo_amplitude_filter_enabled",
|
||||
str(settings.oo_amplitude_filter_enabled),
|
||||
)
|
||||
)
|
||||
.strip()
|
||||
.lower()
|
||||
in ("1", "true", "yes", "on")
|
||||
),
|
||||
"oo_min_option_hours": _pick(
|
||||
"oo_min_option_hours", float(settings.oo_min_option_hours)
|
||||
),
|
||||
|
||||
@@ -67,6 +67,7 @@ KEYS = (
|
||||
"hedge_mode",
|
||||
"oo_amplitude_pct",
|
||||
"oo_amplitude_hours",
|
||||
"oo_amplitude_filter_enabled",
|
||||
"oo_min_option_hours",
|
||||
"oo_min_leverage",
|
||||
"oo_reward_ratio",
|
||||
@@ -123,6 +124,7 @@ class StrategySettingsBody(BaseModel):
|
||||
)
|
||||
oo_amplitude_pct: float | None = Field(default=None, ge=0.1, le=50)
|
||||
oo_amplitude_hours: float | None = Field(default=None, ge=1, le=168)
|
||||
oo_amplitude_filter_enabled: bool | None = None
|
||||
oo_min_option_hours: float | None = Field(default=None, ge=1, le=720)
|
||||
oo_min_leverage: float | None = Field(default=None, ge=1, le=10000)
|
||||
oo_reward_ratio: float | None = Field(default=None, ge=0.5, le=20)
|
||||
@@ -365,6 +367,13 @@ def _read_settings() -> dict:
|
||||
db.get_setting("oo_amplitude_hours", str(s.oo_amplitude_hours))
|
||||
or s.oo_amplitude_hours
|
||||
),
|
||||
"oo_amplitude_filter_enabled": _as_bool(
|
||||
db.get_setting(
|
||||
"oo_amplitude_filter_enabled",
|
||||
str(s.oo_amplitude_filter_enabled),
|
||||
),
|
||||
s.oo_amplitude_filter_enabled,
|
||||
),
|
||||
"oo_min_option_hours": float(
|
||||
db.get_setting("oo_min_option_hours", str(s.oo_min_option_hours))
|
||||
or s.oo_min_option_hours
|
||||
@@ -476,6 +485,7 @@ async def put_strategy_settings(
|
||||
"hedge_mode",
|
||||
"oo_amplitude_pct",
|
||||
"oo_amplitude_hours",
|
||||
"oo_amplitude_filter_enabled",
|
||||
"oo_min_option_hours",
|
||||
"oo_min_leverage",
|
||||
"oo_reward_ratio",
|
||||
|
||||
@@ -82,8 +82,9 @@ class Settings(BaseSettings):
|
||||
martingale_max_doubles: int = 3 # 最多翻倍次数(如 2→4→8→16 为 3 次)
|
||||
# 对冲模式:perp_option=永期(默认)| option_option=期期
|
||||
hedge_mode: str = "perp_option"
|
||||
oo_amplitude_pct: float = 1.5 # 振幅最大 %(回看窗内高低;超过则不开)
|
||||
oo_amplitude_hours: float = 12.0 # 振幅回看小时
|
||||
oo_amplitude_pct: float = 1.5 # 振幅最大 %(过滤开启时:超过则不开)
|
||||
oo_amplitude_hours: float = 12.0 # 振幅回看小时(选约高低点;过滤关也用)
|
||||
oo_amplitude_filter_enabled: bool = False # 振幅过滤开关(默认关)
|
||||
oo_min_option_hours: float = 24.0 # 期期:最短剩余到期小时
|
||||
oo_min_leverage: float = 200.0 # 期期:单腿最低杠杆
|
||||
oo_reward_ratio: float = 2.0 # 盈亏比:出场目标 = 预算 × 比
|
||||
|
||||
@@ -279,6 +279,16 @@ class StrategyEngine:
|
||||
)
|
||||
or s.oo_amplitude_hours
|
||||
),
|
||||
"oo_amplitude_filter_enabled": str(
|
||||
self.ledger.get_setting_str(
|
||||
"oo_amplitude_filter_enabled",
|
||||
str(s.oo_amplitude_filter_enabled),
|
||||
)
|
||||
or s.oo_amplitude_filter_enabled
|
||||
)
|
||||
.strip()
|
||||
.lower()
|
||||
in ("1", "true", "yes", "on"),
|
||||
"oo_min_option_hours": float(
|
||||
self.ledger.get_setting_float(
|
||||
"oo_min_option_hours", s.oo_min_option_hours
|
||||
|
||||
@@ -144,12 +144,13 @@ def build_oo_pick_core(
|
||||
skip_expiry_ymds: set[str] | None = None,
|
||||
now: datetime | None = None,
|
||||
max_dev_pct: float = 1.0,
|
||||
amplitude_filter_enabled: bool = True,
|
||||
) -> OoPickCore | None:
|
||||
"""完整期期选约:振幅门 + 虚值双腿(贴高低≤max_dev%) + 杠杆。"""
|
||||
"""完整期期选约:振幅门(可关)+ 虚值双腿(贴高低≤max_dev%) + 杠杆。"""
|
||||
amp = amplitude or fetch_amplitude_hl_for_runtime(amplitude_hours)
|
||||
if amp is None:
|
||||
return None
|
||||
if float(amp.range_pct) > float(amplitude_pct) + 1e-12:
|
||||
if amplitude_filter_enabled and float(amp.range_pct) > float(amplitude_pct) + 1e-12:
|
||||
return None
|
||||
if spot <= 0:
|
||||
spot = float(amp.mid)
|
||||
|
||||
@@ -192,13 +192,24 @@ def _hedge_mode() -> str:
|
||||
return "perp_option"
|
||||
|
||||
|
||||
def _oo_settings() -> tuple[float, float, float, float, float]:
|
||||
"""amplitude_pct, amplitude_hours, min_option_hours, min_leverage, strike_max_dev_pct"""
|
||||
def _oo_settings() -> tuple[float, float, float, float, float, bool]:
|
||||
"""amplitude_pct, amplitude_hours, min_option_hours, min_leverage, strike_max_dev_pct, amp_filter_on"""
|
||||
s = get_settings()
|
||||
try:
|
||||
from ..models.db import get_db
|
||||
|
||||
db = get_db()
|
||||
filt_raw = db.get_setting(
|
||||
"oo_amplitude_filter_enabled", str(s.oo_amplitude_filter_enabled)
|
||||
)
|
||||
filt_on = str(filt_raw or "").strip().lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
"on",
|
||||
)
|
||||
if filt_raw is None or filt_raw == "":
|
||||
filt_on = bool(s.oo_amplitude_filter_enabled)
|
||||
return (
|
||||
float(db.get_setting("oo_amplitude_pct", str(s.oo_amplitude_pct)) or s.oo_amplitude_pct),
|
||||
float(
|
||||
@@ -216,6 +227,7 @@ def _oo_settings() -> tuple[float, float, float, float, float]:
|
||||
)
|
||||
or s.oo_strike_max_dev_pct
|
||||
),
|
||||
filt_on,
|
||||
)
|
||||
except Exception:
|
||||
return (
|
||||
@@ -224,6 +236,7 @@ def _oo_settings() -> tuple[float, float, float, float, float]:
|
||||
s.oo_min_option_hours,
|
||||
s.oo_min_leverage,
|
||||
s.oo_strike_max_dev_pct,
|
||||
bool(s.oo_amplitude_filter_enabled),
|
||||
)
|
||||
|
||||
|
||||
@@ -457,7 +470,7 @@ class StrategySession:
|
||||
try:
|
||||
from ..exchange.candles import fetch_amplitude_hl_for_runtime
|
||||
|
||||
amp_pct, amp_hours, _, _, _ = _oo_settings()
|
||||
amp_pct, amp_hours, _, _, _, amp_filt = _oo_settings()
|
||||
amp = fetch_amplitude_hl_for_runtime(amp_hours)
|
||||
if amp is None:
|
||||
return self._oo_amp
|
||||
@@ -468,7 +481,12 @@ class StrategySession:
|
||||
"range_pct": float(amp.range_pct),
|
||||
"hours": float(amp_hours),
|
||||
"max_pct": float(amp_pct),
|
||||
"ok": float(amp.range_pct) <= float(amp_pct) + 1e-12,
|
||||
"filter_enabled": bool(amp_filt),
|
||||
"ok": (
|
||||
True
|
||||
if not amp_filt
|
||||
else float(amp.range_pct) <= float(amp_pct) + 1e-12
|
||||
),
|
||||
}
|
||||
return self._oo_amp
|
||||
except Exception:
|
||||
@@ -484,7 +502,7 @@ class StrategySession:
|
||||
if _has_open_position():
|
||||
return self.align_to_held_position()
|
||||
s = self.settings
|
||||
amp_pct, amp_hours, min_hours, _min_lev, max_dev = _oo_settings()
|
||||
amp_pct, amp_hours, min_hours, _min_lev, max_dev, _amp_filt = _oo_settings()
|
||||
idx = self.ex.fetch_index(s.index_inst_id)
|
||||
mark = self.ex.fetch_mark(s.perp_inst_id) or idx
|
||||
if mark is None or mark <= 0:
|
||||
@@ -535,7 +553,7 @@ class StrategySession:
|
||||
from .selection import _complete_by_expiry, option_leverage
|
||||
|
||||
s = self.settings
|
||||
amp_pct, amp_hours, min_hours, min_lev, max_dev = _oo_settings()
|
||||
amp_pct, amp_hours, min_hours, min_lev, max_dev, amp_filt = _oo_settings()
|
||||
idx = self.ex.fetch_index(s.index_inst_id)
|
||||
mark = self.ex.fetch_mark(s.perp_inst_id) or idx
|
||||
if mark is None or mark <= 0:
|
||||
@@ -553,9 +571,14 @@ class StrategySession:
|
||||
"range_pct": float(amp.range_pct),
|
||||
"hours": float(amp_hours),
|
||||
"max_pct": float(amp_pct),
|
||||
"ok": float(amp.range_pct) <= float(amp_pct) + 1e-12,
|
||||
"filter_enabled": bool(amp_filt),
|
||||
"ok": (
|
||||
True
|
||||
if not amp_filt
|
||||
else float(amp.range_pct) <= float(amp_pct) + 1e-12
|
||||
),
|
||||
}
|
||||
if float(amp.range_pct) > float(amp_pct) + 1e-12:
|
||||
if amp_filt and float(amp.range_pct) > float(amp_pct) + 1e-12:
|
||||
logger.info(
|
||||
"oo: amplitude %.3f%% > max %.3f%% (H=%.2f L=%.2f)",
|
||||
amp.range_pct,
|
||||
@@ -852,7 +875,7 @@ class StrategySession:
|
||||
def oo_needs_realign(self) -> bool:
|
||||
if self._pair is None:
|
||||
return True
|
||||
_amp_pct, amp_hours, min_hours, _, max_dev = _oo_settings()
|
||||
_amp_pct, amp_hours, min_hours, _, max_dev, amp_filt = _oo_settings()
|
||||
if (
|
||||
hours_until_expiry(self._pair.expiry_ymd, expiry_ms=self._pair.expiry_ms)
|
||||
+ 1e-9
|
||||
@@ -879,7 +902,12 @@ class StrategySession:
|
||||
"range_pct": float(amp.range_pct),
|
||||
"hours": float(amp_hours),
|
||||
"max_pct": float(_amp_pct),
|
||||
"ok": float(amp.range_pct) <= float(_amp_pct) + 1e-12,
|
||||
"filter_enabled": bool(amp_filt),
|
||||
"ok": (
|
||||
True
|
||||
if not amp_filt
|
||||
else float(amp.range_pct) <= float(_amp_pct) + 1e-12
|
||||
),
|
||||
}
|
||||
contracts = self.ex.list_option_contracts(self.settings.option_inst_family)
|
||||
picked = select_oo_pair(
|
||||
|
||||
@@ -175,3 +175,18 @@ def test_amplitude_max_gate() -> None:
|
||||
assert ok is not None
|
||||
assert ok.call.strike == 2025.0
|
||||
assert ok.put.strike == 1975.0
|
||||
# 过滤关闭:振幅 3% 超过 1.5% 上限也可过
|
||||
ok_off = build_oo_pick_core(
|
||||
contracts=contracts,
|
||||
spot=2000,
|
||||
call_ask=5,
|
||||
put_ask=5,
|
||||
min_hours=1,
|
||||
min_leverage=1,
|
||||
amplitude_hours=12,
|
||||
amplitude_pct=1.5,
|
||||
amplitude=amp,
|
||||
max_dev_pct=1.0,
|
||||
amplitude_filter_enabled=False,
|
||||
)
|
||||
assert ok_off is not None
|
||||
|
||||
Reference in New Issue
Block a user