chore: align binance/okx env examples with gate and add sync script
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -166,6 +166,10 @@ BALANCE_REFRESH_SECONDS=60
|
||||
PRICE_REFRESH_SECONDS=5
|
||||
# 后台监控轮询周期(秒)
|
||||
MONITOR_POLL_SECONDS=3
|
||||
# 重启后多少秒内不做「外部平仓」同步(避免 API 未就绪误判)
|
||||
RECONCILE_STARTUP_GRACE_SEC=90
|
||||
# 连续多少次轮询确认交易所空仓后,才记为外部平仓(默认 3 次 ≈ 9 秒)
|
||||
RECONCILE_FLAT_CONFIRM_POLLS=3
|
||||
# 使用可用资金时的缓冲比例(如0.98代表用98%)
|
||||
FULL_MARGIN_BUFFER_RATIO=0.98
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@ DB_PATH=crypto.db
|
||||
# 交易截图上传目录
|
||||
UPLOAD_DIR=static/images
|
||||
|
||||
# 自动备份(scripts/backup_data.sh + cron,可选;默认即可)
|
||||
# BACKUP_ROOT=/root/backups
|
||||
# BACKUP_RETENTION_DAYS=30
|
||||
# BACKUP_INSTANCE=crypto_monitor_okx
|
||||
|
||||
# 训练总资金(U)
|
||||
# TOTAL_CAPITAL=100 # 已弃用,资金展示读交易所
|
||||
# 计仓:risk=以损定仓(默认);full_margin=合约可用×FULL_MARGIN_BUFFER_RATIO 全仓杠杆(须无仓后重启)
|
||||
@@ -69,6 +74,8 @@ BTC_LEVERAGE=10
|
||||
ALT_LEVERAGE=5
|
||||
# 交易日重置小时(北京时间)
|
||||
TRADING_DAY_RESET_HOUR=8
|
||||
# 整点前禁止新开仓:true=启用(默认),false=关闭(仍可保留 8 点作为交易日划分)
|
||||
TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
|
||||
|
||||
# 是否开启 OKX 实盘下单(false=只做本地流程,true=真实下单)
|
||||
LIVE_TRADING_ENABLED=true
|
||||
@@ -88,6 +95,10 @@ OKX_POSITION_INST_TYPE=SWAP
|
||||
# EXCHANGE_POSITION_SYNC_FROM_BJ=2026-01-01
|
||||
# 单次拉取历史仓位条数上限(OKX 每页最多 100,程序会分页)
|
||||
# EXCHANGE_POSITION_HISTORY_LIMIT=200
|
||||
# 页面与浏览器标签展示的交易所名称(多环境区分时可改成例如 OKX·测试网)
|
||||
# EXCHANGE_DISPLAY_NAME=OKX
|
||||
# 企业微信推送里展示的账户备注
|
||||
# OKX_ACCOUNT_LABEL=
|
||||
|
||||
# =============================================================================
|
||||
# 关键位程序自动下单(与 POSITION_SIZING_MODE 联动,修改后须重启 PM2)
|
||||
@@ -105,18 +116,71 @@ OKX_POSITION_INST_TYPE=SWAP
|
||||
# 顺势加仓、趋势回调不受本开关控制;全仓模式下策略自动仍禁止。
|
||||
KEY_AUTO_ORDER_ENABLED=false
|
||||
|
||||
# 关键位监控:5m收线突破过滤参数
|
||||
# =============================================================================
|
||||
# 关键位门控(页面「关键位监控」规则条与 _key_hard_checks 共用)
|
||||
# =============================================================================
|
||||
# 【周期】门控 K 线周期,如 5m、15m;仅影响关键位硬条件,不改变顶栏分区
|
||||
KLINE_TIMEFRAME=5m
|
||||
# OKX 遗留:突破过滤百分比(与 KEY_BREAKOUT_AMP_* 并存,程序仍读取)
|
||||
KEY_BREAKOUT_LIMIT_PCT=1.5
|
||||
# 【确认K】闭合 K 序列中的棒偏移:突破棒默认 -2(倒数第2根),确认棒默认 -1(倒数第1根)
|
||||
KEY_CONFIRM_BREAKOUT_BAR=-2
|
||||
KEY_CONFIRM_BAR=-1
|
||||
# 【量能】突破棒成交量 > 前 N 根均量 × 倍数(默认 N=20,倍数=1.3 即放大 30%)
|
||||
KEY_VOLUME_MA_BARS=20
|
||||
KEY_VOLUME_RATIO_MIN=1.3
|
||||
# 【箱体/收敛】突破K收盘越过关键位(占该侧价格%)的下限;无上限(过猛由计划RR过滤)
|
||||
KEY_BREAKOUT_AMP_MIN_PCT=0.03
|
||||
# 已不参与门控,可保留配置项兼容旧环境
|
||||
KEY_BREAKOUT_AMP_MAX_PCT=0.5
|
||||
# 【阻力/支撑】突破后微信提醒次数与间隔(分钟)
|
||||
KEY_ALERT_MAX_TIMES=3
|
||||
KEY_ALERT_INTERVAL_MINUTES=5
|
||||
# 【日成交量排名】品种须在该排名前 N 名(添加关键位与运行时门控均校验)
|
||||
KEY_DAILY_VOLUME_RANK_MAX=30
|
||||
# 【关键位自动开仓盈亏比】按确认K收盘 E 计算,严格大于该值才市价开仓(如 1.5 表示须 >1.5:1)
|
||||
KEY_AUTO_MIN_PLANNED_RR=1.5
|
||||
# 止损:突破 K 极值向外缓冲的百分比(默认 0.5 即 0.5%)
|
||||
KEY_STOP_OUTSIDE_BREAKOUT_PCT=0.5
|
||||
# 趋势单方案:止损在突破 K 极值外侧的百分比(默认 1 即 1%)
|
||||
KEY_TREND_STOP_OUTSIDE_PCT=1
|
||||
|
||||
# =============================================================================
|
||||
# 交易执行 / 人工风控(页面「实盘下单」)
|
||||
# =============================================================================
|
||||
# 【最大同时持仓】active 订单数达到该值后禁止人工与关键位自动再加仓(默认 1=单仓)
|
||||
MAX_ACTIVE_POSITIONS=1
|
||||
# 【人工下单最低盈亏比】按当前价与 SL/TP 计算,低于该值前后端均拒绝(默认 1.4,即须 >=1.4:1)
|
||||
MANUAL_MIN_PLANNED_RR=1.4
|
||||
# 【关键位连开计仓】true=已有持仓时关键位自动单仍按「无仓时」资金快照算保证金基数
|
||||
KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT=true
|
||||
# 【单日开仓 AI 提醒】本交易日开仓达到该次数时推送企业微信 AI 克制提醒(不拦单)
|
||||
DAILY_OPEN_ALERT_THRESHOLD=5
|
||||
# 【单日开仓硬上限】本交易日开仓次数>=该值后禁止一切新开仓直至下一交易日(北京时间 TRADING_DAY_RESET_HOUR 切日);0=不启用
|
||||
DAILY_OPEN_HARD_LIMIT=0
|
||||
|
||||
# =============================================================================
|
||||
# 账户冷静期 / 日冻结风控(手动平仓、外部平仓、复盘情绪标签)
|
||||
# 详见 docs/account-risk-cooldown.md
|
||||
# =============================================================================
|
||||
# RISK_CONTROL_ENABLED=true
|
||||
# RISK_COOLING_HOURS_MANUAL=4
|
||||
# RISK_COOLING_HOURS_MANUAL_JOURNAL=1
|
||||
# RISK_MANUAL_CLOSE_DAILY_LIMIT=2
|
||||
# RISK_MOOD_ISSUES_DAILY_FREEZE=true
|
||||
|
||||
# 资金与仓位刷新周期(秒)
|
||||
BALANCE_REFRESH_SECONDS=60
|
||||
# 前端价格快照轮询(秒)
|
||||
PRICE_REFRESH_SECONDS=5
|
||||
# 后台监控轮询周期(秒)
|
||||
MONITOR_POLL_SECONDS=3
|
||||
# 移动保本同步交易所止盈止损的最小间隔(秒),避免频繁撤挂叠单
|
||||
BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC=60
|
||||
# 重启后多少秒内不做「外部平仓」同步(避免 API 未就绪误判)
|
||||
RECONCILE_STARTUP_GRACE_SEC=90
|
||||
# 连续多少次轮询确认交易所空仓后,才记为外部平仓(默认 3 次 ≈ 9 秒)
|
||||
RECONCILE_FLAT_CONFIRM_POLLS=3
|
||||
# 使用可用资金时的缓冲比例(如0.98代表用98%)
|
||||
FULL_MARGIN_BUFFER_RATIO=0.98
|
||||
|
||||
@@ -163,10 +227,7 @@ AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||
# ORDER_CHART_TFS=4h,1h,15m,5m
|
||||
# ORDER_CHART_LIMIT=100
|
||||
# ORDER_CHART_DIR=static/images/order_charts
|
||||
# 详见 DAILY_OPEN_ALERT_THRESHOLD / DAILY_OPEN_HARD_LIMIT;说明文档 docs/daily-open-limit.md
|
||||
# 关键位:标准方案止损外侧%、趋势单方案止损外侧%(默认 0.5 / 1)
|
||||
# KEY_STOP_OUTSIDE_BREAKOUT_PCT=0.5
|
||||
# KEY_TREND_STOP_OUTSIDE_PCT=1
|
||||
# 详见上文 DAILY_OPEN_ALERT_THRESHOLD / DAILY_OPEN_HARD_LIMIT;说明文档 docs/daily-open-limit.md
|
||||
# 以损定仓(按交易账户资金的百分比)
|
||||
# RISK_PERCENT=2
|
||||
# 移动保本触发(达到多少R触发)与偏移(百分比)
|
||||
@@ -178,42 +239,4 @@ AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||
# DEFAULT_TRADE_STYLE=trend
|
||||
|
||||
APP_TIMEZONE=Asia/Shanghai
|
||||
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日/可开仓等
|
||||
|
||||
# 默认启用「北京时间 TRADING_DAY_RESET_HOUR 前禁止新开仓」;网页顶栏可运行时切换(写入 DB)
|
||||
TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
|
||||
|
||||
MAX_ACTIVE_POSITIONS=1
|
||||
MANUAL_MIN_PLANNED_RR=1.4
|
||||
# 【单日开仓 AI 提醒】本交易日开仓达到该次数时推送企业微信 AI 克制提醒(不拦单)
|
||||
DAILY_OPEN_ALERT_THRESHOLD=5
|
||||
# 【单日开仓硬上限】本交易日开仓次数>=该值后禁止一切新开仓直至下一交易日(北京时间 TRADING_DAY_RESET_HOUR 切日);0=不启用
|
||||
DAILY_OPEN_HARD_LIMIT=0
|
||||
|
||||
# =============================================================================
|
||||
# 账户冷静期 / 日冻结风控(手动平仓、外部平仓、复盘情绪标签)
|
||||
# 详见 docs/account-risk-cooldown.md
|
||||
# =============================================================================
|
||||
# RISK_CONTROL_ENABLED=true
|
||||
# RISK_COOLING_HOURS_MANUAL=4
|
||||
# RISK_COOLING_HOURS_MANUAL_JOURNAL=1
|
||||
# RISK_MANUAL_CLOSE_DAILY_LIMIT=2
|
||||
# RISK_MOOD_ISSUES_DAILY_FREEZE=true
|
||||
|
||||
KEY_CONFIRM_BREAKOUT_BAR=-2
|
||||
KEY_CONFIRM_BAR=-1
|
||||
KEY_VOLUME_MA_BARS=20
|
||||
KEY_VOLUME_RATIO_MIN=1.3
|
||||
# 【箱体/收敛】突破K收盘越过关键位下限%
|
||||
KEY_BREAKOUT_AMP_MIN_PCT=0.03
|
||||
KEY_BREAKOUT_AMP_MAX_PCT=0.5
|
||||
# 【阻力/支撑】突破后微信提醒
|
||||
KEY_ALERT_MAX_TIMES=3
|
||||
KEY_ALERT_INTERVAL_MINUTES=5
|
||||
|
||||
EXCHANGE_DISPLAY_NAME=OKX
|
||||
OKX_ACCOUNT_LABEL=
|
||||
|
||||
BACKUP_ROOT=/root/backups
|
||||
BACKUP_RETENTION_DAYS=30
|
||||
BACKUP_INSTANCE=crypto_monitor_okx
|
||||
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日;开仓整点限制见 TRADING_DAY_RESET_OPEN_GUARD_ENABLED
|
||||
|
||||
@@ -103,6 +103,20 @@ python scripts/sync_four_exchange_position_sizing_env.py --set-buffer 0.98
|
||||
|
||||
---
|
||||
|
||||
## 共用交易 / 关键位 / 轮询项
|
||||
|
||||
`scripts/sync_common_trading_env.py`:以 Gate `.env.example` 为基准,向**币安、OKX** 的 `.env` **追加缺失项**(不覆盖 API 密钥与已有自定义值)。
|
||||
|
||||
```bash
|
||||
python scripts/sync_common_trading_env.py
|
||||
python scripts/sync_common_trading_env.py --dry-run
|
||||
python scripts/sync_common_trading_env.py --instances crypto_monitor_okx
|
||||
```
|
||||
|
||||
补全项含:`RECONCILE_*`、`PRICE_REFRESH_SECONDS`、`KEY_*` 门控、`FORCE_CLOSE_*`、`KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT` 等。
|
||||
|
||||
---
|
||||
|
||||
## 部署后重启
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
将三所共用的交易/关键位/轮询 env 写入币安、OKX 的 .env(缺失则追加,不覆盖已有值)。
|
||||
|
||||
以 Gate .env.example 为基准;Gate 自身也可运行以补缺失项。
|
||||
|
||||
用法(仓库根目录):
|
||||
python scripts/sync_common_trading_env.py
|
||||
python scripts/sync_common_trading_env.py --dry-run
|
||||
python scripts/sync_common_trading_env.py --instances crypto_monitor_okx
|
||||
|
||||
修改后须 pm2 restart 对应实例。说明见 docs/env-sync-scripts.md
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
DEFAULT_INSTANCES = (
|
||||
"crypto_monitor_binance",
|
||||
"crypto_monitor_okx",
|
||||
)
|
||||
|
||||
# 与 crypto_monitor_gate/.env.example 对齐(不含 GATE_* / 各所 API 密钥)
|
||||
SHARED_DEFAULTS: dict[str, str] = {
|
||||
"TRADING_DAY_RESET_OPEN_GUARD_ENABLED": "true",
|
||||
"KEY_CONFIRM_BREAKOUT_BAR": "-2",
|
||||
"KEY_CONFIRM_BAR": "-1",
|
||||
"KEY_VOLUME_MA_BARS": "20",
|
||||
"KEY_VOLUME_RATIO_MIN": "1.3",
|
||||
"KEY_BREAKOUT_AMP_MIN_PCT": "0.03",
|
||||
"KEY_BREAKOUT_AMP_MAX_PCT": "0.5",
|
||||
"KEY_ALERT_MAX_TIMES": "3",
|
||||
"KEY_ALERT_INTERVAL_MINUTES": "5",
|
||||
"KEY_DAILY_VOLUME_RANK_MAX": "30",
|
||||
"KEY_AUTO_MIN_PLANNED_RR": "1.5",
|
||||
"KEY_STOP_OUTSIDE_BREAKOUT_PCT": "0.5",
|
||||
"KEY_TREND_STOP_OUTSIDE_PCT": "1",
|
||||
"MAX_ACTIVE_POSITIONS": "1",
|
||||
"MANUAL_MIN_PLANNED_RR": "1.4",
|
||||
"KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT": "true",
|
||||
"DAILY_OPEN_ALERT_THRESHOLD": "5",
|
||||
"DAILY_OPEN_HARD_LIMIT": "0",
|
||||
"BALANCE_REFRESH_SECONDS": "60",
|
||||
"PRICE_REFRESH_SECONDS": "5",
|
||||
"MONITOR_POLL_SECONDS": "3",
|
||||
"RECONCILE_STARTUP_GRACE_SEC": "90",
|
||||
"RECONCILE_FLAT_CONFIRM_POLLS": "3",
|
||||
"FULL_MARGIN_BUFFER_RATIO": "0.98",
|
||||
"FORCE_CLOSE_BJ_HOUR": "0",
|
||||
"FORCE_CLOSE_ENABLED": "false",
|
||||
"WECHAT_TIMEOUT_SECONDS": "10",
|
||||
"AI_TIMEOUT_SECONDS": "120",
|
||||
}
|
||||
|
||||
|
||||
def _parse_env(path: str) -> list[str]:
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
return f.read().replace("\r\n", "\n").replace("\r", "\n").splitlines()
|
||||
|
||||
|
||||
def _env_get(lines: list[str], key: str) -> str | None:
|
||||
pat = re.compile(r"^\s*" + re.escape(key) + r"\s*=\s*(.*)\s*$")
|
||||
for line in lines:
|
||||
m = pat.match(line)
|
||||
if m:
|
||||
return m.group(1).strip().strip('"').strip("'")
|
||||
return None
|
||||
|
||||
|
||||
def _upsert(lines: list[str], key: str, value: str) -> list[str]:
|
||||
pat = re.compile(r"^\s*" + re.escape(key) + r"\s*=")
|
||||
out: list[str] = []
|
||||
replaced = False
|
||||
for line in lines:
|
||||
if pat.match(line):
|
||||
if not replaced:
|
||||
out.append(f"{key}={value}")
|
||||
replaced = True
|
||||
continue
|
||||
out.append(line)
|
||||
if not replaced:
|
||||
if out and out[-1].strip():
|
||||
out.append("")
|
||||
out.append(f"{key}={value}")
|
||||
return out
|
||||
|
||||
|
||||
def sync_one(dir_name: str, *, dry_run: bool, force: bool) -> bool:
|
||||
path = os.path.join(REPO, dir_name, ".env")
|
||||
if not os.path.isfile(path):
|
||||
print(f"skip (no .env): {dir_name}")
|
||||
return False
|
||||
lines = _parse_env(path)
|
||||
added: list[str] = []
|
||||
for key, val in SHARED_DEFAULTS.items():
|
||||
cur = _env_get(lines, key)
|
||||
if cur is None or (force and cur != val):
|
||||
lines = _upsert(lines, key, val)
|
||||
added.append(key)
|
||||
if not added:
|
||||
print(f"ok (unchanged): {dir_name}")
|
||||
return False
|
||||
print(f"update: {dir_name}")
|
||||
for key in added:
|
||||
print(f" + {key}={SHARED_DEFAULTS[key]}")
|
||||
if not dry_run:
|
||||
text = "\n".join(lines).rstrip() + "\n"
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(text)
|
||||
return True
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="同步币安/OKX 共用 trading env(缺失项追加)")
|
||||
ap.add_argument("--dry-run", action="store_true")
|
||||
ap.add_argument("--force", action="store_true", help="覆盖已有值(慎用)")
|
||||
ap.add_argument(
|
||||
"--instances",
|
||||
nargs="+",
|
||||
metavar="DIR",
|
||||
help="默认 crypto_monitor_binance crypto_monitor_okx",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
instances = tuple(args.instances) if args.instances else DEFAULT_INSTANCES
|
||||
any_changed = False
|
||||
for inst in instances:
|
||||
if sync_one(inst, dry_run=args.dry_run, force=args.force):
|
||||
any_changed = True
|
||||
if args.dry_run and any_changed:
|
||||
print("(dry-run, 未写入)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user