Harden env save against empty numerics; default coin margin and hedge off.
Reject blank float/int/select on save and parse env with safe helpers so options config edits cannot crash into 502; default OKX_TRADE_MODE=options and OKX_OPTIONS_MARGIN_MODE=coin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -258,19 +258,41 @@ if not AUTH_DISABLED and (not USERNAME or not PASSWORD):
|
||||
WECHAT_WEBHOOK = os.getenv("WECHAT_WEBHOOK", "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=replace-me")
|
||||
SYSTEM_TYPE = "CRYPTO"
|
||||
HOST = os.getenv("APP_HOST", "0.0.0.0")
|
||||
PORT = int(os.getenv("APP_PORT", "5000"))
|
||||
|
||||
|
||||
def _env_float(name: str, default: float) -> float:
|
||||
try:
|
||||
raw = os.getenv(name)
|
||||
if raw is None or str(raw).strip() == "":
|
||||
return float(default)
|
||||
return float(raw)
|
||||
except (TypeError, ValueError):
|
||||
return float(default)
|
||||
|
||||
|
||||
def _env_int(name: str, default: int) -> int:
|
||||
try:
|
||||
raw = os.getenv(name)
|
||||
if raw is None or str(raw).strip() == "":
|
||||
return int(default)
|
||||
return int(float(raw))
|
||||
except (TypeError, ValueError):
|
||||
return int(default)
|
||||
|
||||
|
||||
PORT = _env_int("APP_PORT", 5000)
|
||||
DEBUG = os.getenv("APP_DEBUG", "false").lower() == "true"
|
||||
DB_PATH = resolve_path(os.getenv("DB_PATH", "crypto.db"))
|
||||
|
||||
# 训练参数(可由 .env 覆盖)
|
||||
TOTAL_CAPITAL = float(os.getenv("TOTAL_CAPITAL", "100"))
|
||||
DAILY_START_CAPITAL = float(os.getenv("DAILY_START_CAPITAL", "30"))
|
||||
DAILY_LOSS_CAPITAL = float(os.getenv("DAILY_LOSS_CAPITAL", "20"))
|
||||
DAILY_PROFIT_CAPITAL = float(os.getenv("DAILY_PROFIT_CAPITAL", "50"))
|
||||
BTC_LEVERAGE = int(os.getenv("BTC_LEVERAGE", "10"))
|
||||
ALT_LEVERAGE = int(os.getenv("ALT_LEVERAGE", "5"))
|
||||
TOTAL_CAPITAL = _env_float("TOTAL_CAPITAL", 100)
|
||||
DAILY_START_CAPITAL = _env_float("DAILY_START_CAPITAL", 30)
|
||||
DAILY_LOSS_CAPITAL = _env_float("DAILY_LOSS_CAPITAL", 20)
|
||||
DAILY_PROFIT_CAPITAL = _env_float("DAILY_PROFIT_CAPITAL", 50)
|
||||
BTC_LEVERAGE = _env_int("BTC_LEVERAGE", 10)
|
||||
ALT_LEVERAGE = _env_int("ALT_LEVERAGE", 5)
|
||||
# 交易日滚动与「可开仓」整点:按应用本地时区 wall clock(默认北京时间 UTC+8)
|
||||
TRADING_DAY_RESET_HOUR = int(os.getenv("TRADING_DAY_RESET_HOUR", "8"))
|
||||
TRADING_DAY_RESET_HOUR = _env_int("TRADING_DAY_RESET_HOUR", 8)
|
||||
TRADING_DAY_RESET_OPEN_GUARD_ENABLED = os.getenv(
|
||||
"TRADING_DAY_RESET_OPEN_GUARD_ENABLED", "true"
|
||||
).lower() in ("1", "true", "yes", "on")
|
||||
@@ -310,47 +332,45 @@ OKX_API_KEY = os.getenv("OKX_API_KEY", "")
|
||||
OKX_API_SECRET = os.getenv("OKX_API_SECRET", "")
|
||||
OKX_API_PASSPHRASE = os.getenv("OKX_API_PASSPHRASE", "")
|
||||
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 = _env_float("OKX_OPTIONS_TRADE_BUDGET_USDC", 10)
|
||||
OKX_OPTIONS_DEFAULT_UNDERLY = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper()
|
||||
OKX_TD_MODE = os.getenv("OKX_TD_MODE", "cross")
|
||||
OKX_POS_MODE = os.getenv("OKX_POS_MODE", "hedge")
|
||||
EXCHANGE_DISPLAY_NAME = (os.getenv("EXCHANGE_DISPLAY_NAME") or "OKX").strip() or "OKX"
|
||||
BALANCE_REFRESH_SECONDS = int(os.getenv("BALANCE_REFRESH_SECONDS", "60"))
|
||||
PRICE_REFRESH_SECONDS = int(os.getenv("PRICE_REFRESH_SECONDS", "5"))
|
||||
KEY_ALERT_MAX_TIMES = int(os.getenv("KEY_ALERT_MAX_TIMES", "3"))
|
||||
KEY_ALERT_INTERVAL_MINUTES = int(os.getenv("KEY_ALERT_INTERVAL_MINUTES", "5"))
|
||||
KEY_BREAKOUT_LIMIT_PCT = float(os.getenv("KEY_BREAKOUT_LIMIT_PCT", "1.5"))
|
||||
BALANCE_REFRESH_SECONDS = _env_int("BALANCE_REFRESH_SECONDS", 60)
|
||||
PRICE_REFRESH_SECONDS = _env_int("PRICE_REFRESH_SECONDS", 5)
|
||||
KEY_ALERT_MAX_TIMES = _env_int("KEY_ALERT_MAX_TIMES", 3)
|
||||
KEY_ALERT_INTERVAL_MINUTES = _env_int("KEY_ALERT_INTERVAL_MINUTES", 5)
|
||||
KEY_BREAKOUT_LIMIT_PCT = _env_float("KEY_BREAKOUT_LIMIT_PCT", 1.5)
|
||||
AUTO_TRANSFER_ENABLED = os.getenv("AUTO_TRANSFER_ENABLED", "false").lower() == "true"
|
||||
AUTO_TRANSFER_AMOUNT = float(os.getenv("AUTO_TRANSFER_AMOUNT", "30"))
|
||||
AUTO_TRANSFER_AMOUNT = _env_float("AUTO_TRANSFER_AMOUNT", 30)
|
||||
AUTO_TRANSFER_FROM = os.getenv("AUTO_TRANSFER_FROM", "funding")
|
||||
AUTO_TRANSFER_TO = os.getenv("AUTO_TRANSFER_TO", "swap")
|
||||
FORCE_CLOSE_ENABLED = os.getenv("FORCE_CLOSE_ENABLED", "false").lower() == "true"
|
||||
FORCE_CLOSE_BJ_HOUR = int(os.getenv("FORCE_CLOSE_BJ_HOUR", "0"))
|
||||
FORCE_CLOSE_BJ_HOUR = _env_int("FORCE_CLOSE_BJ_HOUR", 0)
|
||||
# 自动划转:仅在北京时间该整点「小时」内尝试;transfer_logs.transfer_day 存 UTC 自然日(与 OKX 日界一致便于对账)
|
||||
AUTO_TRANSFER_BJ_HOUR = int(os.getenv("AUTO_TRANSFER_BJ_HOUR", "8"))
|
||||
AUTO_TRANSFER_BJ_HOUR = _env_int("AUTO_TRANSFER_BJ_HOUR", 8)
|
||||
POSITION_SIZING_MODE = load_position_sizing_mode()
|
||||
TRADE_POLICY = load_trade_policy()
|
||||
WECHAT_TIMEOUT_SECONDS = int(os.getenv("WECHAT_TIMEOUT_SECONDS", "10"))
|
||||
MONITOR_POLL_SECONDS = int(os.getenv("MONITOR_POLL_SECONDS", "3"))
|
||||
RECONCILE_STARTUP_GRACE_SEC = int(os.getenv("RECONCILE_STARTUP_GRACE_SEC", "90"))
|
||||
RECONCILE_FLAT_CONFIRM_POLLS = max(1, int(os.getenv("RECONCILE_FLAT_CONFIRM_POLLS", "3")))
|
||||
WECHAT_TIMEOUT_SECONDS = _env_int("WECHAT_TIMEOUT_SECONDS", 10)
|
||||
MONITOR_POLL_SECONDS = _env_int("MONITOR_POLL_SECONDS", 3)
|
||||
RECONCILE_STARTUP_GRACE_SEC = _env_int("RECONCILE_STARTUP_GRACE_SEC", 90)
|
||||
RECONCILE_FLAT_CONFIRM_POLLS = max(1, _env_int("RECONCILE_FLAT_CONFIRM_POLLS", 3))
|
||||
_APP_STARTED_AT = time.time()
|
||||
_RECONCILE_FLAT_STREAK = {}
|
||||
BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC = max(
|
||||
15, int(os.getenv("BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC", "60"))
|
||||
)
|
||||
BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC = max(15, _env_int("BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC", 60))
|
||||
_BREAKEVEN_LAST_EX_SYNC: dict[int, float] = {}
|
||||
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
||||
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO") or "0.98")
|
||||
FULL_MARGIN_BUFFER_RATIO = _env_float("FULL_MARGIN_BUFFER_RATIO", 0.98)
|
||||
TRANSFER_CCY = (os.getenv("TRANSFER_CCY", "USDT") or "USDT").strip().upper() or "USDT"
|
||||
OKX_POSITION_INST_TYPE = os.getenv("OKX_POSITION_INST_TYPE", "SWAP")
|
||||
EXCHANGE_POSITION_SYNC_FROM_BJ = (os.getenv("EXCHANGE_POSITION_SYNC_FROM_BJ") or "").strip()
|
||||
EXCHANGE_POSITION_HISTORY_LIMIT = max(50, min(1000, int(os.getenv("EXCHANGE_POSITION_HISTORY_LIMIT", "200"))))
|
||||
EXCHANGE_POSITION_HISTORY_LIMIT = max(50, min(1000, _env_int("EXCHANGE_POSITION_HISTORY_LIMIT", 200)))
|
||||
_LAST_EXCHANGE_PNL_SYNC_AT = 0.0
|
||||
UPLOAD_FOLDER = resolve_path(os.getenv("UPLOAD_DIR", "static/images"))
|
||||
ORDER_CHART_ENABLED = os.getenv("ORDER_CHART_ENABLED", "true").lower() == "true"
|
||||
ORDER_CHART_TFS = [x.strip() for x in (os.getenv("ORDER_CHART_TFS", "4h,1h,15m,5m") or "").split(",") if x.strip()]
|
||||
ORDER_CHART_LIMIT = int(os.getenv("ORDER_CHART_LIMIT", "100"))
|
||||
ORDER_CHART_LIMIT = _env_int("ORDER_CHART_LIMIT", 100)
|
||||
ORDER_CHART_DIR = resolve_path(os.getenv("ORDER_CHART_DIR", "static/images/order_charts"))
|
||||
from lib.trade.daily_open_limit_lib import (
|
||||
build_daily_open_alert_prompt,
|
||||
@@ -364,25 +384,25 @@ from lib.trade.daily_open_limit_lib import (
|
||||
)
|
||||
|
||||
DAILY_OPEN_ALERT_THRESHOLD, DAILY_OPEN_HARD_LIMIT = load_daily_open_limits_from_env()
|
||||
RISK_PERCENT = float(os.getenv("RISK_PERCENT") or "2")
|
||||
BREAKEVEN_RR_TRIGGER = float(os.getenv("BREAKEVEN_RR_TRIGGER") or "1.0")
|
||||
BREAKEVEN_OFFSET_PCT = float(os.getenv("BREAKEVEN_OFFSET_PCT") or "0.02")
|
||||
BREAKEVEN_STEP_R = float(os.getenv("BREAKEVEN_STEP_R") or "1.0")
|
||||
RISK_PERCENT = _env_float("RISK_PERCENT", 2)
|
||||
BREAKEVEN_RR_TRIGGER = _env_float("BREAKEVEN_RR_TRIGGER", 1.0)
|
||||
BREAKEVEN_OFFSET_PCT = _env_float("BREAKEVEN_OFFSET_PCT", 0.02)
|
||||
BREAKEVEN_STEP_R = _env_float("BREAKEVEN_STEP_R", 1.0)
|
||||
ORDER_MONITOR_TYPE_MANUAL = "下单监控"
|
||||
ORDER_MONITOR_TYPE_KEY_AUTO = "关键位监控"
|
||||
KEY_AUTO_MIN_PLANNED_RR = float(os.getenv("KEY_AUTO_MIN_PLANNED_RR") or "1.5")
|
||||
KEY_STOP_OUTSIDE_BREAKOUT_PCT = float(os.getenv("KEY_STOP_OUTSIDE_BREAKOUT_PCT") or "0.5")
|
||||
KEY_TREND_STOP_OUTSIDE_PCT = float(os.getenv("KEY_TREND_STOP_OUTSIDE_PCT") or "1")
|
||||
KEY_DAILY_VOLUME_RANK_MAX = max(1, int(os.getenv("KEY_DAILY_VOLUME_RANK_MAX") or "30"))
|
||||
KEY_AUTO_MIN_PLANNED_RR = _env_float("KEY_AUTO_MIN_PLANNED_RR", 1.5)
|
||||
KEY_STOP_OUTSIDE_BREAKOUT_PCT = _env_float("KEY_STOP_OUTSIDE_BREAKOUT_PCT", 0.5)
|
||||
KEY_TREND_STOP_OUTSIDE_PCT = _env_float("KEY_TREND_STOP_OUTSIDE_PCT", 1)
|
||||
KEY_DAILY_VOLUME_RANK_MAX = max(1, _env_int("KEY_DAILY_VOLUME_RANK_MAX", 30))
|
||||
|
||||
MANUAL_MIN_PLANNED_RR = float(os.getenv("MANUAL_MIN_PLANNED_RR") or "1.4")
|
||||
MAX_ACTIVE_POSITIONS = max(1, int(os.getenv("MAX_ACTIVE_POSITIONS") or "1"))
|
||||
KEY_VOLUME_MA_BARS = max(1, int(os.getenv("KEY_VOLUME_MA_BARS") or "20"))
|
||||
KEY_VOLUME_RATIO_MIN = float(os.getenv("KEY_VOLUME_RATIO_MIN") or "1.3")
|
||||
KEY_BREAKOUT_AMP_MIN_PCT = float(os.getenv("KEY_BREAKOUT_AMP_MIN_PCT") or "0.03")
|
||||
KEY_BREAKOUT_AMP_MAX_PCT = float(os.getenv("KEY_BREAKOUT_AMP_MAX_PCT") or "0.5")
|
||||
KEY_CONFIRM_BREAKOUT_BAR = int(os.getenv("KEY_CONFIRM_BREAKOUT_BAR") or "-2")
|
||||
KEY_CONFIRM_BAR = int(os.getenv("KEY_CONFIRM_BAR") or "-1")
|
||||
MANUAL_MIN_PLANNED_RR = _env_float("MANUAL_MIN_PLANNED_RR", 1.4)
|
||||
MAX_ACTIVE_POSITIONS = max(1, _env_int("MAX_ACTIVE_POSITIONS", 1))
|
||||
KEY_VOLUME_MA_BARS = max(1, _env_int("KEY_VOLUME_MA_BARS", 20))
|
||||
KEY_VOLUME_RATIO_MIN = _env_float("KEY_VOLUME_RATIO_MIN", 1.3)
|
||||
KEY_BREAKOUT_AMP_MIN_PCT = _env_float("KEY_BREAKOUT_AMP_MIN_PCT", 0.03)
|
||||
KEY_BREAKOUT_AMP_MAX_PCT = _env_float("KEY_BREAKOUT_AMP_MAX_PCT", 0.5)
|
||||
KEY_CONFIRM_BREAKOUT_BAR = _env_int("KEY_CONFIRM_BREAKOUT_BAR", -2)
|
||||
KEY_CONFIRM_BAR = _env_int("KEY_CONFIRM_BAR", -1)
|
||||
KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT = os.getenv("KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT", "true").lower() in (
|
||||
"1",
|
||||
"true",
|
||||
|
||||
Reference in New Issue
Block a user