Add Ubuntu interactive one-click deploy; default sim mode.
Ship deploy/manage.sh toolkit for /opt/coinox, rewrite Ubuntu-only deploy docs, drop Windows deploy guidance. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+339
-338
@@ -1,338 +1,339 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 环境配置模板(可提交 Git).程序运行时只读取同目录下的 .env.
|
# 环境配置模板(可提交 Git).程序运行时只读取同目录下的 .env.
|
||||||
#
|
#
|
||||||
# 首次部署 / 新机:
|
# 首次部署 / 新机:
|
||||||
# cp .env.example .env
|
# cp .env.example .env
|
||||||
# nano .env # 填入真实密钥,端口,代理等
|
# nano .env # 填入真实密钥,端口,代理等
|
||||||
#
|
#
|
||||||
# 升级代码(git pull)前建议备份(.env 不在 Git 中,pull 不会覆盖):
|
# 升级代码(git pull)前建议备份(.env 不在 Git 中,pull 不会覆盖):
|
||||||
# cp .env .env.backup.$(date +%Y%m%d)
|
# cp .env .env.backup.$(date +%Y%m%d)
|
||||||
#
|
#
|
||||||
# 从备份恢复:
|
# 从备份恢复:
|
||||||
# cp .env.backup.YYYYMMDD .env
|
# cp .env.backup.YYYYMMDD .env
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
# 服务监听地址(云服务器通常用 0.0.0.0)
|
# 服务监听地址(云服务器通常用 0.0.0.0)
|
||||||
APP_HOST=0.0.0.0
|
APP_HOST=0.0.0.0
|
||||||
# 服务端口
|
# 服务端口
|
||||||
APP_PORT=5010
|
APP_PORT=5010
|
||||||
# 是否开启调试模式(生产建议 false)
|
# 是否开启调试模式(生产建议 false)
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
|
|
||||||
# 登录账号
|
# 登录账号
|
||||||
APP_USERNAME=admin
|
APP_USERNAME=admin
|
||||||
# 登录密码(请改成你自己的强密码)
|
# 登录密码(请改成你自己的强密码)
|
||||||
APP_PASSWORD=admin123
|
APP_PASSWORD=admin123
|
||||||
# 是否关闭登录校验(局域网可设 true;公网务必 false)
|
# 是否关闭登录校验(局域网可设 true;公网务必 false)
|
||||||
APP_AUTH_DISABLED=true
|
APP_AUTH_DISABLED=true
|
||||||
# --- 多账户交易中控 manual_trading_hub ---
|
# --- 多账户交易中控 manual_trading_hub ---
|
||||||
# 中控请求本实例 /api/hub/* 时携带请求头 X-Hub-Token,须与中控启动环境变量 HUB_BRIDGE_TOKEN 一致
|
# 中控请求本实例 /api/hub/* 时携带请求头 X-Hub-Token,须与中控启动环境变量 HUB_BRIDGE_TOKEN 一致
|
||||||
# 未设置且 APP_AUTH_DISABLED=false 时,仅网页登录后可访问;本机联调可保持 APP_AUTH_DISABLED=true
|
# 未设置且 APP_AUTH_DISABLED=false 时,仅网页登录后可访问;本机联调可保持 APP_AUTH_DISABLED=true
|
||||||
# HUB_BRIDGE_TOKEN=your-long-random-token
|
# HUB_BRIDGE_TOKEN=your-long-random-token
|
||||||
# 允许复盘中控 iframe 内嵌本实例(与 hub 域名一致;默认已开启)
|
# 允许复盘中控 iframe 内嵌本实例(与 hub 域名一致;默认已开启)
|
||||||
# APP_ALLOW_HUB_EMBED=true
|
# APP_ALLOW_HUB_EMBED=true
|
||||||
# HUB_EMBED_PARENT_ORIGINS=https://hub.example.com
|
# HUB_EMBED_PARENT_ORIGINS=https://hub.example.com
|
||||||
# HTTPS 且经 iframe 打开时建议 true;不设则 hub-sso 在 HTTPS 下也会自动尝试 SameSite=None
|
# HTTPS 且经 iframe 打开时建议 true;不设则 hub-sso 在 HTTPS 下也会自动尝试 SameSite=None
|
||||||
# APP_COOKIE_SECURE=true
|
# APP_COOKIE_SECURE=true
|
||||||
# Flask 会话密钥(必须替换为长随机字符串)
|
# Flask 会话密钥(必须替换为长随机字符串)
|
||||||
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET
|
||||||
|
|
||||||
# 企业微信机器人 Webhook(用于行情/风控推送)
|
# 企业微信机器人 Webhook(用于行情/风控推送)
|
||||||
WECHAT_WEBHOOK=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=REPLACE_WITH_REAL_KEY
|
WECHAT_WEBHOOK=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=REPLACE_WITH_REAL_KEY
|
||||||
|
|
||||||
# 数据库文件路径(相对路径会自动按项目目录解析)
|
# 数据库文件路径(相对路径会自动按项目目录解析)
|
||||||
DB_PATH=crypto.db
|
DB_PATH=crypto.db
|
||||||
# 交易截图上传目录
|
# 交易截图上传目录
|
||||||
UPLOAD_DIR=static/images
|
UPLOAD_DIR=static/images
|
||||||
|
|
||||||
# 自动备份(scripts/backup_data.sh + cron,可选;默认即可)
|
# 自动备份(scripts/backup_data.sh + cron,可选;默认即可)
|
||||||
# BACKUP_ROOT=/root/backups
|
# BACKUP_ROOT=/root/backups
|
||||||
# BACKUP_RETENTION_DAYS=30
|
# BACKUP_RETENTION_DAYS=30
|
||||||
# BACKUP_INSTANCE=coinox
|
# BACKUP_INSTANCE=coinox
|
||||||
|
|
||||||
# 训练总资金(U)
|
# 训练总资金(U)
|
||||||
# TOTAL_CAPITAL=100 # 已弃用,资金展示读交易所
|
# TOTAL_CAPITAL=100 # 已弃用,资金展示读交易所
|
||||||
# 计仓:risk=以损定仓(默认);full_margin=合约可用×FULL_MARGIN_BUFFER_RATIO 全仓杠杆(须无仓后重启)
|
# 计仓:risk=以损定仓(默认);full_margin=合约可用×FULL_MARGIN_BUFFER_RATIO 全仓杠杆(须无仓后重启)
|
||||||
POSITION_SIZING_MODE=risk
|
POSITION_SIZING_MODE=risk
|
||||||
# 方向限制(默认 false=双向均可;true 时按 TRADE_DIRECTION 限制,修改后须重启)
|
# 方向限制(默认 false=双向均可;true 时按 TRADE_DIRECTION 限制,修改后须重启)
|
||||||
# TRADE_DIRECTION=long_only | short_only | both(或 多/空/双向)
|
# TRADE_DIRECTION=long_only | short_only | both(或 多/空/双向)
|
||||||
TRADE_DIRECTION_RESTRICT_ENABLED=false
|
TRADE_DIRECTION_RESTRICT_ENABLED=false
|
||||||
TRADE_DIRECTION=both
|
TRADE_DIRECTION=both
|
||||||
# 币种白名单(默认 false=全币种可手输;true 时关键位/下单/策略仅下拉选择)
|
# 币种白名单(默认 false=全币种可手输;true 时关键位/下单/策略仅下拉选择)
|
||||||
TRADE_SYMBOL_RESTRICT_ENABLED=false
|
TRADE_SYMBOL_RESTRICT_ENABLED=false
|
||||||
TRADE_SYMBOL_WHITELIST=BTC,ETH
|
TRADE_SYMBOL_WHITELIST=BTC,ETH
|
||||||
# 每天起始基数(U)
|
# 每天起始基数(U)
|
||||||
DAILY_START_CAPITAL=30
|
DAILY_START_CAPITAL=30
|
||||||
# 日内回撤后基数(U)
|
# 日内回撤后基数(U)
|
||||||
DAILY_LOSS_CAPITAL=20
|
DAILY_LOSS_CAPITAL=20
|
||||||
# 日内盈利后基数(U)
|
# 日内盈利后基数(U)
|
||||||
DAILY_PROFIT_CAPITAL=50
|
DAILY_PROFIT_CAPITAL=50
|
||||||
# BTC 默认杠杆倍数
|
# BTC 默认杠杆倍数
|
||||||
BTC_LEVERAGE=10
|
BTC_LEVERAGE=10
|
||||||
# 山寨币默认杠杆倍数
|
# 山寨币默认杠杆倍数
|
||||||
ALT_LEVERAGE=5
|
ALT_LEVERAGE=5
|
||||||
# 交易日重置小时(北京时间)
|
# 交易日重置小时(北京时间)
|
||||||
TRADING_DAY_RESET_HOUR=8
|
TRADING_DAY_RESET_HOUR=8
|
||||||
# 整点前禁止新开仓:true=启用(默认),false=关闭(仍可保留 8 点作为交易日划分)
|
# 整点前禁止新开仓:true=启用(默认),false=关闭(仍可保留 8 点作为交易日划分)
|
||||||
TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
|
TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
|
||||||
|
|
||||||
# 是否开启 OKX 实盘下单(false=只做本地流程,true=真实下单)
|
# 是否开启 OKX 实盘下单(false=只做本地流程,true=真实下单)
|
||||||
LIVE_TRADING_ENABLED=true
|
LIVE_TRADING_ENABLED=false
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# OKX 账户 API(永续+期权共用;仅服务器 .env 手改,前端不展示)
|
# OKX 账户 API(永续+期权共用;仅服务器 .env 手改,前端不展示)
|
||||||
# 新机保持为空;填真钥后 pm2 restart --update-env(含子代理)
|
# 新机保持为空;填真钥后 pm2 restart --update-env(含子代理)
|
||||||
# 旧键 OKX_OPTIONS_API_* 已废弃:若 OKX_API_* 为空,启动时会从 OPTIONS 键回填
|
# 旧键 OKX_OPTIONS_API_* 已废弃:若 OKX_API_* 为空,启动时会从 OPTIONS 键回填
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
OKX_API_KEY=
|
OKX_API_KEY=
|
||||||
OKX_API_SECRET=
|
OKX_API_SECRET=
|
||||||
OKX_API_PASSPHRASE=
|
OKX_API_PASSPHRASE=
|
||||||
# 保证金模式:cross=全仓,isolated=逐仓
|
# 保证金模式:cross=全仓,isolated=逐仓
|
||||||
OKX_TD_MODE=cross
|
OKX_TD_MODE=cross
|
||||||
# 持仓模式:hedge=双向持仓,net=单向净持仓
|
# 持仓模式:hedge=双向持仓,net=单向净持仓
|
||||||
OKX_POS_MODE=hedge
|
OKX_POS_MODE=hedge
|
||||||
# 仓位查询 instType(OKX)
|
# 仓位查询 instType(OKX)
|
||||||
OKX_POSITION_INST_TYPE=SWAP
|
OKX_POSITION_INST_TYPE=SWAP
|
||||||
# 从 OKX 历史仓位同步已实现盈亏(北京时间起点,空=近 90 天 0 点起)
|
# 从 OKX 历史仓位同步已实现盈亏(北京时间起点,空=近 90 天 0 点起)
|
||||||
# EXCHANGE_POSITION_SYNC_FROM_BJ=2026-01-01
|
# EXCHANGE_POSITION_SYNC_FROM_BJ=2026-01-01
|
||||||
# 单次拉取历史仓位条数上限(OKX 每页最多 100,程序会分页)
|
# 单次拉取历史仓位条数上限(OKX 每页最多 100,程序会分页)
|
||||||
# EXCHANGE_POSITION_HISTORY_LIMIT=200
|
# EXCHANGE_POSITION_HISTORY_LIMIT=200
|
||||||
# 页面与浏览器标签展示的交易所名称(多环境区分时可改成例如 OKX·测试网)
|
# 页面与浏览器标签展示的交易所名称(多环境区分时可改成例如 OKX·测试网)
|
||||||
EXCHANGE_DISPLAY_NAME=CoinOX
|
EXCHANGE_DISPLAY_NAME=CoinOX
|
||||||
# 企业微信推送里展示的账户备注
|
# 企业微信推送里展示的账户备注
|
||||||
# OKX_ACCOUNT_LABEL=
|
# OKX_ACCOUNT_LABEL=
|
||||||
# 顶栏是否显示 USDT 资金/交易账户(热更);false 时总资金仅计期权 USDC 侧
|
# 顶栏是否显示 USDT 资金/交易账户(热更);false 时总资金仅计期权 USDC 侧
|
||||||
OKX_SHOW_PERP_FUNDS=true
|
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=false
|
||||||
# 以下 OKX_OPTIONS_API_* 已废弃,请勿再配置(仅兼容旧部署回填)
|
# 以下 OKX_OPTIONS_API_* 已废弃,请勿再配置(仅兼容旧部署回填)
|
||||||
# OKX_OPTIONS_API_KEY=
|
# OKX_OPTIONS_API_KEY=
|
||||||
# OKX_OPTIONS_API_SECRET=
|
# OKX_OPTIONS_API_SECRET=
|
||||||
# OKX_OPTIONS_API_PASSPHRASE=
|
# OKX_OPTIONS_API_PASSPHRASE=
|
||||||
OKX_OPTIONS_ACCOUNT_LABEL=账户·期权
|
OKX_OPTIONS_ACCOUNT_LABEL=账户·期权
|
||||||
# 单笔期权本位: coin(默认,币本位+USDT买币桥) | usdc(权利金USDC;对冲仍仅USDC)
|
# 单笔期权本位: coin(默认,币本位+USDT买币桥) | usdc(权利金USDC;对冲仍仅USDC)
|
||||||
OKX_OPTIONS_MARGIN_MODE=coin
|
OKX_OPTIONS_MARGIN_MODE=coin
|
||||||
OKX_OPTIONS_TRADE_BUDGET_USDC=10
|
OKX_OPTIONS_TRADE_BUDGET_USDC=10
|
||||||
OKX_OPTIONS_BUDGET_BUFFER=0.95
|
OKX_OPTIONS_BUDGET_BUFFER=0.95
|
||||||
# 币本位:按交易户USDT×缓冲复利;上限开关默认关(靠人工转走)
|
# 币本位:按交易户USDT×缓冲复利;上限开关默认关(靠人工转走)
|
||||||
OKX_OPTIONS_COIN_COMPOUND=true
|
OKX_OPTIONS_COIN_COMPOUND=true
|
||||||
OKX_OPTIONS_COIN_BUDGET_USDT=10
|
OKX_OPTIONS_COIN_BUDGET_USDT=10
|
||||||
OKX_OPTIONS_COIN_MAX_USDT_ENABLED=false
|
OKX_OPTIONS_COIN_MAX_USDT_ENABLED=false
|
||||||
OKX_OPTIONS_COIN_MAX_USDT=50
|
OKX_OPTIONS_COIN_MAX_USDT=50
|
||||||
# 现货买入相对权利金缓冲:1.10=多买10%;也可写 0.10。按最大可开张数×权利金×缓冲买币,不全额兑换
|
# 现货买入相对权利金缓冲:1.10=多买10%;也可写 0.10。按最大可开张数×权利金×缓冲买币,不全额兑换
|
||||||
OKX_OPTIONS_COIN_SPOT_BUY_BUFFER=1.10
|
OKX_OPTIONS_COIN_SPOT_BUY_BUFFER=1.10
|
||||||
# 全仓复利:开启时隐藏单笔预算且不可用打满;关闭后恢复单笔预算
|
# 全仓复利:开启时隐藏单笔预算且不可用打满;关闭后恢复单笔预算
|
||||||
OKX_OPTIONS_COMPOUND_FULL_ENABLED=true
|
OKX_OPTIONS_COMPOUND_FULL_ENABLED=true
|
||||||
OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED=false
|
OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED=false
|
||||||
OKX_OPTIONS_COMPOUND_FULL_CAP_USDC=300
|
OKX_OPTIONS_COMPOUND_FULL_CAP_USDC=300
|
||||||
# 交易模式三选一(热更):options=单独期权 / perp_options=永期对冲 / options_options=期期对冲
|
# 交易模式三选一(热更):options=单独期权 / perp_options=永期对冲 / options_options=期期对冲
|
||||||
# 选单独期权时隐藏对冲导航与对冲配置;选对冲时不可单独开期权,仓位按「对冲组数上限」
|
# 选单独期权时隐藏对冲导航与对冲配置;选对冲时不可单独开期权,仓位按「对冲组数上限」
|
||||||
OKX_TRADE_MODE=perp_options
|
OKX_TRADE_MODE=perp_options
|
||||||
# 仅单独期权模式:期权同时持仓上限(笔);0=不限制;同合约加仓不占新笔数;热更
|
# 仅单独期权模式:期权同时持仓上限(笔);0=不限制;同合约加仓不占新笔数;热更
|
||||||
OKX_OPTIONS_MAX_ACTIVE_POSITIONS=0
|
OKX_OPTIONS_MAX_ACTIVE_POSITIONS=0
|
||||||
OKX_OPTIONS_DEFAULT_UNDERLY=ETH
|
OKX_OPTIONS_DEFAULT_UNDERLY=ETH
|
||||||
# 期权链仅显示卖一深度≥1张的合约(估算卖一/无深度不显示);false 则显示全部
|
# 期权链仅显示卖一深度≥1张的合约(估算卖一/无深度不显示);false 则显示全部
|
||||||
OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED=true
|
OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED=true
|
||||||
OKX_OPTIONS_MAX_DTE_DAYS=2
|
OKX_OPTIONS_MAX_DTE_DAYS=2
|
||||||
OKX_OPTIONS_CHAIN_MAX_DTE_DAYS=14
|
OKX_OPTIONS_CHAIN_MAX_DTE_DAYS=14
|
||||||
OKX_OPTIONS_ITM_MAX_DIST_USD=30
|
OKX_OPTIONS_ITM_MAX_DIST_USD=30
|
||||||
OKX_OPTIONS_PROFIT_ALERT_RATIO=1.0
|
OKX_OPTIONS_PROFIT_ALERT_RATIO=1.0
|
||||||
OKX_OPTIONS_POLL_SECONDS=15
|
OKX_OPTIONS_POLL_SECONDS=15
|
||||||
OKX_OPTIONS_TD_MODE=isolated
|
OKX_OPTIONS_TD_MODE=isolated
|
||||||
OKX_OPTIONS_ALLOW_MARKET_CLOSE=false
|
OKX_OPTIONS_ALLOW_MARKET_CLOSE=false
|
||||||
# 目标平仓门控(目标触达后自动平才校验;比较口径均为 USDT 估值)
|
# 目标平仓门控(目标触达后自动平才校验;比较口径均为 USDT 估值)
|
||||||
OKX_OPTIONS_CLOSE_GATE_MODE=premium
|
OKX_OPTIONS_CLOSE_GATE_MODE=premium
|
||||||
# 全局倍数可选;留空则按本位用下方 COIN/USDC
|
# 全局倍数可选;留空则按本位用下方 COIN/USDC
|
||||||
OKX_OPTIONS_CLOSE_RECYCLE_MULT=
|
OKX_OPTIONS_CLOSE_RECYCLE_MULT=
|
||||||
OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN=1.05
|
OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN=1.05
|
||||||
OKX_OPTIONS_CLOSE_RECYCLE_MULT_USDC=2
|
OKX_OPTIONS_CLOSE_RECYCLE_MULT_USDC=2
|
||||||
OKX_OPTIONS_CLOSE_NET_PNL_MIN_U=0
|
OKX_OPTIONS_CLOSE_NET_PNL_MIN_U=0
|
||||||
OKX_OPTIONS_CLOSE_HOLD_SECONDS=120
|
OKX_OPTIONS_CLOSE_HOLD_SECONDS=120
|
||||||
# 对冲买期权等成交超时(秒);超时撤未成交部分,未完全成交则开仓失败
|
# 对冲买期权等成交超时(秒);超时撤未成交部分,未完全成交则开仓失败
|
||||||
OKX_OPTIONS_OPEN_FILL_TIMEOUT_SEC=12
|
OKX_OPTIONS_OPEN_FILL_TIMEOUT_SEC=12
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 对冲计划(仅 OKX;由 OKX_TRADE_MODE 控制是否启用;详见 docs/对冲计划开发方案.md)
|
# 对冲计划(仅 OKX;由 OKX_TRADE_MODE 控制是否启用;详见 docs/对冲计划开发方案.md)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 以下三项已由 OKX_TRADE_MODE 取代,保留兼容旧部署(未配置 TRADE_MODE 时仍可读)
|
# 以下三项已由 OKX_TRADE_MODE 取代,保留兼容旧部署(未配置 TRADE_MODE 时仍可读)
|
||||||
HEDGE_PLAN_ENABLED=false
|
HEDGE_PLAN_ENABLED=false
|
||||||
HEDGE_PLAN_SHOW_PERP_OPTIONS=true
|
HEDGE_PLAN_SHOW_PERP_OPTIONS=true
|
||||||
HEDGE_PLAN_SHOW_OPTIONS_OPTIONS=true
|
HEDGE_PLAN_SHOW_OPTIONS_OPTIONS=true
|
||||||
HEDGE_PLAN_LIVE_ORDER=false
|
HEDGE_PLAN_LIVE_ORDER=false
|
||||||
# 永期子模式:true=以期权为主;false=保险模式(页面标题前标识,不可页内切换)
|
# 永期子模式:true=以期权为主;false=保险模式(页面标题前标识,不可页内切换)
|
||||||
HEDGE_PLAN_OPTION_PRIMARY=true
|
HEDGE_PLAN_OPTION_PRIMARY=true
|
||||||
HEDGE_PLAN_OPEN_ORDER=options_first
|
HEDGE_PLAN_OPEN_ORDER=options_first
|
||||||
HEDGE_PLAN_ON_PERP_SL_CLOSE_OPTIONS=true
|
HEDGE_PLAN_ON_PERP_SL_CLOSE_OPTIONS=true
|
||||||
HEDGE_PLAN_ON_PERP_TP_CLOSE_OPTIONS=false
|
HEDGE_PLAN_ON_PERP_TP_CLOSE_OPTIONS=false
|
||||||
HEDGE_PLAN_OO_CLOSE_WINNER_ONLY=true
|
HEDGE_PLAN_OO_CLOSE_WINNER_ONLY=true
|
||||||
# 方案C:期期页面显示「平仓模式」(到期平/全平);关则固定到期平.默认开启,页面默认选全平
|
# 方案C:期期页面显示「平仓模式」(到期平/全平);关则固定到期平.默认开启,页面默认选全平
|
||||||
HEDGE_PLAN_OO_CLOSE_MODE_ENABLED=true
|
HEDGE_PLAN_OO_CLOSE_MODE_ENABLED=true
|
||||||
# 期期「做多/做空」拆分口径:budget=按权利金预算(默认);sheets=先算同张数总张数(2n)再按比例拆
|
# 期期「做多/做空」拆分口径:budget=按权利金预算(默认);sheets=先算同张数总张数(2n)再按比例拆
|
||||||
HEDGE_PLAN_OO_BIAS_SPLIT_BY=budget
|
HEDGE_PLAN_OO_BIAS_SPLIT_BY=budget
|
||||||
# 期期「做多/做空」主腿占比(0~1,默认 0.7=7:3);做多主腿=Call,做空主腿=Put
|
# 期期「做多/做空」主腿占比(0~1,默认 0.7=7:3);做多主腿=Call,做空主腿=Put
|
||||||
HEDGE_PLAN_OO_BIAS_RATIO=0.7
|
HEDGE_PLAN_OO_BIAS_RATIO=0.7
|
||||||
# 对冲与单独期权互斥(默认 true):有对冲计划不可单独开期权;有单独期权不可启动对冲;false=可同时开
|
# 对冲与单独期权互斥(默认 true):有对冲计划不可单独开期权;有单独期权不可启动对冲;false=可同时开
|
||||||
HEDGE_PLAN_OPTIONS_MUTUAL_EXCLUSIVE=true
|
HEDGE_PLAN_OPTIONS_MUTUAL_EXCLUSIVE=true
|
||||||
# 半腿失败改手动补开(默认 true):不自动平已成腿,计划挂 partial,页面补开;开启时下方自动平强制无效
|
# 半腿失败改手动补开(默认 true):不自动平已成腿,计划挂 partial,页面补开;开启时下方自动平强制无效
|
||||||
HEDGE_PLAN_MANUAL_COMPLETE_ON_PARTIAL=true
|
HEDGE_PLAN_MANUAL_COMPLETE_ON_PARTIAL=true
|
||||||
# 对冲组数上限(默认 1;opening/active/partial 计入);仅永期/期期模式生效;热更
|
# 对冲组数上限(默认 1;opening/active/partial 计入);仅永期/期期模式生效;热更
|
||||||
MAX_ACTIVE_HEDGE_PLANS=1
|
MAX_ACTIVE_HEDGE_PLANS=1
|
||||||
HEDGE_PLAN_MONITOR_POLL_SECONDS=15
|
HEDGE_PLAN_MONITOR_POLL_SECONDS=15
|
||||||
# 半腿失败自动平期权;若 MANUAL_COMPLETE_ON_PARTIAL=true 则运行时强制无效(建议一并写成 false)
|
# 半腿失败自动平期权;若 MANUAL_COMPLETE_ON_PARTIAL=true 则运行时强制无效(建议一并写成 false)
|
||||||
HEDGE_PLAN_PARTIAL_AUTO_CLOSE_OPTION=false
|
HEDGE_PLAN_PARTIAL_AUTO_CLOSE_OPTION=false
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 关键位程序自动下单(与 POSITION_SIZING_MODE 联动,修改后须重启 PM2)
|
# 关键位程序自动下单(与 POSITION_SIZING_MODE 联动,修改后须重启 PM2)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 默认 false = 关闭所有关键位程序自动单(箱体/收敛/斐波/假突破/触价)
|
# 默认 false = 关闭所有关键位程序自动单(箱体/收敛/斐波/假突破/触价)
|
||||||
#
|
#
|
||||||
# POSITION_SIZING_MODE=risk(以损定仓)
|
# POSITION_SIZING_MODE=risk(以损定仓)
|
||||||
# false → 不执行任何关键位自动单;支撑/阻力提醒,人工下单,顺势加仓不受影响
|
# false → 不执行任何关键位自动单;支撑/阻力提醒,人工下单,顺势加仓不受影响
|
||||||
# true → 允许关键位全套自动(含触价)
|
# true → 允许关键位全套自动(含触价)
|
||||||
#
|
#
|
||||||
# POSITION_SIZING_MODE=full_margin(全仓杠杆,须无仓切换)
|
# POSITION_SIZING_MODE=full_margin(全仓杠杆,须无仓切换)
|
||||||
# false → 不执行触价自动单
|
# false → 不执行触价自动单
|
||||||
# true → 仅回调/突破触价可程序自动开仓;箱体/斐波等仍禁止
|
# true → 仅回调/突破触价可程序自动开仓;箱体/斐波等仍禁止
|
||||||
#
|
#
|
||||||
# 顺势加仓,趋势回调不受本开关控制;全仓模式下策略自动仍禁止.
|
# 顺势加仓,趋势回调不受本开关控制;全仓模式下策略自动仍禁止.
|
||||||
KEY_AUTO_ORDER_ENABLED=false
|
KEY_AUTO_ORDER_ENABLED=false
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 关键位门控(页面「关键位监控」规则条与 _key_hard_checks 共用)
|
# 关键位门控(页面「关键位监控」规则条与 _key_hard_checks 共用)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 【周期】门控 K 线周期,如 5m,15m;仅影响关键位硬条件,不改变顶栏分区
|
# 【周期】门控 K 线周期,如 5m,15m;仅影响关键位硬条件,不改变顶栏分区
|
||||||
KLINE_TIMEFRAME=5m
|
KLINE_TIMEFRAME=5m
|
||||||
# OKX 遗留:突破过滤百分比(与 KEY_BREAKOUT_AMP_* 并存,程序仍读取)
|
# OKX 遗留:突破过滤百分比(与 KEY_BREAKOUT_AMP_* 并存,程序仍读取)
|
||||||
KEY_BREAKOUT_LIMIT_PCT=1.5
|
KEY_BREAKOUT_LIMIT_PCT=1.5
|
||||||
# 【确认K】闭合 K 序列中的棒偏移:突破棒默认 -2(倒数第2根),确认棒默认 -1(倒数第1根)
|
# 【确认K】闭合 K 序列中的棒偏移:突破棒默认 -2(倒数第2根),确认棒默认 -1(倒数第1根)
|
||||||
KEY_CONFIRM_BREAKOUT_BAR=-2
|
KEY_CONFIRM_BREAKOUT_BAR=-2
|
||||||
KEY_CONFIRM_BAR=-1
|
KEY_CONFIRM_BAR=-1
|
||||||
# 【量能】突破棒成交量 > 前 N 根均量 × 倍数(默认 N=20,倍数=1.3 即放大 30%)
|
# 【量能】突破棒成交量 > 前 N 根均量 × 倍数(默认 N=20,倍数=1.3 即放大 30%)
|
||||||
KEY_VOLUME_MA_BARS=20
|
KEY_VOLUME_MA_BARS=20
|
||||||
KEY_VOLUME_RATIO_MIN=1.3
|
KEY_VOLUME_RATIO_MIN=1.3
|
||||||
# 【箱体/收敛】突破K收盘越过关键位(占该侧价格%)的下限;无上限(过猛由计划RR过滤)
|
# 【箱体/收敛】突破K收盘越过关键位(占该侧价格%)的下限;无上限(过猛由计划RR过滤)
|
||||||
KEY_BREAKOUT_AMP_MIN_PCT=0.03
|
KEY_BREAKOUT_AMP_MIN_PCT=0.03
|
||||||
# 已不参与门控,可保留配置项兼容旧环境
|
# 已不参与门控,可保留配置项兼容旧环境
|
||||||
KEY_BREAKOUT_AMP_MAX_PCT=0.5
|
KEY_BREAKOUT_AMP_MAX_PCT=0.5
|
||||||
# 【阻力/支撑】突破后微信提醒次数与间隔(分钟)
|
# 【阻力/支撑】突破后微信提醒次数与间隔(分钟)
|
||||||
KEY_ALERT_MAX_TIMES=3
|
KEY_ALERT_MAX_TIMES=3
|
||||||
KEY_ALERT_INTERVAL_MINUTES=5
|
KEY_ALERT_INTERVAL_MINUTES=5
|
||||||
# 【日成交量排名】品种须在该排名前 N 名(添加关键位与运行时门控均校验)
|
# 【日成交量排名】品种须在该排名前 N 名(添加关键位与运行时门控均校验)
|
||||||
KEY_DAILY_VOLUME_RANK_MAX=30
|
KEY_DAILY_VOLUME_RANK_MAX=30
|
||||||
# 【关键位自动开仓盈亏比】按确认K收盘 E 计算,严格大于该值才市价开仓(如 1.5 表示须 >1.5:1)
|
# 【关键位自动开仓盈亏比】按确认K收盘 E 计算,严格大于该值才市价开仓(如 1.5 表示须 >1.5:1)
|
||||||
KEY_AUTO_MIN_PLANNED_RR=1.5
|
KEY_AUTO_MIN_PLANNED_RR=1.5
|
||||||
# 止损:突破 K 极值向外缓冲的百分比(默认 0.5 即 0.5%)
|
# 止损:突破 K 极值向外缓冲的百分比(默认 0.5 即 0.5%)
|
||||||
KEY_STOP_OUTSIDE_BREAKOUT_PCT=0.5
|
KEY_STOP_OUTSIDE_BREAKOUT_PCT=0.5
|
||||||
# 趋势单方案:止损在突破 K 极值外侧的百分比(默认 1 即 1%)
|
# 趋势单方案:止损在突破 K 极值外侧的百分比(默认 1 即 1%)
|
||||||
KEY_TREND_STOP_OUTSIDE_PCT=1
|
KEY_TREND_STOP_OUTSIDE_PCT=1
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 交易执行 / 人工风控(页面「实盘下单」)
|
# 交易执行 / 人工风控(页面「实盘下单」)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 【最大同时持仓】active 订单数达到该值后禁止人工与关键位自动再加仓(默认 1=单仓)
|
# 【最大同时持仓】active 订单数达到该值后禁止人工与关键位自动再加仓(默认 1=单仓)
|
||||||
MAX_ACTIVE_POSITIONS=1
|
MAX_ACTIVE_POSITIONS=1
|
||||||
# 【人工下单最低盈亏比】按当前价与 SL/TP 计算,低于该值前后端均拒绝(默认 1.4,即须 >=1.4:1)
|
# 【人工下单最低盈亏比】按当前价与 SL/TP 计算,低于该值前后端均拒绝(默认 1.4,即须 >=1.4:1)
|
||||||
MANUAL_MIN_PLANNED_RR=1.4
|
MANUAL_MIN_PLANNED_RR=1.4
|
||||||
# 【关键位连开计仓】true=已有持仓时关键位自动单仍按「无仓时」资金快照算保证金基数
|
# 【关键位连开计仓】true=已有持仓时关键位自动单仍按「无仓时」资金快照算保证金基数
|
||||||
KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT=true
|
KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT=true
|
||||||
# 【单日开仓 AI 提醒】本交易日开仓达到该次数时推送企业微信 AI 克制提醒(不拦单)
|
# 【单日开仓 AI 提醒】本交易日开仓达到该次数时推送企业微信 AI 克制提醒(不拦单)
|
||||||
DAILY_OPEN_ALERT_THRESHOLD=5
|
DAILY_OPEN_ALERT_THRESHOLD=5
|
||||||
# 【单日开仓硬上限】本交易日开仓次数>=该值后禁止一切新开仓直至下一交易日(北京时间 TRADING_DAY_RESET_HOUR 切日);0=不启用
|
# 【单日开仓硬上限】本交易日开仓次数>=该值后禁止一切新开仓直至下一交易日(北京时间 TRADING_DAY_RESET_HOUR 切日);0=不启用
|
||||||
DAILY_OPEN_HARD_LIMIT=0
|
DAILY_OPEN_HARD_LIMIT=0
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 账户冷静期 / 日冻结风控(手动平仓,外部平仓,复盘情绪标签)
|
# 账户冷静期 / 日冻结风控(手动平仓,外部平仓,复盘情绪标签)
|
||||||
# 详见 docs/account-risk-cooldown.md
|
# 详见 docs/account-risk-cooldown.md
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
RISK_CONTROL_ENABLED=true
|
RISK_CONTROL_ENABLED=true
|
||||||
RISK_COOLING_HOURS_MANUAL=4
|
RISK_COOLING_HOURS_MANUAL=4
|
||||||
RISK_COOLING_HOURS_MANUAL_JOURNAL=1
|
RISK_COOLING_HOURS_MANUAL_JOURNAL=1
|
||||||
RISK_MANUAL_CLOSE_DAILY_LIMIT=2
|
RISK_MANUAL_CLOSE_DAILY_LIMIT=2
|
||||||
# 日亏损次数上限:平仓盈亏<0 计1次;达限当日冻结开仓;0=不启用
|
# 日亏损次数上限:平仓盈亏<0 计1次;达限当日冻结开仓;0=不启用
|
||||||
RISK_DAILY_LOSS_LIMIT=2
|
RISK_DAILY_LOSS_LIMIT=2
|
||||||
RISK_MOOD_ISSUES_DAILY_FREEZE=true
|
RISK_MOOD_ISSUES_DAILY_FREEZE=true
|
||||||
|
|
||||||
# 资金与仓位刷新周期(秒)
|
# 资金与仓位刷新周期(秒)
|
||||||
BALANCE_REFRESH_SECONDS=60
|
BALANCE_REFRESH_SECONDS=60
|
||||||
# 前端价格快照轮询(秒)
|
# 前端价格快照轮询(秒)
|
||||||
PRICE_REFRESH_SECONDS=5
|
PRICE_REFRESH_SECONDS=5
|
||||||
# 后台监控轮询周期(秒)
|
# 后台监控轮询周期(秒)
|
||||||
MONITOR_POLL_SECONDS=3
|
MONITOR_POLL_SECONDS=3
|
||||||
# 移动保本同步交易所止盈止损的最小间隔(秒),避免频繁撤挂叠单
|
# 移动保本同步交易所止盈止损的最小间隔(秒),避免频繁撤挂叠单
|
||||||
BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC=60
|
BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC=60
|
||||||
# 重启后多少秒内不做「外部平仓」同步(避免 API 未就绪误判)
|
# 重启后多少秒内不做「外部平仓」同步(避免 API 未就绪误判)
|
||||||
RECONCILE_STARTUP_GRACE_SEC=90
|
RECONCILE_STARTUP_GRACE_SEC=90
|
||||||
# 连续多少次轮询确认交易所空仓后,才记为外部平仓(默认 3 次 ≈ 9 秒)
|
# 连续多少次轮询确认交易所空仓后,才记为外部平仓(默认 3 次 ≈ 9 秒)
|
||||||
RECONCILE_FLAT_CONFIRM_POLLS=3
|
RECONCILE_FLAT_CONFIRM_POLLS=3
|
||||||
# 使用可用资金时的缓冲比例(如0.98代表用98%)
|
# 使用可用资金时的缓冲比例(如0.98代表用98%)
|
||||||
FULL_MARGIN_BUFFER_RATIO=0.98
|
FULL_MARGIN_BUFFER_RATIO=0.98
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
|
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
AUTO_TRANSFER_ENABLED=false
|
AUTO_TRANSFER_ENABLED=false
|
||||||
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
|
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
|
||||||
AUTO_TRANSFER_AMOUNT=30
|
AUTO_TRANSFER_AMOUNT=30
|
||||||
AUTO_TRANSFER_FROM=funding
|
AUTO_TRANSFER_FROM=funding
|
||||||
AUTO_TRANSFER_TO=swap
|
AUTO_TRANSFER_TO=swap
|
||||||
TRANSFER_CCY=USDT
|
TRANSFER_CCY=USDT
|
||||||
# 北京时间该整点小时内尝试;账簿按 UTC 自然日去重
|
# 北京时间该整点小时内尝试;账簿按 UTC 自然日去重
|
||||||
AUTO_TRANSFER_BJ_HOUR=8
|
AUTO_TRANSFER_BJ_HOUR=8
|
||||||
# 强制清仓整点(北京时间,默认 0=凌晨00点)
|
# 强制清仓整点(北京时间,默认 0=凌晨00点)
|
||||||
FORCE_CLOSE_BJ_HOUR=0
|
FORCE_CLOSE_BJ_HOUR=0
|
||||||
# 是否启用强制清仓(默认关闭,true 才会在整点执行)
|
# 是否启用强制清仓(默认关闭,true 才会在整点执行)
|
||||||
FORCE_CLOSE_ENABLED=false
|
FORCE_CLOSE_ENABLED=false
|
||||||
# 强制清仓执行窗口(分钟,默认 5;该窗口内禁止开仓)
|
# 强制清仓执行窗口(分钟,默认 5;该窗口内禁止开仓)
|
||||||
FORCE_CLOSE_GRACE_MINUTES=5
|
FORCE_CLOSE_GRACE_MINUTES=5
|
||||||
|
|
||||||
# 推送与AI超时(秒)
|
# 推送与AI超时(秒)
|
||||||
WECHAT_TIMEOUT_SECONDS=10
|
WECHAT_TIMEOUT_SECONDS=10
|
||||||
AI_TIMEOUT_SECONDS=120
|
AI_TIMEOUT_SECONDS=120
|
||||||
|
|
||||||
# AI 复盘服务地址(本机 Ollama 默认地址)
|
# AI 复盘服务地址(本机 Ollama 默认地址)
|
||||||
AI_PROVIDER=openai
|
AI_PROVIDER=openai
|
||||||
OPENAI_API_BASE=https://op.bz121.com/v1
|
OPENAI_API_BASE=https://op.bz121.com/v1
|
||||||
OPENAI_API_KEY=你的密钥
|
OPENAI_API_KEY=你的密钥
|
||||||
OPENAI_MODEL=gemma4:e4b
|
OPENAI_MODEL=gemma4:e4b
|
||||||
OLLAMA_API=http://127.0.0.1:11434/api/generate
|
OLLAMA_API=http://127.0.0.1:11434/api/generate
|
||||||
AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||||
|
|
||||||
# OKX 代理(可选,仅本地开发网络受限时用;云服务器部署请留空,直连 OKX 即可)
|
# OKX 代理(可选,仅本地开发网络受限时用;云服务器部署请留空,直连 OKX 即可)
|
||||||
# 1) 先在本机建立隧道(示例):
|
# 1) 先在本机建立隧道(示例):
|
||||||
# ssh -N -D 127.0.0.1:1080 root@你的VPS_IP -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes
|
# ssh -N -D 127.0.0.1:1080 root@你的VPS_IP -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes
|
||||||
# 2) 再启用下面这一行(推荐 socks5h,让远端解析域名):
|
# 2) 再启用下面这一行(推荐 socks5h,让远端解析域名):
|
||||||
# OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080
|
# OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080
|
||||||
#
|
#
|
||||||
# 如你更偏向 HTTP 代理(VPS 上跑 tinyproxy 之类),可用:
|
# 如你更偏向 HTTP 代理(VPS 上跑 tinyproxy 之类),可用:
|
||||||
# OKX_HTTP_PROXY=http://127.0.0.1:3128
|
# OKX_HTTP_PROXY=http://127.0.0.1:3128
|
||||||
# OKX_HTTPS_PROXY=http://127.0.0.1:3128
|
# OKX_HTTPS_PROXY=http://127.0.0.1:3128
|
||||||
|
|
||||||
# 开仓多周期K线图(可选)
|
# 开仓多周期K线图(可选)
|
||||||
# ORDER_CHART_ENABLED=true
|
# ORDER_CHART_ENABLED=true
|
||||||
# ORDER_CHART_TFS=4h,1h,15m,5m
|
# ORDER_CHART_TFS=4h,1h,15m,5m
|
||||||
# ORDER_CHART_LIMIT=100
|
# ORDER_CHART_LIMIT=100
|
||||||
# ORDER_CHART_DIR=static/images/order_charts
|
# ORDER_CHART_DIR=static/images/order_charts
|
||||||
# 详见上文 DAILY_OPEN_ALERT_THRESHOLD / DAILY_OPEN_HARD_LIMIT;说明文档 docs/daily-open-limit.md
|
# 详见上文 DAILY_OPEN_ALERT_THRESHOLD / DAILY_OPEN_HARD_LIMIT;说明文档 docs/daily-open-limit.md
|
||||||
# 以损定仓(按交易账户资金的百分比)
|
# 以损定仓(按交易账户资金的百分比)
|
||||||
# RISK_PERCENT=2
|
# RISK_PERCENT=2
|
||||||
# 移动保本触发(达到多少R触发)与偏移(百分比)
|
# 移动保本触发(达到多少R触发)与偏移(百分比)
|
||||||
# BREAKEVEN_RR_TRIGGER=1.0
|
# BREAKEVEN_RR_TRIGGER=1.0
|
||||||
# 移动保本阶梯(每多少R继续上移一次,默认1R)
|
# 移动保本阶梯(每多少R继续上移一次,默认1R)
|
||||||
# BREAKEVEN_STEP_R=1.0
|
# BREAKEVEN_STEP_R=1.0
|
||||||
# BREAKEVEN_OFFSET_PCT=0.02
|
# BREAKEVEN_OFFSET_PCT=0.02
|
||||||
# 开单风格默认值:trend / swing
|
# 开单风格默认值:trend / swing
|
||||||
# DEFAULT_TRADE_STYLE=trend
|
# DEFAULT_TRADE_STYLE=trend
|
||||||
|
|
||||||
APP_TIMEZONE=Asia/Shanghai
|
APP_TIMEZONE=Asia/Shanghai
|
||||||
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日;开仓整点限制见 TRADING_DAY_RESET_OPEN_GUARD_ENABLED
|
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日;开仓整点限制见 TRADING_DAY_RESET_OPEN_GUARD_ENABLED
|
||||||
|
|
||||||
# --- CoinOX 模拟资金 ---
|
# --- CoinOX 模拟资金 ---
|
||||||
# sim=本地模拟资金撮合; live=实盘账户
|
# sim=本地模拟资金撮合; live=实盘账户
|
||||||
SIM_DEFAULT_MODE=sim
|
# 默认模拟盘(本地钱包); 切实盘请改为 live 并配置 API
|
||||||
SIM_INITIAL_EQUITY_USDT=10000
|
SIM_DEFAULT_MODE=sim
|
||||||
SIM_INITIAL_USDC=0
|
SIM_INITIAL_EQUITY_USDT=10000
|
||||||
SIM_FEE_RATE=0.0005
|
SIM_INITIAL_USDC=0
|
||||||
|
SIM_FEE_RATE=0.0005
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ OKX **币本位期权** + **U 本位永续对冲** 独立实例。
|
|||||||
| 单独期权 | 仅币本位(USDT→买币→开期权) |
|
| 单独期权 | 仅币本位(USDT→买币→开期权) |
|
||||||
| 永期对冲 | 币本位期权腿 + USDT 永续腿 |
|
| 永期对冲 | 币本位期权腿 + USDT 永续腿 |
|
||||||
| 期期对冲 | 双腿币本位期权 |
|
| 期期对冲 | 双腿币本位期权 |
|
||||||
| 模拟资金 | 默认 `sim`,本地钱包撮合 |
|
| 模拟资金 | **默认 `sim`**,本地钱包撮合 |
|
||||||
|
|
||||||
| 不提供 | 说明 |
|
| 不提供 | 说明 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
@@ -19,29 +19,48 @@ OKX **币本位期权** + **U 本位永续对冲** 独立实例。
|
|||||||
| 关键位自动开仓 | `/key_monitor` 已重定向 |
|
| 关键位自动开仓 | `/key_monitor` 已重定向 |
|
||||||
| 策略趋势/滚仓 | 未安装 |
|
| 策略趋势/滚仓 | 未安装 |
|
||||||
| USDC 期权 | 已固定币本位 |
|
| USDC 期权 | 已固定币本位 |
|
||||||
|
| Windows 部署 | **仅 Ubuntu 一键部署** |
|
||||||
|
|
||||||
源仓 `crypto_monitor` **只读复制**,不回改源仓。中控二期再做。
|
源仓 `crypto_monitor` **只读复制**,不回改源仓。中控二期再做。
|
||||||
|
|
||||||
## 快速开始
|
## Ubuntu 一键部署(推荐)
|
||||||
|
|
||||||
|
服务器 **root** 执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /path/to/CoinOX # 或 E:\仓库\okx对冲
|
curl -fsSL https://git.bz121.com/dekun/CoinOX/raw/branch/main/deploy/manage.sh | bash
|
||||||
python -m venv .venv
|
```
|
||||||
# Windows: .venv\Scripts\activate
|
|
||||||
|
交互菜单:部署 / 卸载 / 更新 / 仅检测。
|
||||||
|
安装到 `/opt/coinox`,PM2 进程 `coinox`,默认端口 `5010`,**默认模拟盘**。
|
||||||
|
|
||||||
|
详情见 [部署文档.md](./部署文档.md) 与 [deploy/README.md](./deploy/README.md)。
|
||||||
|
|
||||||
|
已安装后管理:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash /opt/coinox/deploy/manage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 本机开发(Ubuntu)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/coinox # 或你的克隆目录
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
copy .env.example .env # 编辑密钥;默认 SIM_DEFAULT_MODE=sim
|
cp -n .env.example .env
|
||||||
python app.py
|
python app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
默认端口见 `.env` 的 `APP_PORT`(模板为 `5010`,避免与旧 OKX 实例冲突)。
|
|
||||||
|
|
||||||
## 环境要点
|
## 环境要点
|
||||||
|
|
||||||
|
- `SIM_DEFAULT_MODE=sim`(默认模拟盘)
|
||||||
- `OKX_OPTIONS_MARGIN_MODE=coin`(固定)
|
- `OKX_OPTIONS_MARGIN_MODE=coin`(固定)
|
||||||
- `OKX_TRADE_MODE=options|perp_options|options_options`
|
- `OKX_TRADE_MODE=options|perp_options|options_options`
|
||||||
- `SIM_DEFAULT_MODE=sim` / `live`
|
|
||||||
- `SIM_INITIAL_EQUITY_USDT=10000`
|
- `SIM_INITIAL_EQUITY_USDT=10000`
|
||||||
|
- `APP_PORT=5010`
|
||||||
|
|
||||||
## 风险
|
## 风险
|
||||||
|
|
||||||
实盘须自行确认 API 权限与资金;默认模拟盘,切 `live` 前请核对。
|
实盘风险自负。默认模拟盘;切 `live` 前请核对 API 权限与资金。
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# CoinOX 环境一键部署(Ubuntu / root /opt)
|
||||||
|
|
||||||
|
在 **`/opt/coinox`** 下以 **root** 部署CoinOX 币本位期权 + U 本位对冲,使用 **PM2** 常驻.
|
||||||
|
|
||||||
|
仓库: [https://git.bz121.com/dekun/CoinOX.git](https://git.bz121.com/dekun/CoinOX.git)
|
||||||
|
|
||||||
|
完整步骤见根目录 **[部署文档.md](../部署文档.md)**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一键部署管理器(推荐)
|
||||||
|
|
||||||
|
新服务器**无需先 clone**,一条命令进入菜单:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.bz121.com/dekun/CoinOX/raw/branch/main/deploy/manage.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
已安装机器:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash /opt/coinox/deploy/manage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 菜单
|
||||||
|
|
||||||
|
| 选项 | 功能 |
|
||||||
|
|------|------|
|
||||||
|
| **1) 一键部署** | 环境识别 + 系统依赖 + Node/PM2 + venv + 密钥 + 启动 `coinox` |
|
||||||
|
| **2) 一键卸载** | 备份 `.env` → 停 PM2 → 移走目录 |
|
||||||
|
| **3) 更新** | 快速更新 / 依赖更新 |
|
||||||
|
| **4) 仅检测** | 识别 OS / Python / Node / PM2 / 项目状态(不安装) |
|
||||||
|
| **0) 退出** | |
|
||||||
|
|
||||||
|
### 部署完成后
|
||||||
|
|
||||||
|
- 访问: `http://<服务器IP>:5010` (端口以 `.env` 的 `APP_PORT` 为准)
|
||||||
|
- 默认入口: `/hedge-plan`(对冲)/ `/options`(期权);默认模拟盘 sim
|
||||||
|
- 登录账号: **admin**(密码见 `.env` 的 `APP_PASSWORD`,首次脚本可写成 `admin123`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 脚本结构
|
||||||
|
|
||||||
|
```
|
||||||
|
deploy/
|
||||||
|
├── manage.sh # 入口(自举 + 菜单)
|
||||||
|
├── lib/
|
||||||
|
│ ├── common.sh # 公共函数 / 环境识别 / 验收
|
||||||
|
│ ├── install.sh # 一键部署
|
||||||
|
│ ├── uninstall.sh # 一键卸载
|
||||||
|
│ ├── update.sh # 更新子菜单
|
||||||
|
│ └── doctor.sh # 仅检测
|
||||||
|
├── setup_env.sh # venv + pip(被 install 调用)
|
||||||
|
└── pull_and_restart.sh # 快速更新
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
- **Ubuntu 22.04 / 24.04**,用户 **root**
|
||||||
|
- 能 `git clone` [coinox](https://git.bz121.com/dekun/CoinOX.git) 到 `/opt/coinox`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 分步安装(仍可用)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt
|
||||||
|
git clone https://git.bz121.com/dekun/CoinOX.git coinox
|
||||||
|
cd /opt/coinox
|
||||||
|
bash deploy/setup_env.sh --install-system-deps
|
||||||
|
pm2 start ecosystem.config.cjs
|
||||||
|
pm2 save && pm2 startup
|
||||||
|
```
|
||||||
|
|
||||||
|
`setup_env.sh` 常用参数:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash deploy/setup_env.sh --recreate-venv # 重建虚拟环境
|
||||||
|
bash deploy/setup_env.sh --skip-pm2 # 不尝试安装 pm2
|
||||||
|
bash deploy/setup_env.sh --skip-env-copy # 不复制 .env.example
|
||||||
|
```
|
||||||
|
|
||||||
|
若在 Windows 编辑过脚本后报 `pipefail` 错误,先转 LF:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sed -i 's/\r$//' deploy/manage.sh deploy/lib/*.sh deploy/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 环境变量(可选)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
INSTALL_ROOT=/opt/coinox
|
||||||
|
GIT_URL=https://git.bz121.com/dekun/CoinOX.git
|
||||||
|
GIT_BRANCH=main
|
||||||
|
BACKUP_ROOT=/root/backups
|
||||||
|
```
|
||||||
@@ -0,0 +1,513 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy/lib/common.sh — CoinOX 部署公共函数
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
|
set -o pipefail
|
||||||
|
fi
|
||||||
|
|
||||||
|
INSTALL_ROOT="${INSTALL_ROOT:-/opt/coinox}"
|
||||||
|
GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/CoinOX.git}"
|
||||||
|
GIT_BRANCH="${GIT_BRANCH:-main}"
|
||||||
|
BACKUP_ROOT="${BACKUP_ROOT:-/root/backups}"
|
||||||
|
TZ_NAME="${CM_TZ:-Asia/Shanghai}"
|
||||||
|
NODE_MAJOR="${NODE_MAJOR:-20}"
|
||||||
|
APP_PORT="${APP_PORT:-5010}"
|
||||||
|
PM2_APP_NAME="${PM2_APP_NAME:-coinox}"
|
||||||
|
|
||||||
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
DEPLOY_DIR="$(cd "${LIB_DIR}/.." && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
|
||||||
|
|
||||||
|
PM2_APPS=(
|
||||||
|
coinox
|
||||||
|
)
|
||||||
|
|
||||||
|
# 历史进程名(仅 stop/delete 时兼容)
|
||||||
|
PM2_APPS_LEGACY=(
|
||||||
|
crypto-monitor-okx
|
||||||
|
crypto-monitor
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_PATHS=(
|
||||||
|
.env
|
||||||
|
)
|
||||||
|
|
||||||
|
log() { printf '[%s] %s\n' "$(TZ="${TZ_NAME}" date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
||||||
|
step() { echo ""; log "==> $*"; }
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "错误: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_root() {
|
||||||
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
|
die "请使用 root 执行(推荐: sudo -i 后运行)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_ubuntu() {
|
||||||
|
if [[ ! -f /etc/os-release ]]; then
|
||||||
|
log "警告: 未检测到 /etc/os-release,跳过 Ubuntu 版本检查"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source /etc/os-release
|
||||||
|
if [[ "${ID:-}" != "ubuntu" ]]; then
|
||||||
|
log "警告: 当前系统为 ${ID:-unknown},官方仅测试 Ubuntu 22.04/24.04"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local ver="${VERSION_ID:-}"
|
||||||
|
if [[ "${ver}" != "22.04" && "${ver}" != "24.04" ]]; then
|
||||||
|
log "警告: Ubuntu ${ver} 未在文档中明确测试,继续执行"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_server_ip() {
|
||||||
|
local ip=""
|
||||||
|
if command -v hostname >/dev/null 2>&1; then
|
||||||
|
ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||||
|
fi
|
||||||
|
if [[ -z "${ip}" ]]; then
|
||||||
|
ip="127.0.0.1"
|
||||||
|
fi
|
||||||
|
echo "${ip}"
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm_yes() {
|
||||||
|
local msg="$1"
|
||||||
|
local ans=""
|
||||||
|
cm_read ans "${msg} [y/N] "
|
||||||
|
[[ "${ans}" == [yY] || "${ans}" == [yY][eE][sS] ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm_uninstall() {
|
||||||
|
local ans=""
|
||||||
|
echo "此操作将停止 CoinOX PM2 进程并移走安装目录."
|
||||||
|
cm_read ans "输入 UNINSTALL 确认卸载: "
|
||||||
|
[[ "${ans}" == "UNINSTALL" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# curl | bash 时 stdin 是管道,须从 /dev/tty 读取用户输入
|
||||||
|
cm_read() {
|
||||||
|
local __var="$1"
|
||||||
|
local __prompt="$2"
|
||||||
|
local __val=""
|
||||||
|
if [[ -r /dev/tty ]]; then
|
||||||
|
IFS= read -r -p "${__prompt}" __val </dev/tty || true
|
||||||
|
else
|
||||||
|
IFS= read -r -p "${__prompt}" __val || true
|
||||||
|
fi
|
||||||
|
printf -v "${__var}" '%s' "${__val}"
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_ready() {
|
||||||
|
[[ -f "${1}/deploy/setup_env.sh" && -f "${1}/deploy/manage.sh" && -f "${1}/app.py" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_repo_root() {
|
||||||
|
if repo_ready "${INSTALL_ROOT}"; then
|
||||||
|
echo "${INSTALL_ROOT}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if repo_ready "${REPO_ROOT}"; then
|
||||||
|
echo "${REPO_ROOT}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_configs_to() {
|
||||||
|
local src_root="$1"
|
||||||
|
local dest="$2"
|
||||||
|
mkdir -p "${dest}"
|
||||||
|
local rel copied=0
|
||||||
|
for rel in "${CONFIG_PATHS[@]}"; do
|
||||||
|
local src="${src_root}/${rel}"
|
||||||
|
if [[ -f "${src}" ]]; then
|
||||||
|
mkdir -p "${dest}/$(dirname "${rel}")"
|
||||||
|
cp -a "${src}" "${dest}/${rel}"
|
||||||
|
log "backup ${rel}"
|
||||||
|
copied=$((copied + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "${copied}" -eq 0 ]]; then
|
||||||
|
log "提示: 未找到可备份的配置文件"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_system_packages() {
|
||||||
|
step "安装系统依赖"
|
||||||
|
if ! command -v apt-get >/dev/null 2>&1; then
|
||||||
|
die "未检测到 apt-get,请手动安装 python3-venv git curl"
|
||||||
|
fi
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y python3 python3-pip python3-venv curl git ca-certificates
|
||||||
|
local pyver=""
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
pyver="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
||||||
|
apt-get install -y "python${pyver}-venv" 2>/dev/null || apt-get install -y python3-venv
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_node_pm2() {
|
||||||
|
step "检查 Node.js 与 PM2"
|
||||||
|
if command -v pm2 >/dev/null 2>&1; then
|
||||||
|
log "PM2 已安装: $(pm2 -v)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ! command -v node >/dev/null 2>&1; then
|
||||||
|
log "安装 Node.js ${NODE_MAJOR}.x ..."
|
||||||
|
curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -
|
||||||
|
apt-get install -y nodejs
|
||||||
|
fi
|
||||||
|
log "安装 PM2 ..."
|
||||||
|
npm install -g pm2
|
||||||
|
log "PM2: $(pm2 -v)"
|
||||||
|
}
|
||||||
|
|
||||||
|
pip_progress_bar_arg() {
|
||||||
|
echo "on"
|
||||||
|
}
|
||||||
|
|
||||||
|
pip_upgrade_tools() {
|
||||||
|
local pip_bin="$1"
|
||||||
|
local bar
|
||||||
|
bar="$(pip_progress_bar_arg)"
|
||||||
|
echo " 升级 pip ..."
|
||||||
|
"${pip_bin}" install -U pip setuptools wheel \
|
||||||
|
--disable-pip-version-check \
|
||||||
|
--progress-bar "${bar}"
|
||||||
|
}
|
||||||
|
|
||||||
|
pip_install_requirements() {
|
||||||
|
local pip_bin="$1"
|
||||||
|
local req_file="$2"
|
||||||
|
local label="${3:-依赖}"
|
||||||
|
local bar
|
||||||
|
bar="$(pip_progress_bar_arg)"
|
||||||
|
echo " 安装${label} (下方为 pip 进度) ..."
|
||||||
|
[[ -f "${req_file}" ]] || die "缺少依赖文件: ${req_file}"
|
||||||
|
"${pip_bin}" install -r "${req_file}" \
|
||||||
|
--disable-pip-version-check \
|
||||||
|
--retries 5 \
|
||||||
|
--progress-bar "${bar}"
|
||||||
|
echo " ${label}安装完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_backup_cron() {
|
||||||
|
step "安装每日备份 cron"
|
||||||
|
local inst="${REPO_ROOT}/scripts/install_backup_cron.sh"
|
||||||
|
if [[ -f "${inst}" ]]; then
|
||||||
|
chmod +x "${inst}"
|
||||||
|
bash "${inst}" || log "警告: cron 安装失败"
|
||||||
|
else
|
||||||
|
log "未找到 scripts/install_backup_cron.sh,跳过"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_backup_cron() {
|
||||||
|
step "移除备份 cron"
|
||||||
|
local tmp removed=0
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
if ! crontab -l 2>/dev/null >"${tmp}"; then
|
||||||
|
rm -f "${tmp}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local filtered
|
||||||
|
filtered="$(grep -vF "backup_data.sh" "${tmp}" || true)"
|
||||||
|
if [[ "${filtered}" != "$(cat "${tmp}")" ]]; then
|
||||||
|
printf '%s\n' "${filtered}" | awk '
|
||||||
|
BEGIN { tz = 0 }
|
||||||
|
/^CRON_TZ=Asia\/Shanghai$/ {
|
||||||
|
if (tz++) next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' | crontab -
|
||||||
|
removed=1
|
||||||
|
fi
|
||||||
|
rm -f "${tmp}"
|
||||||
|
if [[ "${removed}" -eq 1 ]]; then
|
||||||
|
log "已移除 backup_data.sh 相关 cron"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pm2_app_exists() {
|
||||||
|
local name="$1"
|
||||||
|
# pm2 pid 在部分版本对不存在的进程仍返回 0;改用 jlist 精确匹配 name
|
||||||
|
if ! command -v pm2 >/dev/null 2>&1; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
pm2 describe "${name}" 2>/dev/null | grep -q "status"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
pm2 jlist 2>/dev/null | python3 -c '
|
||||||
|
import json,sys
|
||||||
|
name=sys.argv[1]
|
||||||
|
try:
|
||||||
|
apps=json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
sys.exit(0 if any(isinstance(a,dict) and a.get("name")==name for a in (apps or [])) else 1)
|
||||||
|
' "${name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
pm2_stop_project_apps() {
|
||||||
|
if ! command -v pm2 >/dev/null 2>&1; then
|
||||||
|
log "未安装 pm2,跳过"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local name stopped=0
|
||||||
|
for name in "${PM2_APPS[@]}" "${PM2_APPS_LEGACY[@]}"; do
|
||||||
|
if pm2_app_exists "${name}"; then
|
||||||
|
log "pm2 stop ${name}"
|
||||||
|
pm2 stop "${name}" 2>/dev/null || true
|
||||||
|
stopped=$((stopped + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "${stopped}" -eq 0 ]]; then
|
||||||
|
log "未发现本项目 PM2 进程(其它 PM2 不受影响)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pm2_delete_project_apps() {
|
||||||
|
if ! command -v pm2 >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local name deleted=0
|
||||||
|
for name in "${PM2_APPS[@]}" "${PM2_APPS_LEGACY[@]}"; do
|
||||||
|
if pm2_app_exists "${name}"; then
|
||||||
|
log "pm2 delete ${name}"
|
||||||
|
pm2 delete "${name}" 2>/dev/null || true
|
||||||
|
deleted=$((deleted + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "${deleted}" -gt 0 ]]; then
|
||||||
|
pm2 save 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pm2_start_app() {
|
||||||
|
step "启动 PM2: ${PM2_APP_NAME}"
|
||||||
|
cd "${REPO_ROOT}"
|
||||||
|
[[ -f "${REPO_ROOT}/ecosystem.config.cjs" ]] || die "缺少 ecosystem.config.cjs"
|
||||||
|
[[ -x "${REPO_ROOT}/.venv/bin/python" ]] || die "缺少 .venv,请先运行 setup_env.sh"
|
||||||
|
if pm2_app_exists "${PM2_APP_NAME}"; then
|
||||||
|
log "已存在,执行 restart --update-env"
|
||||||
|
if ! pm2 restart "${PM2_APP_NAME}" --update-env; then
|
||||||
|
log "restart 失败,改为 start ecosystem.config.cjs"
|
||||||
|
pm2 start ecosystem.config.cjs
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "首次启动: pm2 start ecosystem.config.cjs"
|
||||||
|
pm2 start ecosystem.config.cjs
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pm2_save_startup() {
|
||||||
|
step "PM2 save & startup"
|
||||||
|
pm2 save 2>/dev/null || true
|
||||||
|
if pm2 startup systemd -u root --hp /root 2>/dev/null | grep -q "sudo"; then
|
||||||
|
pm2 startup systemd -u root --hp /root 2>/dev/null | grep "^sudo" | bash || true
|
||||||
|
else
|
||||||
|
pm2 startup 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_deployed() {
|
||||||
|
local root="$1"
|
||||||
|
[[ -x "${root}/.venv/bin/python" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
read_app_port() {
|
||||||
|
local env_file="${REPO_ROOT}/.env"
|
||||||
|
local port="${APP_PORT}"
|
||||||
|
if [[ -f "${env_file}" ]]; then
|
||||||
|
local line
|
||||||
|
line="$(grep -E '^[[:space:]]*APP_PORT=' "${env_file}" | tail -n1 || true)"
|
||||||
|
if [[ -n "${line}" ]]; then
|
||||||
|
port="${line#*=}"
|
||||||
|
port="$(echo "${port}" | tr -d '"' | tr -d "'" | xargs)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "${port:-5010}"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_http() {
|
||||||
|
local url="$1"
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 5 --max-time 8 "${url}" 2>/dev/null || echo "000")"
|
||||||
|
[[ "${code}" == "200" || "${code}" == "302" || "${code}" == "301" || "${code}" == "401" || "${code}" == "403" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_deployment() {
|
||||||
|
local ip="${1:-$(detect_server_ip)}"
|
||||||
|
local port
|
||||||
|
local ok=1
|
||||||
|
local attempt max_attempts=20
|
||||||
|
port="$(read_app_port)"
|
||||||
|
|
||||||
|
step "部署验收"
|
||||||
|
if command -v pm2 >/dev/null 2>&1 && pm2_app_exists "${PM2_APP_NAME}"; then
|
||||||
|
echo " [✓] PM2 ${PM2_APP_NAME} 已注册"
|
||||||
|
else
|
||||||
|
echo " [✗] PM2 缺少进程 ${PM2_APP_NAME}"
|
||||||
|
ok=0
|
||||||
|
pm2 list 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Flask 冷启动常需数秒;立刻 curl 易误报
|
||||||
|
echo " ... 等待页面就绪 (最多约 ${max_attempts}s,端口 ${port})"
|
||||||
|
local http_ok=0
|
||||||
|
for attempt in $(seq 1 "${max_attempts}"); do
|
||||||
|
if check_http "http://127.0.0.1:${port}/"; then
|
||||||
|
http_ok=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# 进程已挂掉则提前失败
|
||||||
|
if command -v pm2 >/dev/null 2>&1 && ! pm2_app_exists "${PM2_APP_NAME}"; then
|
||||||
|
echo " [✗] 等待期间 PM2 进程消失"
|
||||||
|
ok=0
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
local status=""
|
||||||
|
status="$(pm2 jlist 2>/dev/null | python3 -c '
|
||||||
|
import json,sys
|
||||||
|
name="'"${PM2_APP_NAME}"'"
|
||||||
|
try:
|
||||||
|
apps=json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
print(""); sys.exit(0)
|
||||||
|
for a in apps or []:
|
||||||
|
if isinstance(a,dict) and a.get("name")==name:
|
||||||
|
print((a.get("pm2_env") or {}).get("status") or "")
|
||||||
|
break
|
||||||
|
' 2>/dev/null || true)"
|
||||||
|
if [[ "${status}" == "errored" || "${status}" == "stopped" ]]; then
|
||||||
|
echo " [✗] PM2 状态为 ${status}"
|
||||||
|
ok=0
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${http_ok}" -eq 1 ]]; then
|
||||||
|
echo " [✓] 页面可访问 (http://127.0.0.1:${port}/)"
|
||||||
|
else
|
||||||
|
echo " [✗] 页面不可访问 (http://127.0.0.1:${port}/)"
|
||||||
|
ok=0
|
||||||
|
echo " 排查: pm2 logs ${PM2_APP_NAME} --lines 50"
|
||||||
|
echo " 确认端口: grep APP_PORT ${REPO_ROOT}/.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${ok}" -eq 1 ]]; then
|
||||||
|
echo ""
|
||||||
|
log "验收通过: 进程在线 + 页面可打开"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
log "验收未完全通过,可执行: pm2 logs ${PM2_APP_NAME} --lines 30"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print_post_install_guide() {
|
||||||
|
local ip="${1:-$(detect_server_ip)}"
|
||||||
|
local port
|
||||||
|
port="$(read_app_port)"
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
═══════════════════════════════════════════
|
||||||
|
✅ CoinOX 部署完成(默认模拟盘 sim)
|
||||||
|
|
||||||
|
访问: http://${ip}:${port}
|
||||||
|
入口: /hedge-plan(对冲) /options(期权)
|
||||||
|
|
||||||
|
登录账号: admin
|
||||||
|
登录密码: 见 .env 中 APP_PASSWORD(首次多为 admin123)
|
||||||
|
(请尽快在系统设置 / env 配置中修改)
|
||||||
|
|
||||||
|
模式说明:
|
||||||
|
- SIM_DEFAULT_MODE=sim(本地模拟资金,不打真实私有下单)
|
||||||
|
- 实盘: 在页面/env 切 live,并配置 OKX_API_* 与 LIVE_TRADING_ENABLED
|
||||||
|
|
||||||
|
下一步:
|
||||||
|
1. 浏览器打开上方地址
|
||||||
|
2. 模拟盘直接测期权 / 对冲计划
|
||||||
|
3. 需要实盘时再填 API / 代理并切 live
|
||||||
|
|
||||||
|
管理:
|
||||||
|
bash ${INSTALL_ROOT}/deploy/manage.sh
|
||||||
|
仓库:
|
||||||
|
${GIT_URL}
|
||||||
|
═══════════════════════════════════════════
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
doctor_report() {
|
||||||
|
step "环境识别"
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source /etc/os-release
|
||||||
|
echo " OS: ${PRETTY_NAME:-${ID:-unknown} ${VERSION_ID:-}}"
|
||||||
|
else
|
||||||
|
echo " OS: unknown"
|
||||||
|
fi
|
||||||
|
echo " 用户: $(id -un) (uid=$(id -u))"
|
||||||
|
echo " 主机: $(hostname 2>/dev/null || echo '?')"
|
||||||
|
echo " IP: $(detect_server_ip)"
|
||||||
|
echo " 安装根: ${INSTALL_ROOT}"
|
||||||
|
echo " 仓库: ${GIT_URL} (${GIT_BRANCH})"
|
||||||
|
|
||||||
|
step "依赖检测"
|
||||||
|
local item cmd
|
||||||
|
for item in "python3:python3" "git:git" "curl:curl" "node:node" "npm:npm" "pm2:pm2"; do
|
||||||
|
cmd="${item#*:}"
|
||||||
|
if command -v "${cmd}" >/dev/null 2>&1; then
|
||||||
|
case "${cmd}" in
|
||||||
|
python3) echo " [✓] python3: $(python3 --version 2>&1)" ;;
|
||||||
|
node) echo " [✓] node: $(node -v 2>&1)" ;;
|
||||||
|
npm) echo " [✓] npm: $(npm -v 2>&1)" ;;
|
||||||
|
pm2) echo " [✓] pm2: $(pm2 -v 2>&1)" ;;
|
||||||
|
*) echo " [✓] ${cmd}: $(command -v "${cmd}")" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
echo " [✗] ${cmd}: 未安装"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
local ver major minor
|
||||||
|
ver="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
||||||
|
major="${ver%%.*}"
|
||||||
|
minor="${ver#*.}"
|
||||||
|
if [[ "${major}" -gt 3 ]] || [[ "${major}" -eq 3 && "${minor}" -ge 10 ]]; then
|
||||||
|
echo " [✓] Python 版本满足 3.10+ (${ver})"
|
||||||
|
else
|
||||||
|
echo " [✗] Python 需要 3.10+,当前 ${ver}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
step "项目状态"
|
||||||
|
local root=""
|
||||||
|
if root="$(resolve_repo_root 2>/dev/null)"; then
|
||||||
|
echo " [✓] 仓库就绪: ${root}"
|
||||||
|
[[ -f "${root}/requirements.txt" ]] && echo " [✓] requirements.txt" || echo " [✗] requirements.txt"
|
||||||
|
[[ -f "${root}/ecosystem.config.cjs" ]] && echo " [✓] ecosystem.config.cjs" || echo " [✗] ecosystem.config.cjs"
|
||||||
|
[[ -f "${root}/.env" ]] && echo " [✓] .env 已存在" || echo " [!] .env 尚未生成"
|
||||||
|
if [[ -x "${root}/.venv/bin/python" ]]; then
|
||||||
|
echo " [✓] .venv: $("${root}/.venv/bin/python" --version 2>&1)"
|
||||||
|
else
|
||||||
|
echo " [!] .venv 未创建"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " [!] 尚未安装到 ${INSTALL_ROOT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v pm2 >/dev/null 2>&1; then
|
||||||
|
step "PM2"
|
||||||
|
pm2 list 2>/dev/null | head -n 40 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy/lib/doctor.sh — 仅检测环境/依赖,不安装
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
|
set -o pipefail
|
||||||
|
fi
|
||||||
|
|
||||||
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "${LIB_DIR}/common.sh"
|
||||||
|
|
||||||
|
main_doctor() {
|
||||||
|
if root="$(resolve_repo_root 2>/dev/null)"; then
|
||||||
|
REPO_ROOT="${root}"
|
||||||
|
fi
|
||||||
|
doctor_report
|
||||||
|
echo ""
|
||||||
|
log "检测完成(未修改系统).需要安装请返回菜单选 1."
|
||||||
|
}
|
||||||
|
|
||||||
|
main_doctor "$@"
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy/lib/install.sh — CoinOX 一键部署
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
|
set -o pipefail
|
||||||
|
fi
|
||||||
|
|
||||||
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "${LIB_DIR}/common.sh"
|
||||||
|
|
||||||
|
install_fresh() {
|
||||||
|
step "克隆仓库"
|
||||||
|
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||||
|
die "目录已存在: ${INSTALL_ROOT},请选修复环境"
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname "${INSTALL_ROOT}")"
|
||||||
|
git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_repair() {
|
||||||
|
step "修复环境(保留数据与配置)"
|
||||||
|
bash "${REPO_ROOT}/deploy/setup_env.sh" --install-system-deps --recreate-venv
|
||||||
|
}
|
||||||
|
|
||||||
|
run_install_pipeline() {
|
||||||
|
step "环境部署 setup_env.sh"
|
||||||
|
bash "${REPO_ROOT}/deploy/setup_env.sh" --install-system-deps
|
||||||
|
|
||||||
|
pm2_start_app
|
||||||
|
pm2_save_startup
|
||||||
|
install_backup_cron
|
||||||
|
|
||||||
|
verify_deployment "$(detect_server_ip)" || true
|
||||||
|
print_post_install_guide "$(detect_server_ip)"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_existing_install() {
|
||||||
|
echo ""
|
||||||
|
echo "检测到已部署安装: ${INSTALL_ROOT}"
|
||||||
|
echo " a) 取消"
|
||||||
|
echo " b) 修复环境(重建 venv,保留 .env 与数据库)"
|
||||||
|
echo " c) 重新启动 PM2(不改依赖)"
|
||||||
|
local choice=""
|
||||||
|
cm_read choice "请选择 [a/b/c]: "
|
||||||
|
case "${choice}" in
|
||||||
|
b|B)
|
||||||
|
install_repair
|
||||||
|
if command -v pm2 >/dev/null 2>&1; then
|
||||||
|
pm2_start_app
|
||||||
|
pm2_save_startup
|
||||||
|
fi
|
||||||
|
verify_deployment "$(detect_server_ip)" || true
|
||||||
|
print_post_install_guide "$(detect_server_ip)"
|
||||||
|
;;
|
||||||
|
c|C)
|
||||||
|
pm2_start_app
|
||||||
|
pm2_save_startup
|
||||||
|
verify_deployment "$(detect_server_ip)" || true
|
||||||
|
print_post_install_guide "$(detect_server_ip)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "已取消"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_repo_ready() {
|
||||||
|
if repo_ready "${INSTALL_ROOT}"; then
|
||||||
|
REPO_ROOT="${INSTALL_ROOT}"
|
||||||
|
elif [[ -n "${REPO_ROOT:-}" ]] && repo_ready "${REPO_ROOT}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
REPO_ROOT=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${REPO_ROOT}" ]]; then
|
||||||
|
install_system_packages
|
||||||
|
install_node_pm2
|
||||||
|
install_fresh
|
||||||
|
REPO_ROOT="${INSTALL_ROOT}"
|
||||||
|
else
|
||||||
|
install_system_packages
|
||||||
|
install_node_pm2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main_install() {
|
||||||
|
require_root
|
||||||
|
require_ubuntu
|
||||||
|
|
||||||
|
if repo_ready "${INSTALL_ROOT}"; then
|
||||||
|
REPO_ROOT="${INSTALL_ROOT}"
|
||||||
|
elif repo_ready "${REPO_ROOT}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
REPO_ROOT=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${REPO_ROOT}" ]] && is_deployed "${REPO_ROOT}"; then
|
||||||
|
handle_existing_install
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_repo_ready
|
||||||
|
run_install_pipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
main_install "$@"
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy/lib/uninstall.sh — CoinOX 一键卸载
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
|
set -o pipefail
|
||||||
|
fi
|
||||||
|
|
||||||
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "${LIB_DIR}/common.sh"
|
||||||
|
|
||||||
|
main_uninstall() {
|
||||||
|
require_root
|
||||||
|
|
||||||
|
local root=""
|
||||||
|
if ! root="$(resolve_repo_root)"; then
|
||||||
|
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||||
|
root="${INSTALL_ROOT}"
|
||||||
|
else
|
||||||
|
die "未找到安装目录 ${INSTALL_ROOT}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
REPO_ROOT="${root}"
|
||||||
|
|
||||||
|
if ! confirm_uninstall; then
|
||||||
|
log "已取消卸载"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local stamp backup_dir removed_dir
|
||||||
|
stamp="$(TZ="${TZ_NAME}" date +%Y%m%d-%H%M%S)"
|
||||||
|
backup_dir="${BACKUP_ROOT}/coinox-pre-uninstall-${stamp}"
|
||||||
|
removed_dir="${INSTALL_ROOT}.removed.${stamp}"
|
||||||
|
|
||||||
|
step "备份配置到 ${backup_dir}"
|
||||||
|
backup_configs_to "${REPO_ROOT}" "${backup_dir}"
|
||||||
|
{
|
||||||
|
echo "created_at=${stamp}"
|
||||||
|
echo "install_root=${INSTALL_ROOT}"
|
||||||
|
echo "removed_dir=${removed_dir}"
|
||||||
|
} >"${backup_dir}/uninstall.manifest"
|
||||||
|
|
||||||
|
step "停止并移除本项目 PM2 进程(不影响其它 PM2)"
|
||||||
|
pm2_stop_project_apps
|
||||||
|
pm2_delete_project_apps
|
||||||
|
|
||||||
|
remove_backup_cron
|
||||||
|
|
||||||
|
step "移走安装目录"
|
||||||
|
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||||
|
mv "${INSTALL_ROOT}" "${removed_dir}"
|
||||||
|
log "已移动: ${INSTALL_ROOT} -> ${removed_dir}"
|
||||||
|
else
|
||||||
|
log "安装目录不存在,跳过"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "卸载完成."
|
||||||
|
echo " 配置备份: ${backup_dir}"
|
||||||
|
echo " 旧目录: ${removed_dir} (确认无误后可手动删除)"
|
||||||
|
echo ""
|
||||||
|
echo "回滚示例:"
|
||||||
|
echo " mv ${removed_dir} ${INSTALL_ROOT}"
|
||||||
|
echo " bash ${INSTALL_ROOT}/deploy/manage.sh # 选 1 修复环境"
|
||||||
|
}
|
||||||
|
|
||||||
|
main_uninstall "$@"
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy/lib/update.sh — CoinOX 更新
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
|
set -o pipefail
|
||||||
|
fi
|
||||||
|
|
||||||
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "${LIB_DIR}/common.sh"
|
||||||
|
|
||||||
|
require_installed() {
|
||||||
|
if ! repo_ready "${REPO_ROOT}"; then
|
||||||
|
die "未找到安装,请先执行「1) 一键部署」"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
update_quick() {
|
||||||
|
step "快速更新"
|
||||||
|
bash "${REPO_ROOT}/deploy/pull_and_restart.sh"
|
||||||
|
# git pull 后重新加载,避免菜单进程仍用旧的 verify_deployment
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "${LIB_DIR}/common.sh"
|
||||||
|
verify_deployment "$(detect_server_ip)" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
update_deps() {
|
||||||
|
step "依赖更新"
|
||||||
|
if [[ -x "${REPO_ROOT}/.venv/bin/pip" ]]; then
|
||||||
|
pip_install_requirements "${REPO_ROOT}/.venv/bin/pip" "${REPO_ROOT}/requirements.txt" "CoinOX 依赖"
|
||||||
|
else
|
||||||
|
bash "${REPO_ROOT}/deploy/setup_env.sh" --install-system-deps
|
||||||
|
fi
|
||||||
|
update_quick
|
||||||
|
}
|
||||||
|
|
||||||
|
show_update_menu() {
|
||||||
|
while true; do
|
||||||
|
echo ""
|
||||||
|
echo " 更新选项:"
|
||||||
|
echo " 3-1) 快速更新(git pull + pm2 restart)"
|
||||||
|
echo " 3-2) 依赖更新(含 pip install)"
|
||||||
|
echo " 0) 返回主菜单"
|
||||||
|
local choice=""
|
||||||
|
cm_read choice "请选择 [0/3-1/3-2]: "
|
||||||
|
case "${choice}" in
|
||||||
|
3-1|31|1) update_quick; break ;;
|
||||||
|
3-2|32|2) update_deps; break ;;
|
||||||
|
0) break ;;
|
||||||
|
*) echo "无效选项" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main_update() {
|
||||||
|
require_root
|
||||||
|
if ! REPO_ROOT="$(resolve_repo_root)"; then
|
||||||
|
die "未找到安装目录 ${INSTALL_ROOT},请先执行「1) 一键部署」"
|
||||||
|
fi
|
||||||
|
require_installed
|
||||||
|
show_update_menu
|
||||||
|
}
|
||||||
|
|
||||||
|
main_update "$@"
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# CoinOX 部署管理器 — 一键部署 / 卸载 / 更新
|
||||||
|
#
|
||||||
|
# 新服务器(免克隆):
|
||||||
|
# curl -fsSL https://git.bz121.com/dekun/CoinOX/raw/branch/main/deploy/manage.sh | bash
|
||||||
|
#
|
||||||
|
# 已安装:
|
||||||
|
# bash /opt/coinox/deploy/manage.sh
|
||||||
|
#
|
||||||
|
set -e
|
||||||
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
|
set -o pipefail
|
||||||
|
fi
|
||||||
|
|
||||||
|
INSTALL_ROOT="${INSTALL_ROOT:-/opt/coinox}"
|
||||||
|
GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/CoinOX.git}"
|
||||||
|
GIT_BRANCH="${GIT_BRANCH:-main}"
|
||||||
|
|
||||||
|
_script_src="${BASH_SOURCE[0]:-}"
|
||||||
|
if [[ -n "${_script_src}" && -f "${_script_src}" ]]; then
|
||||||
|
DEPLOY_DIR="$(cd "$(dirname "${_script_src}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
|
||||||
|
LIB_DIR="${DEPLOY_DIR}/lib"
|
||||||
|
else
|
||||||
|
DEPLOY_DIR=""
|
||||||
|
REPO_ROOT=""
|
||||||
|
LIB_DIR=""
|
||||||
|
fi
|
||||||
|
unset _script_src
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
repo_ready() {
|
||||||
|
[[ -f "${1}/deploy/setup_env.sh" && -f "${1}/deploy/manage.sh" && -f "${1}/app.py" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_repo_if_present() {
|
||||||
|
local root="$1"
|
||||||
|
if [[ -d "${root}/.git" ]] && command -v git >/dev/null 2>&1; then
|
||||||
|
git -C "${root}" pull -q --ff-only 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap_repo() {
|
||||||
|
if repo_ready "${INSTALL_ROOT}"; then
|
||||||
|
REPO_ROOT="${INSTALL_ROOT}"
|
||||||
|
DEPLOY_DIR="${REPO_ROOT}/deploy"
|
||||||
|
LIB_DIR="${DEPLOY_DIR}/lib"
|
||||||
|
sync_repo_if_present "${REPO_ROOT}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -n "${REPO_ROOT}" ]] && repo_ready "${REPO_ROOT}"; then
|
||||||
|
DEPLOY_DIR="${REPO_ROOT}/deploy"
|
||||||
|
LIB_DIR="${DEPLOY_DIR}/lib"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "CoinOX 部署管理器 — 首次自举"
|
||||||
|
echo "将克隆到: ${INSTALL_ROOT}"
|
||||||
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
|
echo "错误: 请使用 root 执行" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v git >/dev/null 2>&1; then
|
||||||
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y git ca-certificates curl
|
||||||
|
else
|
||||||
|
echo "错误: 未找到 git" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||||
|
echo "错误: ${INSTALL_ROOT} 已存在但不是有效仓库" >&2
|
||||||
|
echo "请手动处理后再运行,或设置 INSTALL_ROOT 指向其它路径" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname "${INSTALL_ROOT}")"
|
||||||
|
git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}"
|
||||||
|
exec bash "${INSTALL_ROOT}/deploy/manage.sh" "$@" </dev/tty
|
||||||
|
}
|
||||||
|
|
||||||
|
show_banner() {
|
||||||
|
local ip path
|
||||||
|
path="${INSTALL_ROOT}"
|
||||||
|
if repo_ready "${REPO_ROOT:-}"; then
|
||||||
|
path="${REPO_ROOT}"
|
||||||
|
fi
|
||||||
|
if [[ -f "${LIB_DIR}/common.sh" ]]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "${LIB_DIR}/common.sh"
|
||||||
|
ip="$(detect_server_ip)"
|
||||||
|
else
|
||||||
|
ip="?"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════╗"
|
||||||
|
echo "║ CoinOX 部署管理器 ║"
|
||||||
|
echo "║ 仓库: git.bz121.com/dekun/CoinOX"
|
||||||
|
echo "║ 路径: ${path}"
|
||||||
|
echo "║ IP: ${ip}"
|
||||||
|
echo "╚══════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
show_menu() {
|
||||||
|
echo " 1) 一键部署(检测环境 + 依赖 + PM2)"
|
||||||
|
echo " 2) 一键卸载"
|
||||||
|
echo " 3) 更新"
|
||||||
|
echo " 4) 仅检测环境/依赖(不安装)"
|
||||||
|
echo " 0) 退出"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
main_menu() {
|
||||||
|
bootstrap_repo
|
||||||
|
# shellcheck source=lib/common.sh
|
||||||
|
source "${LIB_DIR}/common.sh"
|
||||||
|
REPO_ROOT="$(resolve_repo_root || echo "${INSTALL_ROOT}")"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
show_banner
|
||||||
|
show_menu
|
||||||
|
local choice=""
|
||||||
|
cm_read choice "请选择 [0-4]: "
|
||||||
|
case "${choice}" in
|
||||||
|
1)
|
||||||
|
bash "${LIB_DIR}/install.sh"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
bash "${LIB_DIR}/uninstall.sh"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
bash "${LIB_DIR}/update.sh"
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
bash "${LIB_DIR}/doctor.sh"
|
||||||
|
;;
|
||||||
|
0)
|
||||||
|
echo "再见."
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "无效选项,请输入 0-4"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo ""
|
||||||
|
cm_read _ "按 Enter 返回菜单..."
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main_menu "$@"
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 服务器上拉代码并重启 PM2.
|
||||||
|
# 用法(/opt/coinox 下 root):
|
||||||
|
# bash deploy/pull_and_restart.sh
|
||||||
|
# bash deploy/pull_and_restart.sh --dry-run
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
|
set -o pipefail
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO="${REPO:-/opt/coinox}"
|
||||||
|
PM2_APP_NAME="${PM2_APP_NAME:-coinox}"
|
||||||
|
DRY=0
|
||||||
|
if [[ "${1:-}" == "--dry-run" ]]; then
|
||||||
|
DRY=1
|
||||||
|
echo "(dry-run mode)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pm2_has_app() {
|
||||||
|
local name="$1"
|
||||||
|
if ! command -v pm2 >/dev/null 2>&1; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
pm2 jlist 2>/dev/null | python3 -c '
|
||||||
|
import json,sys
|
||||||
|
name=sys.argv[1]
|
||||||
|
try:
|
||||||
|
apps=json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
sys.exit(0 if any(isinstance(a,dict) and a.get("name")==name for a in (apps or [])) else 1)
|
||||||
|
' "${name}"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
pm2 describe "${name}" 2>/dev/null | grep -qE 'status[[:space:]]*[|:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
read_port() {
|
||||||
|
local env_file="${REPO}/.env"
|
||||||
|
local port="5010"
|
||||||
|
if [[ -f "${env_file}" ]]; then
|
||||||
|
local line
|
||||||
|
line="$(grep -E '^[[:space:]]*APP_PORT=' "${env_file}" | tail -n1 || true)"
|
||||||
|
if [[ -n "${line}" ]]; then
|
||||||
|
port="${line#*=}"
|
||||||
|
port="$(echo "${port}" | tr -d '"' | tr -d "'" | xargs)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "${port:-5010}"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_http_ready() {
|
||||||
|
local port="$1"
|
||||||
|
local url="http://127.0.0.1:${port}/"
|
||||||
|
local i code
|
||||||
|
echo ">>> 等待 HTTP 就绪 ${url} (最多 30s)"
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
code="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 2 --max-time 3 "${url}" 2>/dev/null || echo "000")"
|
||||||
|
if [[ "${code}" == "200" || "${code}" == "302" || "${code}" == "301" || "${code}" == "401" || "${code}" == "403" ]]; then
|
||||||
|
echo ">>> HTTP OK (${code}) after ${i}s"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# 进程挂了就别空等
|
||||||
|
if ! pm2_has_app "${PM2_APP_NAME}"; then
|
||||||
|
echo ">>> PM2 进程已消失" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo ">>> HTTP 仍未就绪 (最后 code=${code:-000})" >&2
|
||||||
|
echo ">>> 请查看: pm2 logs ${PM2_APP_NAME} --lines 50" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cd "${REPO}"
|
||||||
|
echo ">>> git pull"
|
||||||
|
git pull
|
||||||
|
|
||||||
|
if [[ "${DRY}" -eq 1 ]]; then
|
||||||
|
echo "(dry-run, skip pm2 restart)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v pm2 >/dev/null 2>&1; then
|
||||||
|
echo "warn: 未安装 pm2" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f ecosystem.config.cjs ]]; then
|
||||||
|
echo "error: 缺少 ecosystem.config.cjs" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if pm2_has_app "${PM2_APP_NAME}"; then
|
||||||
|
echo ">>> pm2 restart ${PM2_APP_NAME} --update-env"
|
||||||
|
pm2 restart "${PM2_APP_NAME}" --update-env
|
||||||
|
else
|
||||||
|
echo ">>> 未注册 ${PM2_APP_NAME},执行 pm2 start ecosystem.config.cjs"
|
||||||
|
pm2 start ecosystem.config.cjs
|
||||||
|
fi
|
||||||
|
|
||||||
|
port="$(read_port)"
|
||||||
|
wait_http_ready "${port}" || true
|
||||||
|
|
||||||
|
echo "done"
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# coinox 一键环境部署(Ubuntu / root /opt/coinox)
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# bash deploy/setup_env.sh
|
||||||
|
# bash deploy/setup_env.sh --skip-pm2
|
||||||
|
# bash deploy/setup_env.sh --recreate-venv
|
||||||
|
# bash deploy/setup_env.sh --install-system-deps
|
||||||
|
#
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
|
set -o pipefail
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
|
||||||
|
REQ_FILE="${REPO_ROOT}/requirements.txt"
|
||||||
|
# shellcheck source=lib/common.sh
|
||||||
|
source "${DEPLOY_DIR}/lib/common.sh"
|
||||||
|
|
||||||
|
SKIP_PM2=0
|
||||||
|
SKIP_ENV_COPY=0
|
||||||
|
RECREATE_VENV=0
|
||||||
|
INSTALL_APT_DEPS=0
|
||||||
|
PY=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
sed -n '2,10p' "$0" | sed 's/^# \?//'
|
||||||
|
exit "${1:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--skip-pm2) SKIP_PM2=1; shift ;;
|
||||||
|
--skip-env-copy) SKIP_ENV_COPY=1; shift ;;
|
||||||
|
--recreate-venv) RECREATE_VENV=1; shift ;;
|
||||||
|
--install-system-deps) INSTALL_APT_DEPS=1; shift ;;
|
||||||
|
-h|--help) usage 0 ;;
|
||||||
|
*) echo "未知参数: $1" >&2; usage 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
find_python() {
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
echo python3
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if command -v python >/dev/null 2>&1; then
|
||||||
|
echo python
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo "未找到 python3/python,请先安装 Python 3.10+" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_python_version() {
|
||||||
|
local py="$1"
|
||||||
|
local ver
|
||||||
|
ver="$("${py}" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
||||||
|
local major minor
|
||||||
|
major="${ver%%.*}"
|
||||||
|
minor="${ver#*.}"
|
||||||
|
if [[ "${major}" -lt 3 ]] || [[ "${major}" -eq 3 && "${minor}" -lt 10 ]]; then
|
||||||
|
echo "需要 Python 3.10+,当前: ${ver}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Python: $("${py}" --version 2>&1)"
|
||||||
|
}
|
||||||
|
|
||||||
|
python_minor_version() {
|
||||||
|
local py="$1"
|
||||||
|
"${py}" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'
|
||||||
|
}
|
||||||
|
|
||||||
|
check_venv_available() {
|
||||||
|
local py="$1"
|
||||||
|
local tmp
|
||||||
|
tmp="$(mktemp -d 2>/dev/null || mktemp -d -t cmvenv)"
|
||||||
|
if "${py}" -m venv "${tmp}" >/dev/null 2>&1 && [[ -x "${tmp}/bin/python" ]]; then
|
||||||
|
rm -rf "${tmp}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
rm -rf "${tmp}" 2>/dev/null || true
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
install_debian_venv_packages() {
|
||||||
|
local py="$1"
|
||||||
|
local ver
|
||||||
|
ver="$(python_minor_version "${py}")"
|
||||||
|
if ! command -v apt-get >/dev/null 2>&1; then
|
||||||
|
echo " 未检测到 apt-get,请手动安装 python${ver}-venv 与 python3-pip" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
|
echo " 需要 root 安装系统包,请执行:" >&2
|
||||||
|
echo " sudo apt update && sudo apt install -y python${ver}-venv python3-pip curl" >&2
|
||||||
|
echo " 或: sudo bash deploy/setup_env.sh --install-system-deps" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
step "安装系统依赖 (python${ver}-venv) ..."
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
if ! apt-get install -y "python${ver}-venv" python3-pip curl ca-certificates git; then
|
||||||
|
apt-get install -y python3-venv python3-pip curl ca-certificates git
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_venv_prereqs() {
|
||||||
|
local py="$1"
|
||||||
|
if check_venv_available "${py}"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo " 当前 Python 无法创建 venv(缺少 ensurepip,常见于未安装 python*-venv)" >&2
|
||||||
|
if [[ "${INSTALL_APT_DEPS}" -eq 1 ]] || [[ "$(id -u)" -eq 0 ]]; then
|
||||||
|
install_debian_venv_packages "${py}" || exit 1
|
||||||
|
if check_venv_available "${py}"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
local ver
|
||||||
|
ver="$(python_minor_version "${py}")"
|
||||||
|
echo "请安装后重试:" >&2
|
||||||
|
echo " apt update && apt install -y python${ver}-venv python3-pip" >&2
|
||||||
|
echo " bash deploy/setup_env.sh" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
create_project_venv() {
|
||||||
|
local py="$1"
|
||||||
|
if [[ "${RECREATE_VENV}" -eq 1 && -d .venv ]]; then
|
||||||
|
echo " 删除旧 venv ..."
|
||||||
|
rm -rf .venv
|
||||||
|
fi
|
||||||
|
if [[ -d .venv && ! -x .venv/bin/python ]]; then
|
||||||
|
echo " 清理未完成的 venv ..."
|
||||||
|
rm -rf .venv
|
||||||
|
fi
|
||||||
|
if [[ -x .venv/bin/python ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo " 创建 venv ..."
|
||||||
|
if ! "${py}" -m venv .venv; then
|
||||||
|
rm -rf .venv 2>/dev/null || true
|
||||||
|
echo " venv 创建失败" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_app() {
|
||||||
|
step "CoinOX"
|
||||||
|
cd "${REPO_ROOT}"
|
||||||
|
create_project_venv "${PY}"
|
||||||
|
pip_upgrade_tools ".venv/bin/pip"
|
||||||
|
pip_install_requirements ".venv/bin/pip" "${REQ_FILE}" "CoinOX 依赖"
|
||||||
|
if [[ "${SKIP_ENV_COPY}" -eq 0 ]]; then
|
||||||
|
if [[ -f .env.example && ! -f .env ]]; then
|
||||||
|
cp -n .env.example .env 2>/dev/null || cp .env.example .env
|
||||||
|
echo " 已复制 .env.example -> .env"
|
||||||
|
elif [[ -f .env ]]; then
|
||||||
|
echo " 保留已有 .env"
|
||||||
|
else
|
||||||
|
echo " 无 .env.example,请手动配置 .env"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
mkdir -p static/images/order_charts
|
||||||
|
echo " 完成: ${REPO_ROOT}/.venv/bin/python"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_pm2_optional() {
|
||||||
|
if [[ "${SKIP_PM2}" -eq 1 ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
step "PM2(可选)"
|
||||||
|
if ! command -v node >/dev/null 2>&1; then
|
||||||
|
echo " 未检测到 Node.js,跳过.安装后执行: npm install -g pm2"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if command -v pm2 >/dev/null 2>&1; then
|
||||||
|
echo " PM2 已安装: $(pm2 -v)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo " 正在安装 pm2 ..."
|
||||||
|
npm install -g pm2
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "CoinOX 环境部署"
|
||||||
|
echo "仓库根目录: ${REPO_ROOT}"
|
||||||
|
echo "远端仓库: ${GIT_URL}"
|
||||||
|
|
||||||
|
[[ -f "${REQ_FILE}" ]] || { echo "缺少 ${REQ_FILE}" >&2; exit 1; }
|
||||||
|
|
||||||
|
PY="$(find_python)"
|
||||||
|
check_python_version "${PY}"
|
||||||
|
ensure_venv_prereqs "${PY}"
|
||||||
|
|
||||||
|
setup_app
|
||||||
|
install_pm2_optional
|
||||||
|
|
||||||
|
step "部署密钥(首次自动生成,不覆盖已有)"
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 "${REPO_ROOT}/scripts/bootstrap_deploy_secrets.py" || true
|
||||||
|
else
|
||||||
|
echo " 跳过 bootstrap_deploy_secrets(未找到 python3)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "部署完成.下一步:"
|
||||||
|
echo " 1. 编辑 ${REPO_ROOT}/.env(默认已是模拟盘 sim;实盘再填 OKX API / 代理 / 密码)"
|
||||||
|
echo " 2. pm2 start ecosystem.config.cjs"
|
||||||
|
echo " 或: bash deploy/manage.sh # 选 1"
|
||||||
|
echo ""
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""首次部署:自动生成登录会话密钥与初始账号(仅空/占位时写入,不覆盖已有).
|
||||||
|
|
||||||
|
- FLASK_SECRET_KEY
|
||||||
|
- APP_USERNAME=admin / APP_PASSWORD=admin123(仅空时)
|
||||||
|
- HUB_BRIDGE_TOKEN(仅空时,供后期中控)
|
||||||
|
- 强制默认模拟盘相关键(仅空/占位时)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
if _REPO not in sys.path:
|
||||||
|
sys.path.insert(0, _REPO)
|
||||||
|
|
||||||
|
from lib.env.env_file_lib import apply_env_updates, env_get, read_env_lines
|
||||||
|
|
||||||
|
FLASK_PLACEHOLDERS = frozenset(
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
"CHANGE_TO_LONG_RANDOM_SECRET",
|
||||||
|
"crypto_monitor_2026_secret_key",
|
||||||
|
"coinox_2026_secret_key",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
PASSWORD_PLACEHOLDERS = frozenset({"", "CHANGE_ME_STRONG_PASSWORD"})
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Bootstrap deploy secrets for CoinOX")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="只打印将写入的项,不改文件")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
env_path = os.path.join(_REPO, ".env")
|
||||||
|
if not os.path.isfile(env_path):
|
||||||
|
print(f" 跳过: 未找到 {env_path}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
lines = read_env_lines(env_path)
|
||||||
|
updates: dict[str, str] = {}
|
||||||
|
|
||||||
|
cur_secret = env_get(lines, "FLASK_SECRET_KEY")
|
||||||
|
if (cur_secret or "").strip() in FLASK_PLACEHOLDERS:
|
||||||
|
updates["FLASK_SECRET_KEY"] = secrets.token_urlsafe(48)
|
||||||
|
|
||||||
|
if not (env_get(lines, "APP_USERNAME") or "").strip():
|
||||||
|
updates["APP_USERNAME"] = "admin"
|
||||||
|
|
||||||
|
cur_pass = env_get(lines, "APP_PASSWORD")
|
||||||
|
if (cur_pass or "").strip() in PASSWORD_PLACEHOLDERS:
|
||||||
|
updates["APP_PASSWORD"] = "admin123"
|
||||||
|
|
||||||
|
if not (env_get(lines, "HUB_BRIDGE_TOKEN") or "").strip():
|
||||||
|
updates["HUB_BRIDGE_TOKEN"] = secrets.token_urlsafe(32)
|
||||||
|
|
||||||
|
# CoinOX: 默认模拟盘(仅当未配置或空时写入)
|
||||||
|
if not (env_get(lines, "SIM_DEFAULT_MODE") or "").strip():
|
||||||
|
updates["SIM_DEFAULT_MODE"] = "sim"
|
||||||
|
if not (env_get(lines, "OKX_OPTIONS_MARGIN_MODE") or "").strip():
|
||||||
|
updates["OKX_OPTIONS_MARGIN_MODE"] = "coin"
|
||||||
|
if not (env_get(lines, "EXCHANGE_DISPLAY_NAME") or "").strip():
|
||||||
|
updates["EXCHANGE_DISPLAY_NAME"] = "CoinOX"
|
||||||
|
live = (env_get(lines, "LIVE_TRADING_ENABLED") or "").strip().lower()
|
||||||
|
if live in ("",):
|
||||||
|
updates["LIVE_TRADING_ENABLED"] = "false"
|
||||||
|
|
||||||
|
if not updates:
|
||||||
|
print(" 密钥已存在,无需写入")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f" 将更新 {env_path}: {', '.join(updates.keys())}")
|
||||||
|
if args.dry_run:
|
||||||
|
for k, v in updates.items():
|
||||||
|
shown = (
|
||||||
|
"***"
|
||||||
|
if k in ("APP_PASSWORD", "HUB_BRIDGE_TOKEN") or "SECRET" in k
|
||||||
|
else v
|
||||||
|
)
|
||||||
|
print(f" {k}={shown}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
apply_env_updates(env_path, updates)
|
||||||
|
print(" 已写入初始密钥/账号与模拟盘默认(请尽快修改 APP_PASSWORD)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -1,367 +1,95 @@
|
|||||||
# `crypto_monitor_okx` 部署文档(Ubuntu)
|
# CoinOX 部署文档(仅 Ubuntu)
|
||||||
|
|
||||||
**功能与页面操作** 见同目录 **[使用说明.md](./使用说明.md)**.Ubuntu 环境(Python / Node / PM2)见 **[docs/ubuntu-server.md](../docs/ubuntu-server.md)**.策略与 AI 见 **[策略交易说明.md](../策略交易说明.md)**,**[AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)**.
|
仓库:<https://git.bz121.com/dekun/CoinOX.git>
|
||||||
|
安装路径:`/opt/coinox`
|
||||||
|
进程名:`coinox`(PM2)
|
||||||
|
默认端口:`5010`
|
||||||
|
默认模式:**模拟盘**(`SIM_DEFAULT_MODE=sim`)
|
||||||
|
|
||||||
|
本项目**仅支持 Ubuntu 22.04 / 24.04 + root 一键部署**,不提供 Windows 部署步骤。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 本地部署 + SSH SOCKS 转发 + PM2 启动指南(Ubuntu)
|
## 一键交互式部署(推荐)
|
||||||
|
|
||||||
本文面向:**本地 Ubuntu 机器运行项目**,但 **本机直连 OKX 会被 TLS/SNI reset** 的场景.解决思路是:
|
新服务器无需先 clone,root 执行:
|
||||||
|
|
||||||
- 本机启动 `ssh -D` 动态转发,把 **SOCKS5 出口**放到你可正常访问 OKX 的 VPS 上
|
|
||||||
- 项目通过环境变量 `OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080` 让 `ccxt` 走 SOCKS
|
|
||||||
- **SSH 隧道**用 `ssh -D` 常驻(可用 tmux / autossh);**Flask 应用** 仅用 **PM2** 托管(见 [docs/ubuntu-server.md](../docs/ubuntu-server.md))
|
|
||||||
|
|
||||||
> 安全提醒:不要把 `.env`,私钥 `.pem`,OKX API Key 提交到 Git;文档里只用占位符.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 你需要准备的东西
|
|
||||||
|
|
||||||
- 一台 **Ubuntu** 本地机器(下文称“本机”)
|
|
||||||
- 一台可 SSH 登录,且 **能正常访问 OKX** 的 VPS(示例公网 IP:`47.76.87.111`,用户:`root`)
|
|
||||||
- VPS 登录方式:**SSH 私钥**(推荐)或密码(不推荐用于无人值守)
|
|
||||||
- 本机已安装:
|
|
||||||
- `python3`,`python3-venv`,`pip`(或 `python3-pip`)
|
|
||||||
- `git`(可选)
|
|
||||||
- `curl`,`ssh`
|
|
||||||
- `node` + `npm`(用于安装 `pm2`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 从云服务器把项目同步到本地(推荐:打包下载)
|
|
||||||
|
|
||||||
在云服务器项目目录(包含 `app.py` 的目录)执行:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/crypto_monitor/crypto_monitor_okx
|
curl -fsSL https://git.bz121.com/dekun/CoinOX/raw/branch/main/deploy/manage.sh | bash
|
||||||
|
|
||||||
# 可选:清理 Python 缓存,减少小文件传输
|
|
||||||
find . -type d -name __pycache__ -prune -exec rm -rf {} +
|
|
||||||
find . -type f -name "*.pyc" -delete
|
|
||||||
|
|
||||||
tar -czf crypto_monitor.tgz .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
下载 `crypto_monitor.tgz` 到本机后解压:
|
已安装机器:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /opt/crypto_monitor/crypto_monitor_okx
|
bash /opt/coinox/deploy/manage.sh
|
||||||
cd /opt/crypto_monitor
|
```
|
||||||
tar -xzf crypto_monitor.tgz -C crypto_monitor_okx
|
|
||||||
cd crypto_monitor_okx
|
### 菜单
|
||||||
cp -n .env.example .env # 若尚无 .env
|
|
||||||
|
| 选项 | 功能 |
|
||||||
|
|------|------|
|
||||||
|
| **1) 一键部署** | 检测环境 + 系统依赖 + Node/PM2 + venv + `.env` + 启动 PM2 |
|
||||||
|
| **2) 一键卸载** | 备份 `.env` → 停 PM2 → 移走目录 |
|
||||||
|
| **3) 更新** | 快速更新 / 依赖更新 |
|
||||||
|
| **4) 仅检测** | 识别 OS / Python / Node / PM2 / 项目状态(不安装) |
|
||||||
|
| **0) 退出** | |
|
||||||
|
|
||||||
|
### 部署完成后
|
||||||
|
|
||||||
|
- 访问:`http://<服务器IP>:5010`
|
||||||
|
- 默认入口:`/hedge-plan`(对冲)、`/options`(期权)
|
||||||
|
- 登录:`admin` / `.env` 中 `APP_PASSWORD`(首次多为 `admin123`)
|
||||||
|
- **默认模拟盘**:本地钱包撮合,不打真实私有下单
|
||||||
|
|
||||||
|
切实盘前请在 env / 系统设置中配置 `OKX_API_*`,并将交易模式切为 `live`,同时确认 `LIVE_TRADING_ENABLED`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 脚本结构
|
||||||
|
|
||||||
|
```
|
||||||
|
deploy/
|
||||||
|
├── manage.sh # 入口(自举 + 交互菜单)
|
||||||
|
├── lib/
|
||||||
|
│ ├── common.sh
|
||||||
|
│ ├── install.sh
|
||||||
|
│ ├── uninstall.sh
|
||||||
|
│ ├── update.sh
|
||||||
|
│ └── doctor.sh
|
||||||
|
├── setup_env.sh
|
||||||
|
└── pull_and_restart.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
详见 [deploy/README.md](./deploy/README.md)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 分步安装(可选)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt
|
||||||
|
git clone -b main https://git.bz121.com/dekun/CoinOX.git coinox
|
||||||
|
cd /opt/coinox
|
||||||
|
bash deploy/setup_env.sh --install-system-deps
|
||||||
|
pm2 start ecosystem.config.cjs
|
||||||
|
pm2 save && pm2 startup
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 配置 SSH 私钥与 `~/.ssh/config`(推荐)
|
## 常用运维
|
||||||
|
|
||||||
把私钥放到本机(示例:`~/.ssh/vps1.pem`),并设置权限:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.ssh
|
|
||||||
chmod 700 ~/.ssh
|
|
||||||
mv ~/Downloads/vps1.pem ~/.ssh/vps1.pem
|
|
||||||
chmod 600 ~/.ssh/vps1.pem
|
|
||||||
```
|
|
||||||
|
|
||||||
编辑 `~/.ssh/config`(没有就创建),添加:
|
|
||||||
|
|
||||||
```sshconfig
|
|
||||||
Host okx-vps
|
|
||||||
HostName 47.76.87.111
|
|
||||||
User root
|
|
||||||
IdentityFile ~/.ssh/vps1.pem
|
|
||||||
IdentitiesOnly yes
|
|
||||||
ServerAliveInterval 30
|
|
||||||
ServerAliveCountMax 3
|
|
||||||
ExitOnForwardFailure yes
|
|
||||||
BatchMode yes
|
|
||||||
```
|
|
||||||
|
|
||||||
测试:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh okx-vps true
|
|
||||||
```
|
|
||||||
|
|
||||||
> 如果你还没完全切到密钥登录(还会交互要密码),先把 `BatchMode yes` 注释掉,等密钥登录稳定后再打开.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 先手工验证:SSH SOCKS + OKX API
|
|
||||||
|
|
||||||
### 3.1 开一个本地 SOCKS(1080)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh -N -D 127.0.0.1:1080 okx-vps
|
|
||||||
```
|
|
||||||
|
|
||||||
保持该进程运行(另开终端继续下面步骤).
|
|
||||||
|
|
||||||
### 3.2 验证 OKX 走 SOCKS 可用
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -4 -Iv --max-time 15 --proxy socks5h://127.0.0.1:1080 https://www.okx.com/api/v5/public/time
|
|
||||||
```
|
|
||||||
|
|
||||||
看到 `HTTP/2 200`(或至少 TLS 握手成功且返回 JSON)即 OK.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Python 虚拟环境(venv)
|
|
||||||
|
|
||||||
在本机项目目录:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /opt/crypto_monitor/crypto_monitor_okx
|
|
||||||
|
|
||||||
python3 -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
|
|
||||||
python -m pip install -U pip
|
|
||||||
pip install flask requests ccxt werkzeug PySocks Pillow
|
|
||||||
```
|
|
||||||
|
|
||||||
> 说明:本仓库当前没有 `requirements.txt`.如果你希望“完全复刻云服务器依赖”,可以在云服务器项目环境里执行 `pip freeze > requirements.txt` 带回本机再 `pip install -r requirements.txt`(记得删掉明显无关/体积巨大的包).
|
|
||||||
|
|
||||||
建议减少 `.pyc` 垃圾文件(可选):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export PYTHONDONTWRITEBYTECODE=1
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 配置环境变量(`.env.example` → `.env`)
|
|
||||||
|
|
||||||
| 文件 | 是否进 Git | 说明 |
|
|
||||||
|------|------------|------|
|
|
||||||
| **`.env.example`** | ✅ 是 | 变量模板与注释,可随 `git pull` 更新 |
|
|
||||||
| **`.env`** | ❌ 否 | 本机真实配置;`app.py` **只读此文件** |
|
|
||||||
|
|
||||||
### 5.1 首次配置
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /opt/crypto_monitor/crypto_monitor_okx
|
|
||||||
|
|
||||||
cp -n .env.example .env # 已存在 .env 时不覆盖
|
|
||||||
nano .env
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 备份与 `git pull`
|
|
||||||
|
|
||||||
- **`.env` 不在 Git 中**:`git pull` **不会**覆盖本地 `.env`.
|
|
||||||
- 远端若更新 **`.env.example`**,pull 后请**手动**把新增变量补进你的 `.env`.
|
|
||||||
- **升级前备份**:`cp .env .env.backup.$(date +%Y%m%d)`;恢复:`cp .env.backup.YYYYMMDD .env`.
|
|
||||||
- **换机**:`scp` 复制 `.env`,或新机 `cp .env.example .env` 后重填.
|
|
||||||
|
|
||||||
**AI 复盘**:三所共用根目录 **`ai_client.py`**.默认 **`AI_PROVIDER=openai`**,网关 `https://op.bz121.com/v1`,模型 `gemma4:e4b`;或改 **`ollama`** 走本机 Ollama.PM2 须 **`PYTHONPATH=..`**.详见 **[AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)**.
|
|
||||||
|
|
||||||
### 5.3 必填项检查(OKX + 代理)
|
|
||||||
|
|
||||||
至少确认/填写这些关键项(示例):
|
|
||||||
|
|
||||||
```env
|
|
||||||
APP_HOST=127.0.0.1
|
|
||||||
APP_PORT=5000
|
|
||||||
|
|
||||||
# OKX(如需实盘)
|
|
||||||
LIVE_TRADING_ENABLED=false
|
|
||||||
OKX_API_KEY=...
|
|
||||||
OKX_API_SECRET=...
|
|
||||||
OKX_API_PASSPHRASE=...
|
|
||||||
|
|
||||||
# OKX 出口:走本机 SSH 动态转发 SOCKS
|
|
||||||
OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080
|
|
||||||
|
|
||||||
# 开仓多周期K线图(可选)
|
|
||||||
# ORDER_CHART_ENABLED=true
|
|
||||||
# ORDER_CHART_TFS=4h,1h,15m,5m
|
|
||||||
# ORDER_CHART_LIMIT=100
|
|
||||||
# ORDER_CHART_DIR=static/images/order_charts
|
|
||||||
# DAILY_OPEN_ALERT_THRESHOLD=5
|
|
||||||
# DAILY_OPEN_HARD_LIMIT=0
|
|
||||||
# 说明见仓库 docs/daily-open-limit.md
|
|
||||||
|
|
||||||
# AI 复盘(默认 OpenAI 兼容网关;与 Ollama 二选一)
|
|
||||||
AI_PROVIDER=openai
|
|
||||||
AI_TIMEOUT_SECONDS=120
|
|
||||||
OPENAI_API_BASE=https://op.bz121.com/v1
|
|
||||||
OPENAI_API_KEY=你的密钥
|
|
||||||
OPENAI_MODEL=gemma4:e4b
|
|
||||||
# 本机 Ollama(仅 AI_PROVIDER=ollama)
|
|
||||||
OLLAMA_API=http://127.0.0.1:11434/api/generate
|
|
||||||
AI_MODEL=你的模型名
|
|
||||||
```
|
|
||||||
|
|
||||||
> 完整说明见仓库根 **[AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)**.`OPENAI_API_KEY` 在 [op.bz121.com](https://op.bz121.com/) 的 `gateway.json` 获取.
|
|
||||||
|
|
||||||
> `OKX_SOCKS_PROXY` 使用 `socks5h`:让 SOCKS 侧做域名解析(更贴近你 `curl --proxy socks5h://...` 的成功路径).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 本机手工启动(验证 Flask)
|
|
||||||
|
|
||||||
确保:
|
|
||||||
|
|
||||||
1. SOCKS 隧道已运行(127.0.0.1:1080)
|
|
||||||
2. 虚拟环境已 `activate`
|
|
||||||
3. `.env` 已配置
|
|
||||||
|
|
||||||
启动:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /opt/crypto_monitor/crypto_monitor_okx
|
|
||||||
source .venv/bin/activate
|
|
||||||
python app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
浏览器访问:`http://127.0.0.1:5000`(或你在 `.env` 配的端口).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 安装 PM2(Node)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo npm i -g pm2
|
|
||||||
pm2 -v
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 用 PM2 启动 SSH SOCKS 隧道(推荐:密钥免交互)
|
|
||||||
|
|
||||||
### 8.1 启动隧道进程
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 start "ssh" --name okx-socks-tunnel -- \
|
|
||||||
-N -D 127.0.0.1:1080 okx-vps \
|
|
||||||
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
|
|
||||||
-o ExitOnForwardFailure=yes -o BatchMode=yes
|
|
||||||
```
|
|
||||||
|
|
||||||
查看日志:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 logs okx-socks-tunnel --lines 200
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.2 仍然验证 OKX
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -4 -Iv --max-time 15 --proxy socks5h://127.0.0.1:1080 https://www.okx.com/api/v5/public/time
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.3 开机自启(可选)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 save
|
|
||||||
pm2 startup
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 用 PM2 启动 Flask(`app.py`)
|
|
||||||
|
|
||||||
`pm2` 管理 Python 的常用方式是直接启动解释器:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /opt/crypto_monitor/crypto_monitor_okx
|
|
||||||
|
|
||||||
pm2 start /opt/crypto_monitor/crypto_monitor_okx/.venv/bin/python --name crypto-monitor -- \
|
|
||||||
/opt/crypto_monitor/crypto_monitor_okx/app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
> 若项目目录与上文不一致,请替换为实际绝对路径;或用 `readlink -f app.py` 得到绝对路径.
|
|
||||||
|
|
||||||
查看日志:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 logs crypto-monitor --lines 200
|
|
||||||
```
|
|
||||||
|
|
||||||
保存进程列表:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 save
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 常见问题排查(高频)
|
|
||||||
|
|
||||||
### 10.1 OKX 仍然失败:先看隧道是否在
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ss -lntp | grep 1080 || true
|
|
||||||
pm2 status
|
pm2 status
|
||||||
```
|
pm2 logs coinox --lines 80
|
||||||
|
bash /opt/coinox/deploy/pull_and_restart.sh
|
||||||
### 10.2 `pm2` 里的 `ssh` 立刻退出
|
bash /opt/coinox/deploy/manage.sh # 选 3 更新
|
||||||
|
|
||||||
常见原因:
|
|
||||||
|
|
||||||
- 私钥权限不对(`chmod 600`)
|
|
||||||
- `~/.ssh/config` 写错 `HostName/User/IdentityFile`
|
|
||||||
- 开了 `BatchMode yes` 但仍需要密码(会失败)
|
|
||||||
|
|
||||||
### 10.3 `ccxt` SOCKS 报错 / 代理不生效
|
|
||||||
|
|
||||||
本机 Python 依赖通常需要:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source .venv/bin/activate
|
|
||||||
pip install PySocks
|
|
||||||
```
|
|
||||||
|
|
||||||
### 10.4 `.pyc` 很多导致同步慢
|
|
||||||
|
|
||||||
`.pyc` 是缓存,删除不影响功能:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
find . -type d -name __pycache__ -prune -exec rm -rf {} +
|
|
||||||
find . -type f -name "*.pyc" -delete
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. 推荐的启动顺序(固定习惯)
|
## 安全提醒
|
||||||
|
|
||||||
1. `pm2` 启动 `okx-socks-tunnel`
|
- 不要把 `.env`、API Key 提交到 Git
|
||||||
2. `curl --proxy socks5h://127.0.0.1:1080 ...` 验证 OKX
|
- 公网务必改掉默认密码,并关闭 `APP_AUTH_DISABLED`
|
||||||
3. `pm2` 启动 `crypto-monitor`
|
- 模拟盘与实盘建议使用不同服务器或至少不同子账户 API
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. 免责声明
|
|
||||||
|
|
||||||
交易所有合规与地区政策要求.请确保你的使用方式符合当地法律法规与交易所条款.本文仅描述网络与工程部署技术路径.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
写好了,脚本路径:
|
|
||||||
|
|
||||||
- `scripts/fix_breakeven_labels.py`
|
|
||||||
|
|
||||||
你在 Ubuntu 上这样用:
|
|
||||||
|
|
||||||
1) 先预览(不写库):
|
|
||||||
```bash
|
|
||||||
python scripts/fix_breakeven_labels.py --db ./crypto.db --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
2) 确认后执行:
|
|
||||||
```bash
|
|
||||||
python scripts/fix_breakeven_labels.py --db ./crypto.db --apply
|
|
||||||
```
|
|
||||||
|
|
||||||
默认修复条件就是你要的:
|
|
||||||
- `monitor_type='下单监控'`
|
|
||||||
- `result='止损'`
|
|
||||||
- `pnl_amount > 0`
|
|
||||||
- 改成 `result='保本止盈'`
|
|
||||||
|
|
||||||
如果你想,我还可以再给你一条“先自动备份 DB 再执行”的一键命令.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user