Add configurable SIM equity (default 10k) and strategy docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-25 11:43:51 +08:00
parent b50c134f5b
commit 5dcec0fde0
9 changed files with 308 additions and 6 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ OPTION_INST_FAMILY=ETH-USD_UM
INDEX_INST_ID=ETH-USD
FEE_RATE=0.0005
INITIAL_EQUITY=100000
INITIAL_EQUITY=10000
LEVERAGE=3
MIN_OPTION_HOURS=12
MIN_OPTION_LEVERAGE=100
+1
View File
@@ -10,6 +10,7 @@
- [开发方案](docs/开发方案.md)
- [代码结构](docs/代码结构.md)
- [商业化与授权方案](docs/商业化与授权方案.md)
- [策略说明](docs/策略说明.md)
## 访问(测试机)
+22 -2
View File
@@ -2,12 +2,13 @@ from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from ..config import get_settings
from ..models.db import get_db
from ..sim.ledger import Ledger
from ..sim.matcher import Matcher
from .auth import require_user
router = APIRouter(prefix="/api/settings", tags=["settings"])
@@ -38,7 +39,7 @@ class StrategySettingsBody(BaseModel):
premium_exit_multiple: float | None = Field(default=None, ge=0.1, le=100)
rest_seconds: int | None = Field(default=None, ge=0, le=3600)
skip_weekends: bool | None = None
initial_equity: float | None = Field(default=None, ge=1000)
initial_equity: float | None = Field(default=None, ge=1000, le=10_000_000)
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)
@@ -116,8 +117,27 @@ async def put_strategy_settings(
_user: Annotated[str, Depends(require_user)],
) -> dict:
db = get_db()
s = get_settings()
data = body.model_dump(exclude_none=True)
equity_to_apply: float | None = None
if "initial_equity" in data:
new_eq = float(data["initial_equity"])
old_eq = float(
db.get_setting("initial_equity", str(s.initial_equity)) or s.initial_equity
)
if abs(new_eq - old_eq) > 1e-9:
if Matcher(db).has_open_position():
raise HTTPException(
status_code=409,
detail="有未平仓,无法重置模拟资金;请先平仓后再改",
)
equity_to_apply = new_eq
for k, v in data.items():
if k in KEYS:
db.set_setting(k, str(v))
if equity_to_apply is not None:
Ledger(db).reset_equity(
equity_to_apply,
note=f"设置模拟资金={equity_to_apply:.2f}",
)
return _read_settings()
+1 -1
View File
@@ -38,7 +38,7 @@ class Settings(BaseSettings):
index_inst_id: str = "ETH-USD"
fee_rate: float = 0.0005
initial_equity: float = 100_000.0
initial_equity: float = 10_000.0 # SIM 模拟初始资金(USDT),设置页可改
max_rounds: int = 3 # 已不再强管控,仅兼容旧字段
open_hhmm: str = "16:00" # 已废弃开仓窗
stop_open_hhmm: str = "08:00" # 已废弃开仓窗
+18
View File
@@ -48,6 +48,24 @@ class Ledger:
self.db._conn.commit()
return equity
def reset_equity(self, amount: float, *, note: str = "重置模拟资金") -> float:
"""将权益与可用资金重置为 amount(reserved 清零)。须在无持仓时调用。"""
now = int(time.time() * 1000)
amt = float(amount)
if amt < 0:
raise ValueError("模拟资金不能为负")
with self.db._lock:
self.db._conn.execute(
"UPDATE ledger_meta SET equity=?, available=?, reserved=0, updated_at_ms=? WHERE id=1",
(amt, amt, now),
)
self.db._conn.execute(
"INSERT INTO ledger_entries(group_id, kind, amount, balance_after, note, ts_ms) VALUES (?,?,?,?,?,?)",
(None, "reset", amt, amt, note, now),
)
self.db._conn.commit()
return amt
def get_setting_float(self, key: str, default: float) -> float:
v = self.db.get_setting(key)
if v is None or v == "":
+1 -1
View File
@@ -186,7 +186,7 @@ OKX_API_KEY=... # 模拟阶段只需能拉行情的权限
OKX_API_SECRET=...
OKX_API_PASSPHRASE=...
FEE_RATE=0.0005 # 示例;滑点 = 1 × FEE_RATE
INITIAL_EQUITY=100000
INITIAL_EQUITY=10000
TZ=Asia/Shanghai
```
+236
View File
@@ -0,0 +1,236 @@
# ETH 永续 + 期权对冲策略说明
> 依据当前代码逻辑整理(SIM 默认真值参数)。
> 关联:[开发方案](./开发方案.md)、[商业化与授权方案](./商业化与授权方案.md)
> 更新:2026-07-25
---
## 1. 策略一句话
**ATM 期权买方** 表达方向弹性,用 **反向永续** 做对冲腿;波动大时争取多轮兑现净盈利,波动小时接受权利金磨损,**到期自动全平**。
本质是 **概率与样本**:不追求每天固定轮次,而按行情吃机会。
---
## 2. 仓位结构(默认)
| 腿 | 默认数量 | 方向规则 | 成交方式 |
|----|----------|----------|----------|
| 永续 ETH-USDT-SWAP | **1 ETH** | 见下表 | 市价(吃买一/卖一 + 滑点) |
| 期权 ETH-USD_UM | **2 ETH** 名义 | 只买不卖 | 开仓吃卖一,平仓吃买一 |
永续杠杆默认 **3×**(可配)。同时最多 **1 组**仓,禁止叠仓开下一组。
### 2.1 开仓方向(卖一比价)
比较同一 ATM 行权价的 **Call 卖一****Put 卖一**
| 条件 | 期权 | 永续 |
|------|------|------|
| Call 卖一 > Put 卖一 | 买入 Call | 做空 |
| Call 卖一 < Put 卖一 | 买入 Put | 做多 |
| 相等 | 不开仓,继续等待 | — |
直觉:卖一更高的一侧往往隐含更强的短期方向/溢价偏好,买入该侧期权;永续反向以平衡部分 Delta。
---
## 3. 开仓机制
### 3.1 流程概览
```
策略运行中
→ 无持仓且不在休息期
→ 非周末跳过(若开启)
→ 选到期:剩余时长 ≥ min_option_hours(默认 12h
→ 该到期 ATM 行权价(最接近标的)
→ Call/Put 卖一比价选方向
→ 期权杠杆 = 标的价 ÷ 卖一权利金 ≥ min_option_leverage(默认 100
→ 开永续 + 开期权(一组)
→ 锁定 initial_premium = 期权成交价 × 期权名义(不含费)
```
### 3.2 选约约束
| 参数 | 默认 | 作用 |
|------|------|------|
| `min_option_hours` | 12 | 过滤过近到期,减少刚开仓就到期 |
| `min_option_leverage` | 100 | 权利金相对标的不能太贵(现价/卖一) |
| ATM | — | 同到期、最接近指数/标记价的行权价 |
无合格合约时:状态停留等待,记录「无合格期权…」,不硬开。
### 3.3 时间与节奏
| 规则 | 说明 |
|------|------|
| 开仓时间窗 | **已取消**,全天可尝试(仍受选约约束) |
| 周六/周日 | 默认 **跳过新开仓**(上海时区,`skip_weekends`);持仓仍可平 |
| 每日轮次上限 | **不限制**;波动大可能多轮,波动小可能一轮都难 |
| 组间休息 | 全平后默认休息 **300 秒**`rest_seconds`)再开下一组 |
| 同时持仓 | 最多 1 组 |
### 3.4 费用(SIM
- 永续、期权均按可配 `fee_rate`(默认 0.0005)计费。
- 滑点按约 1 倍费率计入成交价,手续费另扣。
- 开仓现金:支付期权权利金 + 开仓手续费;永续开仓主要扣费。
---
## 4. 平仓机制
任一触发 → **该组永续 + 期权全部平掉**
### 4.1 净盈利达标(主出场)
**净盈利**口径(盯盘用):
```
净盈利 ≈ 永续浮盈 + 期权浮盈 − 预估平仓手续费
期权浮盈 = 当前买一 × 数量 − 初始权利金
```
| 出场模式 | 默认 | 触发条件 |
|----------|------|----------|
| `fixed_usdt` | 目标 **15 USDT** | 净盈利 ≥ `net_profit_target` |
| `premium_multiple` | 倍数 **1.0** | 净盈利 ≥ `initial_premium × premium_exit_multiple` |
说明:权利金倍数模式下,**1× = 赚回一倍权利金金额的净利**(例如权利金 20.41,目标约 20.41,不是 40.82)。
### 4.2 到期自动全平
- 到达期权到期时刻(OKXUTC 08:00 = **上海 16:00**)→ 原因 `expiry`
- **绕过**买一深度 / 买一相对标记偏差闸门(与紧急全平同类),尽量卖掉。
- 策略 **暂停时仍执行**,避免拖过期。
- 波动小、拖到到期:权利金亏损视为 **预算内成本**,可接受。
### 4.3 平仓流动性闸门(常规出场)
常规净盈利平仓时:
- 期权买一深度需覆盖平仓名义;
- 买一相对标记偏差默认 ≤ **30%**`close_bid_mark_max_pct`);
- 不满足 → `liquidity_wait`,继续等待,不改开仓。
**到期 / 紧急全平**:绕过上述闸门。
### 4.4 其它平仓入口
| 原因码 | 含义 |
|--------|------|
| `fixed_usdt` / `premium_multiple` | 净盈利达标 |
| `expiry` | 到期自动全平 |
| `emergency` | 界面紧急全平 |
| `manual` | 手动平仓 |
---
## 5. 策略侧重点
| 侧重点 | 说明 |
|--------|------|
| **吃波动,不赌横盘** | 买方期权需要标的走动;大波动利于达标出场 |
| **概率与轮次** | 波动日可能多轮;磨盘日可能零轮或一轮认亏 |
| **确定性时间规则** | 用周末跳过、到期全平,**不做**难以精准的横盘识别 |
| **费用后净利** | 出场看扣完预估平仓费后的净盈利,避免「账面赚、平完亏」 |
| **单组纪律** | 有仓不开下一组,防止重叠风险 |
| **权利金是成本预算** | 小波动拖到期亏权利金,属于策略设计内损耗,不是异常 bug |
适合对外表述:
> 适合有波动的时段;震荡磨盘是主要磨损源。不怕单边大波动本身,怕没波动却持续付时间价值。
---
## 6. 风险点评估
### 6.1 主要风险
| 风险 | 等级 | 说明 | 缓解(已有/建议) |
|------|------|------|------------------|
| **横盘 / 低波动** | 高 | Theta 消耗权利金;永续几乎不贡献利润 | 周末跳过;到期认亏全平;接受磨损日 |
| **方向选错** | 中高 | 卖一比价并非预测圣杯;错向时期权可能失利,永续端也可能承压 | 净盈利达标才兑现;到期强平止损时间 |
| **开平滑点与手续费** | 中 | 双边 taker + 滑点侵蚀小目标利润 | 目标默认 15U 需覆盖费用;勿把目标设过低 |
| **期权流动性** | 中 | 平仓买一薄、偏差大 → 等待 | 流动性闸门;到期/紧急可绕过 |
| **杠杆与保证金** | 中 | 永续 3×,标的急跌/急涨挤压保证金 | 控制名义、保留资金缓冲;实盘注意强平线 |
| **同到期 ATM 拥挤** | 低–中 | 临近到期价差与盘口恶化 | `min_option_hours`;到期强平 |
| **SIM≠实盘** | 高(若直接外推) | 本地撮合、深度与成交假设简化 | 小资金实盘验证后再放大 |
| **交易所限流 / 断连** | 中 | 行情中断导致延误开平 | 健康检查;部署侧监控 |
### 6.2 明确不做什么(边界)
- 不做卖方期权(不收权利金那套)。
- 不做横盘「智能识别」开仓过滤。
- 不做多组并行。
- 当前主路径为 **SIM**;实盘需另行授权与二次确认(见商业化方案)。
### 6.3 损益直觉
- **有利波动**:期权端弹性(2 ETH 名义)常快于永续(1 ETH)的对冲损耗 → 易达净利目标。
- **不利 / 无波动**:权利金贬值 + 费用 → 拖到到期全平,单组亏损量级约在 **权利金量级 + 双边费用**(再叠加永续期间浮亏/浮盈)。
---
## 7. 最小资金推荐
以下按 **默认仓位**(永续 1 ETH、期权 2 ETH 名义、杠杆 3×)估算,标的价按 **ETH ≈ 1,8002,200 USDT** 量级。
### 7.1 单组占用粗算
| 项目 | 约略金额(USDT) |
|------|------------------|
| 永续保证金(名义÷杠杆) | ≈ 1,800–2,200 ÷ 3 ≈ **600750** |
| 期权权利金(ATM、短期限,2 ETH) | 常见约 **1560**(行情波动大时更高) |
| 开平手续费 / 滑点缓冲 | **2050** |
| 逆向波动缓冲(避免过紧) | **300800**(经验缓冲,非精确 VaR |
### 7.2 建议档位
| 档位 | 建议权益(USDT) | 适用 |
|------|------------------|------|
| **绝对下限(不推荐)** | ≥ **1,500** | 仅能勉强覆盖保证金 + 小权利金,缓冲极薄,易心态与风控双紧 |
| **最小可用** | ≥ **3,000** | 默认参数下可跑 SIM / 极小实盘验证,仍偏紧 |
| **推荐起步** | ≥ **5,000** | 留出磨损日与波动缓冲,更符合「概率样本」玩法 |
| **较舒适** | ≥ **10,000** | 参数微调、连续多日磨损时更从容 |
> 缩放规则:若把永续改为 `0.5 ETH`、期权 `1 ETH`,资金需求大约可按比例减半,但费用占比会上升,小目标更难赚。
### 7.3 与出场目标的关系
- 默认净利目标 **15 USDT**:在费用后属于「小而频」目标,依赖波动日多次兑现。
- 单日/单组最坏常见情形更接近 **亏掉当期权利金 + 费用**(外加永续路径损益),资金规划应按 **多次磨损日** 而非「每组必赚 15」来留余量。
### 7.4 SIM 默认
系统默认虚拟权益 `initial_equity = 10,000` USDT(策略设置可改;保存且数值变更时在无持仓下重置账本),**不代表**实盘建议入金。
---
## 8. 关键可配参数速查
| 参数 | 默认 | 类别 |
|------|------|------|
| `perp_qty_eth` | 1 | 仓位 |
| `option_qty_eth` | 2 | 仓位 |
| `leverage` | 3 | 永续 |
| `fee_rate` | 0.0005 | 成本 |
| `exit_mode` | fixed_usdt | 出场 |
| `net_profit_target` | 15 | 出场 |
| `premium_exit_multiple` | 1.0 | 出场 |
| `rest_seconds` | 300 | 节奏 |
| `skip_weekends` | true | 时间 |
| `min_option_hours` | 12 | 选约 |
| `min_option_leverage` | 100 | 选约 |
| `close_bid_mark_max_pct` | 30 | 平仓流动性 |
---
## 9. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-07-25 | 初稿:对齐当前开平仓、周末跳过、到期全平、净盈利口径与资金建议 |
+21 -1
View File
@@ -34,6 +34,7 @@ export default function SettingsPage() {
const [closeDevPct, setCloseDevPct] = useState(30);
const [perpQty, setPerpQty] = useState(1);
const [optQty, setOptQty] = useState(2);
const [initialEquity, setInitialEquity] = useState(10000);
const [stratOk, setStratOk] = useState("");
useEffect(() => {
@@ -51,6 +52,7 @@ export default function SettingsPage() {
setCloseDevPct(s.close_bid_mark_max_pct ?? 30);
setPerpQty(s.perp_qty_eth ?? 1);
setOptQty(s.option_qty_eth ?? 2);
setInitialEquity(s.initial_equity ?? 10000);
})
.catch(() => undefined);
}, []);
@@ -106,9 +108,12 @@ export default function SettingsPage() {
close_bid_mark_max_pct: closeDevPct,
perp_qty_eth: perpQty,
option_qty_eth: optQty,
initial_equity: initialEquity,
}),
});
setStratOk("策略参数已保存");
setStratOk(
"策略参数已保存(模拟资金仅在数值变更且无持仓时重置账本)",
);
} catch (ex) {
setErr(ex instanceof Error ? ex.message : String(ex));
}
@@ -145,6 +150,21 @@ export default function SettingsPage() {
<section className="settings-section">
<h3></h3>
<div className="settings-fields">
<div className="field">
<label htmlFor="equity">USDT</label>
<input
id="equity"
className="mono"
type="number"
step="100"
min="1000"
value={initialEquity}
onChange={(e) => setInitialEquity(Number(e.target.value))}
/>
<p className="settings-hint">
10000
</p>
</div>
<div className="field">
<label htmlFor="lev"></label>
<input
+7
View File
@@ -433,6 +433,13 @@ input {
margin-bottom: 12px;
}
.settings-hint {
margin: 6px 0 0;
font-size: 12px;
color: var(--muted);
line-height: 1.4;
}
.settings-actions {
margin-top: 8px;
display: flex;