Enable options by default and allow full sim trading without OKX API keys.
Relax hedge start gates in sim, route perp open/close through sim broker, and mount strategy templates for records pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -108,7 +108,7 @@ OKX_SHOW_PERP_FUNDS=true
|
|||||||
# 期权模块(与上方 OKX_API_* 同源;修改启用开关后须重启 PM2)
|
# 期权模块(与上方 OKX_API_* 同源;修改启用开关后须重启 PM2)
|
||||||
# 详见 docs/期权方案.md 与 docs/期权用法.md
|
# 详见 docs/期权方案.md 与 docs/期权用法.md
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
OKX_OPTIONS_ENABLED=false
|
OKX_OPTIONS_ENABLED=true
|
||||||
# 以下 OKX_OPTIONS_API_* 已废弃,请勿再配置(仅兼容旧部署回填)
|
# 以下 OKX_OPTIONS_API_* 已废弃,请勿再配置(仅兼容旧部署回填)
|
||||||
# OKX_OPTIONS_API_KEY=
|
# OKX_OPTIONS_API_KEY=
|
||||||
# OKX_OPTIONS_API_SECRET=
|
# OKX_OPTIONS_API_SECRET=
|
||||||
|
|||||||
@@ -305,8 +305,21 @@ def resolve_path(path_value):
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.getenv("FLASK_SECRET_KEY", "coinox_2026_secret_key")
|
app.secret_key = os.getenv("FLASK_SECRET_KEY", "coinox_2026_secret_key")
|
||||||
from lib.instance.instance_embed_lib import attach_embed_templates, redirect_to_embed_shell_if_enabled
|
from lib.instance.instance_embed_lib import attach_embed_templates, redirect_to_embed_shell_if_enabled
|
||||||
|
from jinja2 import ChoiceLoader, FileSystemLoader
|
||||||
|
from lib.paths import strategy_templates_dir
|
||||||
|
|
||||||
attach_embed_templates(app, _REPO_ROOT)
|
attach_embed_templates(app, _REPO_ROOT)
|
||||||
|
# 复盘页引用 strategy 模板字段;CoinOX 不装策略交易,仍挂载模板目录
|
||||||
|
_strategy_tpl = strategy_templates_dir(_REPO_ROOT)
|
||||||
|
if os.path.isdir(_strategy_tpl):
|
||||||
|
_existing = app.jinja_loader
|
||||||
|
_loaders = [FileSystemLoader(_strategy_tpl)]
|
||||||
|
if _existing is not None:
|
||||||
|
if isinstance(_existing, ChoiceLoader):
|
||||||
|
_loaders = list(_existing.loaders) + _loaders
|
||||||
|
else:
|
||||||
|
_loaders.insert(0, _existing)
|
||||||
|
app.jinja_loader = ChoiceLoader(_loaders)
|
||||||
|
|
||||||
# ====================== 登录配置 ======================
|
# ====================== 登录配置 ======================
|
||||||
USERNAME = os.getenv("APP_USERNAME", "dekun")
|
USERNAME = os.getenv("APP_USERNAME", "dekun")
|
||||||
@@ -368,7 +381,8 @@ _promote_legacy_options_api_keys()
|
|||||||
OKX_API_KEY = normalize_api_credential(os.getenv("OKX_API_KEY"))
|
OKX_API_KEY = normalize_api_credential(os.getenv("OKX_API_KEY"))
|
||||||
OKX_API_SECRET = normalize_api_credential(os.getenv("OKX_API_SECRET"))
|
OKX_API_SECRET = normalize_api_credential(os.getenv("OKX_API_SECRET"))
|
||||||
OKX_API_PASSPHRASE = normalize_api_credential(os.getenv("OKX_API_PASSPHRASE"))
|
OKX_API_PASSPHRASE = normalize_api_credential(os.getenv("OKX_API_PASSPHRASE"))
|
||||||
OKX_OPTIONS_ENABLED = os.getenv("OKX_OPTIONS_ENABLED", "false").lower() in ("1", "true", "yes", "on")
|
# CoinOX 默认开启期权;模拟盘可不配 OKX API
|
||||||
|
OKX_OPTIONS_ENABLED = os.getenv("OKX_OPTIONS_ENABLED", "true").lower() in ("1", "true", "yes", "on")
|
||||||
OKX_OPTIONS_TRADE_BUDGET_USDC = float(os.getenv("OKX_OPTIONS_TRADE_BUDGET_USDC", "10"))
|
OKX_OPTIONS_TRADE_BUDGET_USDC = float(os.getenv("OKX_OPTIONS_TRADE_BUDGET_USDC", "10"))
|
||||||
OKX_OPTIONS_DEFAULT_UNDERLY = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper()
|
OKX_OPTIONS_DEFAULT_UNDERLY = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper()
|
||||||
OKX_TD_MODE = os.getenv("OKX_TD_MODE", "cross")
|
OKX_TD_MODE = os.getenv("OKX_TD_MODE", "cross")
|
||||||
@@ -9911,11 +9925,12 @@ install_hedge_plan(app, _REPO_ROOT, app_module=sys.modules[__name__])
|
|||||||
try:
|
try:
|
||||||
from lib.sim.hooks import patch_options_cfg
|
from lib.sim.hooks import patch_options_cfg
|
||||||
|
|
||||||
_opt_cfg = app.extensions.get("options_cfg")
|
for _cfg_key in ("options_cfg", "hedge_plan_cfg"):
|
||||||
if isinstance(_opt_cfg, dict):
|
_cfg = app.extensions.get(_cfg_key)
|
||||||
patch_options_cfg(_opt_cfg)
|
if isinstance(_cfg, dict):
|
||||||
|
patch_options_cfg(_cfg)
|
||||||
except Exception as _sim_patch_err:
|
except Exception as _sim_patch_err:
|
||||||
print(f"[sim] patch options_cfg: {_sim_patch_err}", flush=True)
|
print(f"[sim] patch options/hedge cfg: {_sim_patch_err}", flush=True)
|
||||||
|
|
||||||
_purge_key_monitors_if_full_margin()
|
_purge_key_monitors_if_full_margin()
|
||||||
|
|
||||||
|
|||||||
@@ -716,6 +716,7 @@ def gate_status(
|
|||||||
show_options_options: bool = True,
|
show_options_options: bool = True,
|
||||||
mutual_exclusive: bool = True,
|
mutual_exclusive: bool = True,
|
||||||
has_standalone_option: bool = False,
|
has_standalone_option: bool = False,
|
||||||
|
sim_mode: bool = False,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
from lib.trade.position_sizing_lib import is_full_margin_mode
|
from lib.trade.position_sizing_lib import is_full_margin_mode
|
||||||
|
|
||||||
@@ -739,7 +740,8 @@ def gate_status(
|
|||||||
can_preview = False
|
can_preview = False
|
||||||
can_start = False
|
can_start = False
|
||||||
reasons.append("期期对冲已隐藏(HEDGE_PLAN_SHOW_OPTIONS_OPTIONS)")
|
reasons.append("期期对冲已隐藏(HEDGE_PLAN_SHOW_OPTIONS_OPTIONS)")
|
||||||
if not live_order:
|
# 模拟盘用本地钱包撮合,不要求实盘开关 / API
|
||||||
|
if not sim_mode and not live_order:
|
||||||
can_start = False
|
can_start = False
|
||||||
reasons.append("未允许对冲真实下单(HEDGE_PLAN_LIVE_ORDER)")
|
reasons.append("未允许对冲真实下单(HEDGE_PLAN_LIVE_ORDER)")
|
||||||
if active_count >= max(1, int(max_active or 1)):
|
if active_count >= max(1, int(max_active or 1)):
|
||||||
@@ -749,10 +751,10 @@ def gate_status(
|
|||||||
can_start = False
|
can_start = False
|
||||||
reasons.append("存在单独期权持仓,禁止启动对冲计划(互斥门控)")
|
reasons.append("存在单独期权持仓,禁止启动对冲计划(互斥门控)")
|
||||||
if pt == "perp_options":
|
if pt == "perp_options":
|
||||||
if not full:
|
if not full and not sim_mode:
|
||||||
can_start = False
|
can_start = False
|
||||||
reasons.append("永期开仓仅全仓模式可用(当前可测算)")
|
reasons.append("永期开仓仅全仓模式可用(当前可测算)")
|
||||||
if not live_trading:
|
if not sim_mode and not live_trading:
|
||||||
can_start = False
|
can_start = False
|
||||||
reasons.append("未开启实盘(LIVE_TRADING_ENABLED)")
|
reasons.append("未开启实盘(LIVE_TRADING_ENABLED)")
|
||||||
elif pt == "options_options":
|
elif pt == "options_options":
|
||||||
@@ -770,6 +772,7 @@ def gate_status(
|
|||||||
"plan_type": pt,
|
"plan_type": pt,
|
||||||
"live_order": live_order,
|
"live_order": live_order,
|
||||||
"live_trading": live_trading,
|
"live_trading": live_trading,
|
||||||
|
"sim_mode": bool(sim_mode),
|
||||||
"active_count": active_count,
|
"active_count": active_count,
|
||||||
"max_active": max_active,
|
"max_active": max_active,
|
||||||
"show_perp_options": bool(show_perp_options),
|
"show_perp_options": bool(show_perp_options),
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
|||||||
"alt_leverage": int(getattr(app_module, "ALT_LEVERAGE", 5) or 5),
|
"alt_leverage": int(getattr(app_module, "ALT_LEVERAGE", 5) or 5),
|
||||||
"full_margin_buffer": float(getattr(app_module, "FULL_MARGIN_BUFFER_RATIO", 0.98) or 0.98),
|
"full_margin_buffer": float(getattr(app_module, "FULL_MARGIN_BUFFER_RATIO", 0.98) or 0.98),
|
||||||
"funds_decimals": int(getattr(app_module, "FUNDS_DECIMALS", 2) or 2),
|
"funds_decimals": int(getattr(app_module, "FUNDS_DECIMALS", 2) or 2),
|
||||||
"options_enabled": _env_bool("OKX_OPTIONS_ENABLED", False),
|
"options_enabled": _env_bool("OKX_OPTIONS_ENABLED", True),
|
||||||
"default_underly": (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper(),
|
"default_underly": (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper(),
|
||||||
"chain_max_dte": float(os.getenv("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS") or os.getenv("OKX_OPTIONS_MAX_DTE_DAYS") or "14"),
|
"chain_max_dte": float(os.getenv("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS") or os.getenv("OKX_OPTIONS_MAX_DTE_DAYS") or "14"),
|
||||||
"perp_account_label": (os.getenv("OKX_ACCOUNT_LABEL") or "合约账户").strip(),
|
"perp_account_label": (os.getenv("OKX_ACCOUNT_LABEL") or "合约账户").strip(),
|
||||||
@@ -195,6 +195,15 @@ def _gates_dict(cfg: dict[str, Any], plan_type: str) -> dict[str, Any]:
|
|||||||
# fail-closed:探测失败视为不可开仓
|
# fail-closed:探测失败视为不可开仓
|
||||||
active = 10**9
|
active = 10**9
|
||||||
has_standalone = True
|
has_standalone = True
|
||||||
|
sim_mode = False
|
||||||
|
try:
|
||||||
|
from lib.sim.mode_lib import is_sim_mode
|
||||||
|
|
||||||
|
get_db = cfg.get("get_db")
|
||||||
|
if callable(get_db):
|
||||||
|
sim_mode = bool(is_sim_mode(get_db))
|
||||||
|
except Exception:
|
||||||
|
sim_mode = False
|
||||||
return gate_status(
|
return gate_status(
|
||||||
hedge_enabled=_hedge_enabled(),
|
hedge_enabled=_hedge_enabled(),
|
||||||
sizing_mode=load_position_sizing_mode(),
|
sizing_mode=load_position_sizing_mode(),
|
||||||
@@ -208,6 +217,7 @@ def _gates_dict(cfg: dict[str, Any], plan_type: str) -> dict[str, Any]:
|
|||||||
show_options_options=_show_options_options(),
|
show_options_options=_show_options_options(),
|
||||||
mutual_exclusive=mutual,
|
mutual_exclusive=mutual,
|
||||||
has_standalone_option=has_standalone,
|
has_standalone_option=has_standalone,
|
||||||
|
sim_mode=sim_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ def build_instance_settings_view(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (exchange_key or "").strip().lower() == "okx" and _env_bool("OKX_OPTIONS_ENABLED", False):
|
if (exchange_key or "").strip().lower() == "okx" and _env_bool("OKX_OPTIONS_ENABLED", True):
|
||||||
api_key = (os.getenv("OKX_API_KEY") or "").strip()
|
api_key = (os.getenv("OKX_API_KEY") or "").strip()
|
||||||
sections.append(
|
sections.append(
|
||||||
{
|
{
|
||||||
@@ -194,7 +194,7 @@ def build_instance_settings_view(
|
|||||||
"data_export_version": int(data_export_version),
|
"data_export_version": int(data_export_version),
|
||||||
"show_transfer": (exchange_key or "").strip().lower() in ("gate", "binance", "okx"),
|
"show_transfer": (exchange_key or "").strip().lower() in ("gate", "binance", "okx"),
|
||||||
"options_settings_enabled": (exchange_key or "").strip().lower() == "okx"
|
"options_settings_enabled": (exchange_key or "").strip().lower() == "okx"
|
||||||
and _env_bool("OKX_OPTIONS_ENABLED", False),
|
and _env_bool("OKX_OPTIONS_ENABLED", True),
|
||||||
"auto_transfer_enabled": auto_transfer_on,
|
"auto_transfer_enabled": auto_transfer_on,
|
||||||
"auto_transfer_bj_hour": _env_int("AUTO_TRANSFER_BJ_HOUR", 8),
|
"auto_transfer_bj_hour": _env_int("AUTO_TRANSFER_BJ_HOUR", 8),
|
||||||
"auto_transfer_amount": _env_float("AUTO_TRANSFER_AMOUNT", 30),
|
"auto_transfer_amount": _env_float("AUTO_TRANSFER_AMOUNT", 30),
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ def attach_options_templates(app: Flask, repo_root: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def install_options_trading(app: Flask, repo_root: str, app_module: Any) -> None:
|
def install_options_trading(app: Flask, repo_root: str, app_module: Any) -> None:
|
||||||
enabled = _env_bool("OKX_OPTIONS_ENABLED", False)
|
enabled = _env_bool("OKX_OPTIONS_ENABLED", True)
|
||||||
attach_options_templates(app, repo_root)
|
attach_options_templates(app, repo_root)
|
||||||
cfg = _build_cfg(app_module)
|
cfg = _build_cfg(app_module)
|
||||||
app.extensions["options_cfg"] = cfg
|
app.extensions["options_cfg"] = cfg
|
||||||
@@ -95,7 +95,7 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"enabled": _env_bool("OKX_OPTIONS_ENABLED", False),
|
"enabled": _env_bool("OKX_OPTIONS_ENABLED", True),
|
||||||
"get_db": app_module.get_db,
|
"get_db": app_module.get_db,
|
||||||
"login_required": app_module.login_required,
|
"login_required": app_module.login_required,
|
||||||
"exchange_options": getattr(app_module, "exchange_options", None),
|
"exchange_options": getattr(app_module, "exchange_options", None),
|
||||||
|
|||||||
@@ -74,6 +74,69 @@ def apply_sim_hooks(app_module: Any) -> None:
|
|||||||
app_module.get_available_trading_usdt = get_available_trading_usdt
|
app_module.get_available_trading_usdt = get_available_trading_usdt
|
||||||
app_module.get_live_position_contracts = get_live_position_contracts
|
app_module.get_live_position_contracts = get_live_position_contracts
|
||||||
|
|
||||||
|
_orig_place = getattr(app_module, "place_exchange_order", None)
|
||||||
|
_orig_close = getattr(app_module, "close_exchange_order", None)
|
||||||
|
if callable(_orig_place):
|
||||||
|
|
||||||
|
def place_exchange_order(
|
||||||
|
exchange_symbol,
|
||||||
|
direction,
|
||||||
|
amount,
|
||||||
|
leverage,
|
||||||
|
stop_loss=None,
|
||||||
|
take_profit=None,
|
||||||
|
):
|
||||||
|
if is_sim_mode(app_module.get_db):
|
||||||
|
ex = getattr(app_module, "exchange", None)
|
||||||
|
if ex is None:
|
||||||
|
raise RuntimeError("sim: 无公开行情 exchange")
|
||||||
|
ensure = getattr(app_module, "ensure_markets_loaded", None)
|
||||||
|
if callable(ensure):
|
||||||
|
try:
|
||||||
|
ensure()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return broker().place_perp_market(
|
||||||
|
ex,
|
||||||
|
symbol=exchange_symbol,
|
||||||
|
direction=direction,
|
||||||
|
contracts=float(amount),
|
||||||
|
leverage=int(leverage or 1),
|
||||||
|
stop_loss=stop_loss,
|
||||||
|
take_profit=take_profit,
|
||||||
|
)
|
||||||
|
return _orig_place(
|
||||||
|
exchange_symbol,
|
||||||
|
direction,
|
||||||
|
amount,
|
||||||
|
leverage,
|
||||||
|
stop_loss=stop_loss,
|
||||||
|
take_profit=take_profit,
|
||||||
|
)
|
||||||
|
|
||||||
|
app_module.place_exchange_order = place_exchange_order
|
||||||
|
|
||||||
|
if callable(_orig_close):
|
||||||
|
|
||||||
|
def close_exchange_order(order_row):
|
||||||
|
if is_sim_mode(app_module.get_db):
|
||||||
|
ex = getattr(app_module, "exchange", None)
|
||||||
|
if ex is None:
|
||||||
|
raise RuntimeError("sim: 无公开行情 exchange")
|
||||||
|
row = order_row or {}
|
||||||
|
symbol = row.get("exchange_symbol") or row.get("symbol")
|
||||||
|
direction = row.get("direction")
|
||||||
|
contracts = float(row.get("order_amount") or 0)
|
||||||
|
return broker().close_perp_market(
|
||||||
|
ex,
|
||||||
|
symbol=str(symbol or ""),
|
||||||
|
direction=str(direction or ""),
|
||||||
|
contracts=contracts if contracts > 0 else None,
|
||||||
|
)
|
||||||
|
return _orig_close(order_row)
|
||||||
|
|
||||||
|
app_module.close_exchange_order = close_exchange_order
|
||||||
|
|
||||||
_orig_xfer = getattr(app_module, "execute_transfer_usdt", None)
|
_orig_xfer = getattr(app_module, "execute_transfer_usdt", None)
|
||||||
if callable(_orig_xfer):
|
if callable(_orig_xfer):
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ def main() -> int:
|
|||||||
updates["OKX_OPTIONS_MARGIN_MODE"] = "coin"
|
updates["OKX_OPTIONS_MARGIN_MODE"] = "coin"
|
||||||
if not (env_get(lines, "EXCHANGE_DISPLAY_NAME") or "").strip():
|
if not (env_get(lines, "EXCHANGE_DISPLAY_NAME") or "").strip():
|
||||||
updates["EXCHANGE_DISPLAY_NAME"] = "CoinOX"
|
updates["EXCHANGE_DISPLAY_NAME"] = "CoinOX"
|
||||||
|
if not (env_get(lines, "OKX_OPTIONS_ENABLED") or "").strip():
|
||||||
|
updates["OKX_OPTIONS_ENABLED"] = "true"
|
||||||
live = (env_get(lines, "LIVE_TRADING_ENABLED") or "").strip().lower()
|
live = (env_get(lines, "LIVE_TRADING_ENABLED") or "").strip().lower()
|
||||||
if live in ("",):
|
if live in ("",):
|
||||||
updates["LIVE_TRADING_ENABLED"] = "false"
|
updates["LIVE_TRADING_ENABLED"] = "false"
|
||||||
|
|||||||
Reference in New Issue
Block a user