Add fixed-direction switch for perp/option open side.

When enabled, lock perp long→buy Put or short→buy Call with ITM/ATM only; off keeps ATM/ask rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-29 19:18:12 +08:00
parent 55109e82d3
commit 0ee1d8be5d
16 changed files with 427 additions and 46 deletions
+25
View File
@@ -234,6 +234,8 @@ export type PlanState = {
min_option_leverage: number;
atm_open_offset_enabled?: boolean;
max_atm_open_offset?: number;
fixed_direction_enabled?: boolean;
fixed_perp_side?: "long" | "short";
can_open: boolean;
open_capacity?: {
leverage?: number;
@@ -299,6 +301,29 @@ export type PlanState = {
live_ready_reason?: string;
};
export type StrategySettings = {
fee_rate: number;
exit_mode: "fixed_usdt" | "premium_multiple";
net_profit_target: number;
premium_exit_multiple: number;
rest_seconds: number;
live_order_interval_sec?: number;
skip_weekends?: boolean;
initial_equity?: number;
leverage?: number;
min_option_hours?: number;
min_option_leverage?: number;
atm_open_offset_enabled?: boolean;
max_atm_open_offset?: number;
fixed_direction_enabled?: boolean;
fixed_perp_side?: "long" | "short";
close_bid_mark_max_pct?: number;
perp_qty_eth?: number;
option_qty_eth?: number;
show_manual_trade_buttons?: boolean;
exchange?: string;
};
export type RuntimeSettings = {
mode: "SIM" | "LIVE";
exchange: string;
+20 -8
View File
@@ -130,9 +130,13 @@ export default function PlanPage() {
const bias = snap?.ask_compare?.bias;
const biasTag =
bias === "strike_below_spot" || bias === "call_ask_gt_put" ? (
bias === "strike_below_spot" ||
bias === "call_ask_gt_put" ||
bias === "fixed_short_call" ? (
<span className="tag up"> Call + </span>
) : bias === "strike_above_spot" || bias === "put_ask_gt_call" ? (
) : bias === "strike_above_spot" ||
bias === "put_ask_gt_call" ||
bias === "fixed_long_put" ? (
<span className="tag down"> Put + </span>
) : (
<span className="tag"> / </span>
@@ -149,9 +153,13 @@ export default function PlanPage() {
? `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}`
: `固定 ${fmt(plan?.net_profit_target ?? 15)} U`;
const phaseLabel = PHASE_ZH[plan?.phase || ""] || plan?.phase || "—";
const atmRule = plan?.atm_open_offset_enabled
? `ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}`
: "ATM偏差关";
const atmRule = plan?.fixed_direction_enabled
? plan?.fixed_perp_side === "short"
? "固定:永续空+买Call(实/平)"
: "固定:永续多+买Put(实/平)"
: plan?.atm_open_offset_enabled
? `ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}`
: "ATM偏差关";
const exitDetail =
exitMode === "premium_multiple"
? `净盈利≥初始权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}`
@@ -337,9 +345,13 @@ export default function PlanPage() {
<span className="mono">
{fmt(plan?.min_option_hours, 0)}h ·
{fmt(plan?.min_option_leverage, 0)}x
{plan?.atm_open_offset_enabled
? ` · ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}`
: " · ATM偏差关"}
{plan?.fixed_direction_enabled
? plan?.fixed_perp_side === "short"
? " · 固定空+Call(实/平)"
: " · 固定多+Put(实/平)"
: plan?.atm_open_offset_enabled
? ` · ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}`
: " · ATM偏差关"}
</span>
</div>
<div className="kv">
+43 -3
View File
@@ -74,6 +74,8 @@ export default function SettingsPage() {
const [minOptLev, setMinOptLev] = useState(100);
const [atmOffOn, setAtmOffOn] = useState(false);
const [maxAtmOff, setMaxAtmOff] = useState(3);
const [fixedDirOn, setFixedDirOn] = useState(false);
const [fixedPerpSide, setFixedPerpSide] = useState<"long" | "short">("long");
const [closeDevPct, setCloseDevPct] = useState(30);
const [perpQty, setPerpQty] = useState(1);
const [optQty, setOptQty] = useState(2);
@@ -158,6 +160,8 @@ export default function SettingsPage() {
setMinOptLev(s.min_option_leverage ?? 100);
setAtmOffOn(s.atm_open_offset_enabled === true);
setMaxAtmOff(s.max_atm_open_offset ?? 3);
setFixedDirOn(s.fixed_direction_enabled === true);
setFixedPerpSide(s.fixed_perp_side === "short" ? "short" : "long");
setCloseDevPct(s.close_bid_mark_max_pct ?? 30);
setPerpQty(s.perp_qty_eth ?? 1);
setOptQty(s.option_qty_eth ?? 2);
@@ -302,6 +306,8 @@ export default function SettingsPage() {
min_option_leverage: minOptLev,
atm_open_offset_enabled: atmOffOn,
max_atm_open_offset: maxAtmOff,
fixed_direction_enabled: fixedDirOn,
fixed_perp_side: fixedPerpSide,
close_bid_mark_max_pct: closeDevPct,
perp_qty_eth: perpQty,
option_qty_eth: optQty,
@@ -610,11 +616,41 @@ export default function SettingsPage() {
onChange={(e) => setMinOptLev(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="fixedDirOn"></label>
<select
id="fixedDirOn"
className="mono"
value={fixedDirOn ? "1" : "0"}
onChange={(e) => setFixedDirOn(e.target.value === "1")}
>
<option value="0"></option>
<option value="1"></option>
</select>
</div>
<div className="field">
<label htmlFor="fixedPerp"></label>
<select
id="fixedPerp"
className="mono"
disabled={!fixedDirOn}
value={fixedPerpSide}
onChange={(e) =>
setFixedPerpSide(
e.target.value === "short" ? "short" : "long",
)
}
>
<option value="long"> · Put/</option>
<option value="short"> · Call/</option>
</select>
</div>
<div className="field">
<label htmlFor="atmoffOn"> ATM </label>
<select
id="atmoffOn"
className="mono"
disabled={fixedDirOn}
value={atmOffOn ? "1" : "0"}
onChange={(e) => setAtmOffOn(e.target.value === "1")}
>
@@ -630,7 +666,7 @@ export default function SettingsPage() {
type="number"
step="0.5"
min="0"
disabled={!atmOffOn}
disabled={!atmOffOn || fixedDirOn}
value={maxAtmOff}
onChange={(e) => setMaxAtmOff(Number(e.target.value))}
/>
@@ -803,11 +839,15 @@ export default function SettingsPage() {
{stratSub === "select" ? (
<>
<li>
ATM |ATM
Put
Call ATM/
</li>
<li>
ATM |ATM
|
</li>
<li>
ATM
ATM
</li>
</>
) : null}