Compare commits

...

304 Commits

Author SHA1 Message Date
dekun 54c1984ec7 fix: 中控 iframe 嵌入模板路径改用 REPO_ROOT
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 22:58:56 +08:00
dekun be7896cc25 fix: OKX 持仓张数优先读 info.pos,滚仓后同步 order_amount
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 22:40:41 +08:00
dekun 394793b9d2 fix: 突破加仓按 mark 触价触发,避免漏单
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 22:31:49 +08:00
dekun 0b8f410fbe fix: 中控持仓卡合并最新风险与保证金展示
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 22:19:07 +08:00
dekun 687a34474d feat: 修改委托后展示最新风险,四所持仓卡增加张数
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 22:08:27 +08:00
dekun 9d9d0af31e fix: 市价加仓预览后无法执行滚仓
修复 embed 壳拦截 roll-form 提交,以及策略 tab 切换后未重新绑定顺势加仓脚本的问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 21:42:12 +08:00
dekun bfa3352122 feat: 系统设置增加备份恢复与默认登录 admin
支持手动/每日自动备份四所数据库、K线库与 env,上传 zip 一键恢复;中控默认账号 admin/admin123。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 16:39:46 +08:00
dekun 55261b7812 fix: lib 迁移后中控数据路径指向 manual_trading_hub
修复 hub_fund_history 等模块误读 lib/hub/manual_trading_hub 导致资金概况历史曲线丢失的问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 16:28:36 +08:00
dekun 5797d49d8a refactor: 将共用代码迁入 lib/ 模块化目录
统一 strategy、key_monitor、trade、hub 等共用库到 lib/ 子包,并补充 lib-structure 文档,便于四所与中控维护。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 16:23:09 +08:00
dekun 4742a0bb9d refactor: 移除四所统计分析页交易日历
删除日历 UI、bootstrap 与 /api/stats/calendar 注册;保留日/周/月统计表。内照明心档案日历不受影响。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 09:51:10 +08:00
dekun 32079bb4c2 fix: 修复统计日历 bootstrap 导致整站 500
日历数据改为安全 JSON 内嵌,仅统计页构建;构建失败时降级为空,避免拖垮其他页面。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 09:24:54 +08:00
dekun 3b687d17eb fix: 统计日历服务端内嵌 bootstrap,首屏显示盈亏与笔数
与月统计同源 initial_calendar 写入页面,API 失败时仍渲染;四所日历路由独立注册并传入 get_db_fn。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 09:13:58 +08:00
dekun 4f784d09ac fix: 四所统计日历显示每日盈亏与交易笔数
日历格子重置实例全局 button 样式,日期格展示 +X.XU 与 N 笔,标题栏汇总当月盈亏与总笔数。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 08:56:20 +08:00
dekun 052dcf63bd fix: 四所统计日历 embed 切换后初始化与加载失败仍渲染网格
统一 initInstanceStatsCalendar,统计 tab 动态注入时重新挂载日历,API 异常时保留月历骨架。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 08:42:39 +08:00
dekun ac4cdceb39 feat: 四所独立统计页日历,修复档案盈亏重复与日历交互
四所 index.html 统计分析页接入交易日历;内照明心剔除犯病盈亏列不再重复计入,犯病日点击显示全部交易,选中日历蓝色高亮。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 08:27:38 +08:00
dekun 14dbf25798 feat: 档案统计独立卡片、共用交易日历与四所统计页日历
内照明心统计表移至顶部卡片,右侧为日历/图表/交易记录;日历样式适配浅深主题,四所统计分析页同步展示按月盈亏日历。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 08:17:53 +08:00
dekun 6b872b1f43 feat: 内照明心交易日历与交易所口径成交额/手续费统计
新增按 08:00 切日的月历(盈亏、笔数、犯病日高亮与点击筛选);平仓时从交易所 fill 写入双边成交额与手续费,统计表与明细同步展示。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 08:05:46 +08:00
dekun 865567fbd3 fix: add_key 允许突破触价开仓类型通过白名单校验
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 20:55:51 +08:00
dekun a0d57fc65e fix: 箱体/收敛突破标记价反向越界时自动撤销监控
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 10:54:36 +08:00
dekun 5b3448b52b feat: 关键位回调/突破触价开仓拆分与穿越触发
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 10:49:43 +08:00
dekun e51d7824a7 fix: 全仓模式预估风险/盈利按杠杆与可用保证金计算
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 00:38:27 +08:00
dekun 9cb63c368a fix: 修正突破加仓当前价与突破价的几何校验
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 23:34:59 +08:00
dekun be8e4ce6c6 fix: 注册 strategy_roll.js 与 account_risk_badge.js 静态路由
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 23:29:56 +08:00
dekun 02255a3b02 fix: 修复斐波/突破滚仓重复脚本导致无法提交
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 23:25:27 +08:00
dekun 6352fa6be3 fix: 滚仓拒绝原因页内展示,斐波/突破隐藏预览按钮
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 23:16:59 +08:00
dekun 5a887de6f4 fix: 滚仓斐波/突破价输入框可在切换模式后编辑
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 23:07:08 +08:00
dekun 7d03e8e93e fix: 滚仓字段显隐与浅色模式样式
市价加仓默认隐藏上沿/下沿/突破价(CSS+JS);说明页与预估风险条适配浅色主题。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 22:11:58 +08:00
dekun d467760d5c 顺势加仓 v2:程序监控滚仓、文档页与平仓同步
重写滚仓计仓与四种加仓方式(市价/斐波/突破),程序盯 mark 触价成交;风险读监控单;pending 可删不可改;手动平仓同步结束滚仓。新增 /strategy/roll/docs 说明页与顺势加仓滚仓说明.md。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 22:03:23 +08:00
dekun 4aebe70611 Fix hub market chart live K-line updates in manual follow mode.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 19:41:17 +08:00
dekun ee011800e1 Open instance in new tab as full page, not embed shell.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 22:47:02 +08:00
dekun 5cf88818c1 Open instance in new tab; add in-hub trade, monitor, and review shortcuts.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 22:37:54 +08:00
dekun 448e88ec55 Count instance win rate by positive PnL and show external closes as manual close.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 22:27:52 +08:00
dekun 0a20ee7eec Show estimated risk, profit, and RR below manual order form.
Add a preview bar under the live order form with risk in red and profit in green; extend preview logic for all SL/TP modes across embed and standalone instances.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 19:19:32 +08:00
dekun cfc703ae5b Fix double POST on open position in embed shell mode.
Embed capture-phase form handler and allowManualOrderSubmit both submitted /add_order; skip custom forms and use a single fetch reload path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 19:03:25 +08:00
dekun 2dadd93d91 Fix embed date filter reload and classify profitable stops as trailing TP.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 11:27:42 +08:00
dekun 924a385d6c Fix plan history detail modal unreadable text over stats table.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 11:17:31 +08:00
dekun 61d79c4de1 Fix hub market chart live K-line updates without manual reload.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 14:48:01 +08:00
dekun 6ffae02d30 Allow roll add-ons while position-limit freeze is active.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 02:08:44 +08:00
dekun 9d1986d771 Exclude trend and roll monitors from position-limit freeze count.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 02:04:09 +08:00
dekun 322060de31 Show position-limit freeze on hub and instance risk badges.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 01:58:24 +08:00
dekun 3e8ecbf712 Add refresh buttons to hub plan and calculator pages.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 01:43:27 +08:00
dekun 384d404bb3 Slim embed tab rendering to cut memory use and restore calculator.
Load only per-tab data for embed fragments, skip exchange capital fetches on tab switches, and harden calculator market imports/timeouts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 01:33:42 +08:00
dekun bced61b9d7 Avoid hub iframe overlay on embed shell tab switches.
Use in-shell content loading state instead of parent postMessage so tab changes do not trigger the full instance-frame loading mask.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 01:23:40 +08:00
dekun 4ad335ca84 Add hub iframe embed shell with tab fragment API.
Replace full-page soft nav with a persistent shell and /api/embed/page loads so tab switches in the hub iframe avoid document.write flicker.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 01:13:34 +08:00
dekun 157d9ada21 Fix calculator import error for hub_supervisor_lib.
Ensure settings_store resolves supervisor module when imported from repo-root calculator libs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 00:42:21 +08:00
dekun 813ebf0e4e Skip exchange PnL sync on hub iframe soft nav to fix slow records tab.
Remove hover prefetch and mark soft-nav fetches so Gate/OKX render pages from local DB without blocking on exchange history sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 00:39:54 +08:00
dekun b18b2143b5 Restore hub iframe soft nav to cut blank tab switch gap.
Use fetch in-frame navigation with overlay and hover prefetch; show delayed hub loading spinner instead of hiding the iframe.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 00:32:03 +08:00
dekun f63f8810e6 Fix Gate/Binance memory regression and roll stop offset from avg.
Stop fetch_tickers fallback for volume rank and keep stale cache on failed refresh. Compute roll unified stop as merge-average plus offset percent instead of break-even.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 00:21:07 +08:00
dekun 7f8ae97a98 Fix hub iframe nav flicker with normal navigation and loading overlay
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 00:06:17 +08:00
dekun e3559531d9 Revert instance soft nav cache to fix navigation flicker
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 23:59:27 +08:00
dekun 016c93faf2 Add 7-day local page cache for instant instance nav switching
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 23:52:28 +08:00
dekun e03863d780 Add roll leg avg/TP profit display and reduce instance nav flicker
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 23:42:02 +08:00
dekun 54ba412d1d Fix false supervisor open events for existing holdings
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 20:20:33 +08:00
dekun 65901c5577 Fix supervisor AI empty replies with fallback templates
Skip appending AI error strings to the session and use event-specific fallback commentary when the model returns empty content.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 20:10:33 +08:00
dekun acc158f85d Use auto-fit grid for funds and dashboard account cards
Account cards expand to fill available width when fewer exchanges are enabled instead of staying in fixed four-column tracks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 19:57:24 +08:00
dekun ea5c6cddb4 Add per-card save and collapse on settings page
Each settings section and exchange card gets its own save button and fold toggle with state persisted in localStorage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 19:37:16 +08:00
dekun 0dedaa2b4d Fix supervisor settings panel inner padding
Align the trading supervision card with other settings panels so content is not flush against the border.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 19:30:14 +08:00
dekun bfbd6879d6 Add AI trading supervisor with WeChat push and daily session
Proactive monitoring for manual/hub closes and new opens prevents overtrading via in-app alerts, configurable WeChat links, and supervisor chat.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 19:25:01 +08:00
dekun d3d366d0ee Hide disabled exchanges from dashboard and fund overview.
Only aggregate and display exchanges with enabled monitoring in system settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 18:41:52 +08:00
dekun faa41eece1 Make calculator cards equal height on desktop layout.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 18:34:15 +08:00
dekun f4d7dec111 Trim trailing zeros in calculator market info display.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 18:29:08 +08:00
dekun b0ec291345 Replace httpx with urllib in calculator market fetch.
Avoid ModuleNotFoundError when hub_calculator_market_lib loads outside the manual-trading-hub venv.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 18:20:51 +08:00
dekun f78ea1288e Fix calculator instance auth header to X-Hub-Token.
Market info fetch was rejected by instances because it sent the wrong bridge token header; align with hub monitor calls.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 18:17:42 +08:00
dekun 5e507d0b66 Use hub exchange instances for calculator contract precision.
Load enabled instances from settings, fetch market info via /api/hub/market, and apply exchange-specific amount and price precision in trend and roll calculators.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 18:13:02 +08:00
dekun d938bc6c59 Redesign roll calculator with auto first entry and chained add legs.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:44:27 +08:00
dekun 253d353206 Add hub strategy calculator page with trend and roll risk-based sizing.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:35:06 +08:00
dekun 1ba0014fff Add padding to macro settings panel so text is not flush to edges.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:24:07 +08:00
dekun caf4996159 Add settings toggles for plan, archive, and AI coach nav items.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:19:12 +08:00
dekun 89909c64a3 Align trade record detail fields with label-value grid layout.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:04:50 +08:00
dekun 21f86906da Fix mobile trade records hidden by CSS cascade overriding compact list.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 16:58:56 +08:00
dekun c302c3e4ea Add mobile compact trade and journal lists with tap-to-expand detail.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 16:53:56 +08:00
dekun 8e810154ca Add open-instance to trade page and mobile/tablet responsive layouts.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 16:38:30 +08:00
dekun ed3709dddf Move entry scheme to active plans only, required on archive.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 16:56:34 +08:00
dekun a837cfd14c Fix broken hub_macro_calendar_lib import in hub.py.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 16:50:09 +08:00
dekun 091317276d Add entry plan page with CRUD, archive flow, and win-rate stats.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 16:19:56 +08:00
dekun bd759c42d6 Use lightweight ticker APIs for liquidity rank to cut memory.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:24:27 +08:00
dekun c0f3606ecc Add win rate and profit-loss ratio to archive stats.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:13:37 +08:00
dekun c05afbbedf Add win/loss metrics to archive stats with symbol filter sync.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:03:21 +08:00
dekun 073a382d41 Unify key support/resistance monitor type and fix form parity.
Merge 关键阻力位/关键支撑位 into 关键支撑阻力, share key_monitor_form.js across hub and new-tab views, and add hub shortcut to /key_monitor.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-19 08:31:14 +08:00
dekun ce172a7cee Fix false freeze after restart from stale account_risk_state.
Clear expired cooloff on read, never restart timer from invalid future anchors, and reconcile with journaled manual closes when the 1h window already ended.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 22:14:58 +08:00
dekun 9330e356fc Fix freeze countdown exceeding configured cooloff hours.
Clamp future last_close anchors, cap remaining time server-side, prefer freeze_remaining_sec in the badge JS, and auto-repair stale DB rows on read.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 22:11:09 +08:00
dekun deb240d4eb docs(risk): clarify cooloff countdown uses last manual close only
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 22:04:50 +08:00
dekun c73944581c fix(risk): stop stale 4h cooloff after 1h journal expires
Anchor last_close on journal save, ignore leftover stored until when 1h window ended, and clear expired cooloff on trading-day rollover.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 22:04:14 +08:00
dekun 9c778e0232 fix(risk): align freeze countdown with latest close not stale 4h until
Pick the shortest active cooloff end and derive 1h/4h label from remaining time.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 20:27:13 +08:00
dekun ff8caf7f8d fix(risk): correct freeze countdown timezone (Asia/Shanghai)
Treat naive app datetimes as local time, normalize legacy UTC-ms rows, and resolve cooloff end from stored until or last_close+duration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 18:00:40 +08:00
dekun f8e760961e fix(risk): reset badge to normal when freeze countdown expires
Also expand account-risk-cooldown docs with countdown format, API fields, and frontend assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 17:47:47 +08:00
dekun 97370926d6 feat(risk): show live countdown on freeze status badges
Expose freeze_until_ms from risk API and tick hub/instance badges with remaining 1h/4h/daily time.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 17:41:04 +08:00
dekun 0280b4f065 fix(risk): shorten cooloff on review save and when count was reset
Allow 1h reduction for any active 4h-tier cooloff, hook trade record review updates, and fix freeze label thresholds.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 16:35:51 +08:00
dekun f0a158686e fix(risk): allow journal to reduce 4h cooloff to 1h without pending trade id
Hub closes and late journal saves now shorten active manual cooloffs when exit trigger and note are filled in.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 16:27:21 +08:00
dekun e470c5952f feat(hub): add macro calendar for pre-release risk alerts
Manual FOMC/CPI/employment entries in settings drive ±1h monitor banners without touching exchange instances.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 11:52:30 +08:00
dekun 3d29b4f9d9 fix(instance): restore review edit button after hub iframe nav
Sync review-mode checkbox with disabled state after form restore; repair initHubEmbedInFrameNav regression in instance_theme.js.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 11:21:31 +08:00
dekun d8dccb8606 fix(hub): stop instance iframe nav flash after account status badge
Load account_risk_badge.css before body paint, skip redundant hub theme re-apply, remove iframe hide overlay, and disable badge transitions in hub embed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 19:36:48 +08:00
dekun 6520234bd8 fix(hub): eliminate iframe flash when switching instance nav tabs
Use soft in-frame navigation and loading overlay in hub instance shell; pass embed=1 for iframe SSO opens.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 19:27:52 +08:00
dekun be7f5d5072 fix(auth): stop pre-filling login username on new devices
Remove username_hint from hub auth status API and disable autocomplete on hub and instance login forms.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 19:21:46 +08:00
dekun b6acbf4b2c fix(risk): trigger cooldown only on user-initiated closes
Remove external-close risk hooks; register user_instance, user_hub, and user_trend_stop via hub API and trend stop; update docs and tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 19:14:05 +08:00
dekun 850ffcd7d2 style(risk): polish account status badge for light and dark themes
Extract shared account_risk_badge.css with theme-aware contrast, dot indicator, and hub/instance layout fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 17:37:32 +08:00
dekun e307eef690 feat(risk): add account cooldown and daily freeze after manual/external close
Implements shared account_risk_lib with 4h/1h cooloff and daily freeze rules, wires hooks into all four exchange apps and hub monitor UI, with tests and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 17:05:19 +08:00
dekun b77741ee21 fix(order): hide RR preview in fixed-RR mode and serve shared JS
manual_order_rr_preview.js was not routed from repo static/, so hide logic never ran. Add Flask routes on four exchanges, default hidden in HTML, and toggleSltpMode visibility.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 10:59:45 +08:00
dekun ca1e25888d fix(sync-close): reject pre-open fills when backfilling trade records
False external-close sync could match historical closing trades before opened_at, producing stop-loss results with closed_at earlier than opened_at. Only use fills at or after open time.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 17:03:51 +08:00
dekun 6287ca9129 fix(binance): show orphan recover banner on trade page load
Trade tab uses refreshPriceSnapshotConditional, not refreshPriceSnapshot; render recover banner there and on server when live exchange position lacks active monitor.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:59:20 +08:00
dekun 7fe7c2e918 feat(binance): recover live position when local monitor was lost
Detect exchange positions without active order_monitors, show a recover banner on the trade page, and reactivate stopped monitors with optional TP/SL restore via API.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:54:38 +08:00
dekun c1ee0dae25 fix(reconcile): guard Binance/OKX restart false flat sync
Add startup grace and consecutive flat polls before external-close reconcile, matching Gate, to avoid stopping monitors and canceling TP/SL when positions API is not ready after restart.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:34:17 +08:00
dekun 58e940629a fix(order): hide estimated RR preview in fixed RR SLTP mode
Only price and percentage modes show estimated profit-loss ratio before open; fixed RR mode keeps the existing estimated take-profit display.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:26:18 +08:00
dekun f9257b64e4 feat(order): show estimated RR before open across four exchanges
Add shared manual_order_rr_preview.js to fetch order_defaults after symbol and TP/SL inputs complete, display estimated profit-loss ratio before submit in price and percentage modes (and fixed RR), unified for risk and full-margin sizing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:19:32 +08:00
dekun 869728ce10 feat: archive trades by close time and show open time on live positions
Sort inner-archive daily trades by closed_at_ms; add open time and live hold duration to instance and hub position cards, with exchange margin on hub footer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 06:20:18 +08:00
dekun ad1c08a2cc fix(hub): remove duplicate AI chat declarations causing white screen
Drop redeclared AI_CHAT_MAX_ATTACHMENTS and aiChatPendingFiles in app.js that broke script parsing after the chat perf update.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 02:02:33 +08:00
dekun 467d160f4d perf(hub-ai): reduce CPU load during trading coach chat
Cache chat context, parallelize exchange fetches, skip fund history writes, defer rolling summary to a background thread, and cache markdown rendering on the client.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 01:59:43 +08:00
dekun 28a23008f3 feat(hub-ai): paste screenshots in chat and include position TP/SL in coach context
Let users paste images into AI chat with removable pending attachments, and feed exchange/monitor stop-loss and take-profit into trading coach snapshots so replies reflect actual protection on open positions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 01:53:24 +08:00
dekun 42c06c0f38 chore(scripts): remove one-off trigger entry patch scripts
These temporary patch helpers are no longer needed after the feature landed in the main apps.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 01:11:55 +08:00
dekun 4573ccca9a fix(key-monitor): repair trigger entry bugs in four exchange apps
Restore missing check_fib_key_monitors, fix gate preview and OKX add_key,
and unify trigger execution error handling to avoid duplicate history writes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 01:06:53 +08:00
dekun edf4bb835d feat(key-monitor): add program trigger entry across four exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 00:42:21 +08:00
dekun c95ca6ac35 fix(hub): color host status summary metrics green/red and bust css cache
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 14:26:26 +08:00
dekun 47910e6cb3 fix(hub): use green/red host status indicator at 85% CPU memory disk threshold
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 14:22:49 +08:00
dekun c1e0e52f8c fix(hub): restore monitor host status panel after missing pref helpers
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 14:18:34 +08:00
dekun 9e395b6732 feat(hub): fold host status by default, add entrust on grid positions, alert on high CPU/memory
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 14:14:48 +08:00
dekun a89b446d74 fix(hub): improve monitor host status bar layout and document psutil setup
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 14:10:00 +08:00
dekun 1fd0003fc8 feat(hub): show server CPU memory disk and network status on monitor page
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 14:04:05 +08:00
dekun ab862efc4e feat(hub): add yesterday close and high-low price line toggles on market chart
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 20:45:09 +08:00
dekun 309eebc61d feat: add time-close option to journal exit trigger dropdown
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 22:28:04 +08:00
dekun 44c3703f07 feat: include time-close result in hub sync and instance trade records
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 22:18:12 +08:00
dekun 6a1f2608b5 feat(hub): rolling chat summary to cap AI context and prevent mid-session failures
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 21:51:34 +08:00
dekun 180aff5310 feat(hub): add archive quote AI coach review from inner light page
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 21:10:39 +08:00
dekun bb8bb3ae34 feat(hub): fix archive quotes inline expand and mobile archive layout
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:50:58 +08:00
dekun cf1265763c feat(hub): refactor archive quotes to list-detail with top-form edit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:42:35 +08:00
dekun e7e3a49151 feat(hub): enlarge archive quotes panel and show full text on expand
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:33:51 +08:00
dekun a9c40097b3 feat(hub): time-close countdown in monitor position table
Show timed-close badge with live countdown next to contract name in the grid holdings table.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:24:30 +08:00
dekun 0d82cd2ad3 feat: show time-close countdown after symbol in monitor area
Move timed-close badge next to contract name on hub board and instance position cards; refresh countdown on board render.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:20:13 +08:00
dekun 035060b68a feat(hub): click archive trade row to switch chart
When the K-line panel is open, clicking a trade row switches selection and chart markers without re-fetching candles for the same symbol.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:10:56 +08:00
dekun 3ef0750ea9 fix(hub): archive trade row selection by exchange+id
Use composite trade key so Gate accounts with duplicate trade IDs no longer highlight two rows at once.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:01:20 +08:00
dekun a87234f627 fix(hub): archive chart perpetual label and trade row highlight
Label archive K-lines as USDT swap in title and API; highlight the full trade row when opening its chart.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 19:56:55 +08:00
dekun a9637fafb2 fix(hub): archive labels, symbol column, and time-close dropdown
Force exchange_key on archive sync; add contract column and tag select styles on inner-light-mind; serve time_close_ui.js on instances so order/key time-close duration can be selected.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 19:44:51 +08:00
dekun 959593cdab feat: add timed position close (1h/2h/4h) for key levels and live orders
Program monitors open positions and market-closes at deadline; UI shows label and countdown on instance and hub boards.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 19:30:16 +08:00
dekun 879ea5e228 feat(hub): stats table and adaptive trades list height on archive page
Show period stats in a table; limit trades to 10 rows when chart is open and fill viewport when collapsed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 18:18:08 +08:00
dekun 7b0b8996fe feat(hub): add period date range and trade stats to inner-light-mind
Support today/week/month/custom range selection with sick count, PnL, and per-exchange breakdown; update docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 18:09:39 +08:00
dekun 5f79a62b13 fix(hub): sick trade rows use red text only, widen AI history
Remove red background on 犯病 archive rows; fix AI markdown ordered lists; widen chat history sidebar.
2026-06-11 18:01:52 +08:00
dekun 2388ecc882 fix(hub): inner-light-mind chart, exchange column and layout
Preserve exchange_key from archive cache, fix chart resize in details panel, move filters above quotes, align panel heights, and style sick trades with red/cyan.
2026-06-11 17:53:25 +08:00
dekun bb800b876b feat(hub): redesign archive as inner-light-mind journal
Rename archive to 内照明心 with daily trade records by default, review quotes sidebar, on-demand chart, sick-row highlighting, and new daily-trades/quotes APIs.
2026-06-11 17:43:45 +08:00
dekun 65d2bc5e00 fix(hub): restore desktop monitor layout in PWA
Split mobile AI layout from viewport-only mobile detection so standalone desktop app uses full monitor cards again.
2026-06-11 11:41:03 +08:00
dekun 51252d5dda fix(hub): keep mobile PWA AI navigation visible
Fix keyboard detection in standalone app so top nav and AI tabs stay visible; recognize PWA display mode for mobile layout.
2026-06-11 11:35:44 +08:00
dekun 08ae171e48 feat(hub): mobile AI one-screen and dashboard monitor counts
Fix mobile AI to scroll only in the chat area. Dashboard cards show monitor item counts with expand-to-fullscreen and color-coded position floating P&L.
2026-06-11 11:27:28 +08:00
dekun 1042fdeef3 feat(hub): mobile AI tabs and dashboard position lines
Mobile AI coach uses four top tabs (trading, general, history, new) with single-panel view and wider desktop history. Dashboard account cards show key levels and positions one per line with colored float PnL.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 11:16:28 +08:00
dekun c59a17f9ac fix(hub): improve mobile AI coach layout
Move new-chat button to top toolbar to prevent overlap, compact chrome, hide chrome when keyboard opens, and soften mobile tab styling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 11:06:47 +08:00
dekun 007e089121 feat(hub): settings toggles for funds and dashboard nav
Add show_nav_funds and show_nav_dashboard in hub_settings display prefs to hide top nav entries and redirect direct URL access.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 11:00:19 +08:00
dekun 07e8604ea6 feat(hub): dashboard SSE push, light-theme cards, simplify AI coach
Replace dashboard polling with backend SSE and snapshot refresh. Restyle for light/dark theme with soft card glow instead of neon. Remove Today's Summary from AI page; keep trading and general chat only. Update hub documentation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 10:53:50 +08:00
dekun 582ada7e60 feat(hub): add data dashboard and AI chat with session history
Add /dashboard with daily PnL overview and loss alerts. Extend AI coach chat with history sidebar, delete/switch sessions, message copy, and trading vs general bot modes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 10:42:33 +08:00
dekun a45a3b18e2 feat: hub setting to hide account balances and PnL in monitor
Persist show_account_pnl in hub_settings.json; refine key monitor panel layout with right-aligned live stats and scrollable history (max 8 rows).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 10:15:57 +08:00
dekun b671c05b1b fix: equalize key monitor and history card heights
Stretch both cards in the dual panel grid while keeping list areas scrollable inside.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 10:04:03 +08:00
dekun acce230a0d chore: normalize line endings in fund overview doc
Keep manual_trading_hub fund overview markdown consistent with Windows CRLF.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 09:51:17 +08:00
dekun 0647bba5f5 fix: make key level history list vertically scrollable
Prevent history rows from shrinking when the list grows; show a stable right-side scrollbar.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 09:49:03 +08:00
dekun 324aa1c5c6 fix: include created_at in price_snapshot key monitor query
Avoid IndexError when rendering false breakout gate preview after the gate UI change.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 08:26:15 +08:00
dekun fa59fc1273 fix: show limit-order gate for false breakout monitors
False breakout used box/convergence volume/break gates in the UI; now shows pending limit order status like fib monitors.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 08:22:41 +08:00
dekun 8c5b9681a9 fix: backfill key_signal_type without sqlite3.Row in init_db
Use tuple indices because init_db connects before row_factory is set.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 08:15:12 +08:00
dekun 3bdf7cf384 fix: show box/convergence breakout labels in key level trade records
Persist 箱体突破/收敛突破 as key_signal_type and backfill historical records on startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 08:11:57 +08:00
dekun f6e3d54d29 revert: restore original order monitor form layout with SL/TP mode width fix
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 08:01:06 +08:00
dekun 91c8cd8c2a fix: redesign order monitor open form for clearer SL/TP inputs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 07:56:55 +08:00
dekun 401ee2f130 fix: stabilize AI coach chat against truncation and empty replies
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 06:27:06 +08:00
dekun 7f1015f852 fix: AI coach chat continuation now carries full draft text
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 00:53:45 +08:00
dekun 6169fee7b9 fix: prevent AI coach chat replies from truncating mid-sentence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 00:44:08 +08:00
dekun 0e2e360ccf fix: improve AI coach chat context, 128k window, and output limits
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 00:35:01 +08:00
dekun 6977bce64f fix: tighten key monitor rule table typography and copy
Use smaller fonts and shorter cell text so the rule reference fits better on the key monitor page.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 18:50:46 +08:00
dekun 9c7290dea5 feat: add tabular key monitor rule reference on all exchanges
Replace the pipe-separated rule blurb with a detailed five-column table driven by .env-backed gate parameters for easier trading reference.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 18:40:36 +08:00
dekun 5faedfbfb7 feat: collapse fund overview help and bind start day to env
Fold the stats description by default and render history_start_day and keep_days from the API so HUB_FUND_HISTORY_START_DAY in .env drives the UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 17:23:54 +08:00
dekun 4f5a982b63 fix: improve fund overview readability and layout
Use tabular mono for amounts, show four account columns on desktop, and remove the scanning line animation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 17:19:19 +08:00
dekun 9602acafb2 feat: restyle fund overview with HUD UI and nav reorder
Move funds before monitor in nav while keeping monitor the default landing page; use card layout with curves in fullscreen and a tech/AI-themed funds dashboard.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 17:12:58 +08:00
dekun ec8607932b feat: circular fund account cards with fullscreen detail view
Show per-exchange balances as clickable circles with mini curves; open a fullscreen panel for equity history and drawdown.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 17:04:21 +08:00
dekun ba629ea0ee fix: fund overview history starts from 2026-06-09
Add HUB_FUND_HISTORY_START_DAY so curves and drawdown exclude snapshots before the baseline trading day.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 16:58:57 +08:00
dekun 77c7bbbb13 feat: add hub fund overview tab with 180-day equity curves
Add /funds page for total and per-account balance (funding+trading), drawdown, and daily snapshots from monitor board aggregation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 16:50:47 +08:00
dekun 6eb17b7ddc feat: add trading day split lines on hub market chart
Add toggle before technical indicators to show blue dashed vertical lines at Beijing 8:00 day boundaries.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 08:35:35 +08:00
dekun b6d343a951 sync gate_bot with gate as identical copy instance
Align gate_bot app, templates, and env template with gate while keeping bot identity (port 5002, hub key gate_bot).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 18:19:50 +08:00
dekun 59a45ed027 fix: Gate hub close sync and trade record open stop-loss snapshot
Sync order monitors from Gate position history after hub flat close. Store and display initial_stop_loss in trade records instead of post-amend exchange stops.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 18:01:59 +08:00
dekun 02d2a6c70b fix: mobile hub AI keyboard layout and instance top nav scroll
Sync hub shell to visualViewport when the keyboard opens to prevent white screen above chat input. Make instance tabs horizontally scrollable with active tab centered on phone.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 17:16:00 +08:00
dekun 9aba8ec645 feat: hide instance export and stats on mobile, desktop-only downloads
Mark export bar and summary stat cards as instance-desktop-only. Hide all CSV/MD export actions on phone across four exchange instances.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 17:07:00 +08:00
dekun e60beeedd3 feat: fix mobile AI full-width layout and simplify instance stats
Move hub AI mobile CSS after desktop grid rules so chat fills the screen. Hide exchange and total-trade stat cards on phone across all instances and compact export bar.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 16:57:47 +08:00
dekun ea3ef71477 feat: optimize hub mobile layout with chat-first AI coach tabs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 16:47:19 +08:00
dekun 3527c26717 feat: collapse trade/strategy rule tips and color-code key history outcomes
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 16:39:36 +08:00
dekun 04d5d5329e feat: show pending key monitor orders in green on summary line
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 16:26:07 +08:00
dekun 77aef229e9 fix: show full key history alert when expanded in scroll panel
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 16:22:46 +08:00
dekun 4a043e65e3 feat: improve light mode key monitor UI and collapse rule tips by default
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 16:18:10 +08:00
dekun 260828041f feat: unify key monitor UI with one-line summary and expandable details across all exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 16:11:31 +08:00
dekun 24a86a710c feat: add per-account daily open hard limit across all exchanges
Enforce optional DAILY_OPEN_HARD_LIMIT in precheck_risk and can_trade, keep AI alerts at DAILY_OPEN_ALERT_THRESHOLD, and document env setup for all four instances.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 15:46:18 +08:00
dekun f7d94f67d7 fix: sync live TP/SL to position cards after entrust changes
Use exchange TP/SL for display and DB sync on price_snapshot polls, refresh instance UI cells on each tick, and merge live values into hub monitor board.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 11:06:27 +08:00
dekun 7cb55f6557 feat: add false breakout key monitor for BTC/ETH on three exchanges
Place limit orders outside key levels with fixed SL and 1.5 RR, 24h expiry, separate stats, and full-margin mode guard.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 10:13:53 +08:00
dekun 2786acf884 fix: feed today-only data to AI daily summary to reduce hallucination
Shrink summary context and prompts to today's trades and positions only, and tighten anti-fabrication rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 09:39:40 +08:00
dekun a5c6e0c5b6 feat: add fixed RR stop-loss mode for manual live orders on all instances
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 20:57:29 +08:00
dekun 38f4280bb8 fix: UTC+8 market chart times and archive full history K-line load
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 16:55:48 +08:00
dekun 55a979eee5 fix: show archive chart times in UTC+8 and parse Beijing wall clock
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 16:47:09 +08:00
dekun 947b58084d ui: add Top20 button to market chart fullscreen toolbar
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 16:37:05 +08:00
dekun 5fb4a10638 ui: show Top20 rank sheet below toolbar instead of over chart
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 16:11:11 +08:00
dekun 4c55932906 fix: Top20 dropdown floats above chart with fixed positioning
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 16:08:05 +08:00
dekun 93b84da72e fix: invalidate stale 12-item volume rank cache and force full top20 refresh
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 16:01:27 +08:00
dekun 89a58c7323 fix: exchange-specific volume rank APIs for OKX and full top20
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:53:41 +08:00
dekun 4bf0c2363f feat: daily volume top20 rank per exchange in market page
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:46:36 +08:00
dekun 09eb9dc475 ui: remove crosshair, channel and text tools from draw toolbar
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:05:37 +08:00
dekun 94679f10d0 feat: right-click context menu and Delete key for chart drawings
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:02:28 +08:00
dekun d659cf7a4c fix: TV-style fib colors, range badge, polyline preview, one-shot tools
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:57:23 +08:00
dekun ef57ba13c5 fix: drag-to-draw for range and fibonacci like TradingView
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:50:25 +08:00
dekun ef57872d14 fix: market drawing tools overlay alignment and pointer handling
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:45:47 +08:00
dekun 26a4c04b88 feat: add vertical drawing toolbar on market chart
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:41:52 +08:00
dekun 46963a4498 feat: archive entry type from review, prune stale trades on sync, manual delete
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:39:27 +08:00
dekun e68e29629e feat: push chart tail candles over SSE for faster market refresh
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:24:25 +08:00
dekun 4918699276 feat: show review fields in symbol archive trade table
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:20:29 +08:00
dekun 1dcf62bb08 fix: align OHLCV fetch window with limit so chart seeds past 30 bars
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 11:37:00 +08:00
dekun ca6ef59a14 Add clear-and-refetch for hub K-line cache.
Force refresh wipes the series in hub_kline.db before pulling from the exchange; add a Linux clear script and rename the UI button to 清库重拉.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 11:31:16 +08:00
dekun 2095839fc3 Fix hub chart skipping remote fetch when DB bars are discontinuous.
Trim gaps before deciding fetch need, always backfill short contiguous tails, and relax gap detection so tail polls do not block full history loads.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 11:27:00 +08:00
dekun 440d1ecbc9 Fix discontinuous hub chart candles from orphaned DB bars.
Keep only the latest contiguous K-line segment, purge isolated stale rows, and backfill when the tail is still shorter than the initial limit.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 11:21:01 +08:00
dekun cfa28e7f4e Fix hub full-close double-booking trend plans.
Sync active plans after hub position close, merge final close snapshots per plan, and backfill missing trade records when ending an already-stopped plan.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 09:06:36 +08:00
dekun e71bfe095c Prevent duplicate strategy trade snapshots on plan close.
Finalize plans before writing snapshots, dedupe on startup and page load, and add a cleanup script for existing repeated rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 09:00:51 +08:00
dekun ea92160d54 Fix strategy records list showing empty rows after hub close.
Records were still in the database but CSS clipping hid row text; restore visible summary styling and symbol fallback from snapshots.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 08:51:47 +08:00
dekun b34aefbcc4 Patch tail candles without resetting chart viewport.
When auto-follow is off, refresh only updates the latest bars via series.update instead of setData so zoom and pan stay fixed during background polls.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 08:39:56 +08:00
dekun 5af0cbf286 Fix chart auto toggle to clearly split follow vs manual zoom.
On tail refresh, auto-on always snaps to latest candles while auto-off preserves the saved viewport with clamped range restore.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 08:35:56 +08:00
dekun bae78d8368 Respect manual chart zoom when auto-follow is off.
Tie the market auto toggle to both price scale and viewport: tail refresh updates data silently while preserving zoom/pan when auto is disabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 08:30:44 +08:00
dekun c8ffc764e1 Increase default chart initial bar counts per timeframe.
Load 2000 bars for 1m/5m/15m, 1000 for 1h/2h/4h, and 500 for 1d/1w on first screen instead of 300-bar chunked defaults.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 08:23:43 +08:00
dekun 35088be097 Fix chart viewport regressions from tail refresh and period switch.
Remove pendingViewportEpoch, fetch only 30 tail bars on poll, restore wasViewingTail logic, and fix left-scroll range shift from actual merge delta.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 08:18:06 +08:00
dekun 7ea51818f1 Fix chart viewport hiding after timeframe switch.
Use pendingViewportEpoch so tail refresh applies default range on new timeframe instead of reusing the previous period's saved range.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 08:12:33 +08:00
dekun c2203abfa8 Preserve chart zoom and pan across tail refresh updates.
Keep the user viewport when SSE updates candles so zooming out to see the full series is not reset to the recent 200-bar view.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 08:06:57 +08:00
dekun 63472719ec Fetch native exchange OHLCV per timeframe instead of local aggregation.
Store and serve 15m/2h/4h directly from the exchange so market charts match venue candles.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:59:49 +08:00
dekun 3ac854d74c Remove 12h timeframe and stabilize chart wheel zoom.
Drop 12h from market chart options and storage, and avoid left-pan reload and tail refresh from resetting the viewport while zooming.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:54:37 +08:00
dekun 4afea6bb97 Fix chart tail viewport after 5m/15m timeframe switches.
Snap to latest candles on period change and only preserve scroll position when viewing history.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:46:14 +08:00
dekun 06897c59f1 Fix market chart viewport jump when switching timeframes.
Reset visible range and block stale left-pan/tail refresh from applying the previous period logical range to new candles.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:40:45 +08:00
dekun 11cc482599 Refactor market K-line storage with tiered retention and chunked loading.
Store 1m/5m/1h/12h/1d/1w with per-timeframe policies, aggregate 15m and 2h/4h on read, and support left-pan history fetches via before_ms.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:27:16 +08:00
dekun 41bdee2416 fix(hub): green open arrows for long, red for short on archive chart
Explicit open marker colors and broader direction parsing (short/空/sell).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 23:43:21 +08:00
dekun 69f554214c feat(hub): auto-mark all archive trades on chart
Add 自动 toggle on archive chart; when on, load history span for all trades and plot numbered open/close arrows for each.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 23:26:57 +08:00
dekun 5ceacd8077 fix(hub): load full pre-entry history for archive chart pan/zoom
range=history serves archive seed through close (not now). Default view focuses hold period; user can scroll/zoom left to see global morphology before entry.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 23:20:37 +08:00
dekun 54c0b169c7 feat(hub): add open/close arrows on archive chart with continuous klines
Span chart window across hold period, fill 5m gaps for smooth aggregation, and mark entry/exit with lightweight-charts arrows.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 23:14:40 +08:00
dekun 92ff945d72 fix(hub): correct _mark_meta_sync NameError in archive seed
seed_symbol_archive and sync_symbol_klines_incremental called undefined _mark_meta.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 23:05:25 +08:00
dekun 3052607280 fix(hub): merge strategy snapshots into archive for gate_bot
Include strategy_trade_snapshots when trade_records is empty, harden SQL for older schemas, and show per-exchange sync errors in the archive UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 23:02:46 +08:00
dekun 6a56928d59 feat(hub): add symbol archive with permanent 5m klines
Add /archive page, hub_symbol_archive.db, trade overlay, 4h background sync, and instance /api/hub/trades/archive. Document in hub-symbol-archive-kline.md with cross-links.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 22:51:48 +08:00
dekun 32b66fc343 docs(trend): add hub close and trade records review guide
Document manual hub close flow, trade_records vs strategy snapshots, DCA display rules, backfill script, and deployment checklist.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:39:46 +08:00
dekun 80226eebcf fix(trend): write trade_records when hub closes plan on gate_bot
Gate_bot insert_trade_record lacked entry_reason, causing _finalize_plan to save strategy snapshots but fail trade insert. Filter kwargs by signature, insert before plan commit, and add backfill script.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:32:44 +08:00
dekun 08082eb88f fix(trend): remove inferred DCA fill prices across all exchanges
Unify display on trend_leg_display_price: use recorded fills or grid triggers only; last done row avg comes from live entry, never back-solve trigger prices.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 18:10:33 +08:00
dekun 6a4ec69dba fix(trend): align hub and four-exchange trend plan display
Unify gate_bot with shared enrich_trend_plan for strategy pages and hub monitor, reconcile DCA avg with live entry price, and fix missing fill price display.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 18:05:30 +08:00
dekun e5576eaaed test(trend): cover binance oneway empty order params for DCA
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 17:54:04 +08:00
dekun 72f0090fb8 fix(trend): pass empty dict to ccxt create_order for DCA adds
Gate build_gate_order_params returns {} which is falsy; params or None broke trend_market_add with NoneType is not iterable while first-leg place_exchange_order worked.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 17:52:12 +08:00
dekun f5b4513ddb fix(trend): surface DCA block reasons and ensure gate_bot poll thread
Log poll exceptions, diagnose live-trading and mark-price blocks on the trend page, start background monitors on app import, and add /api/trend_poll_status for debugging.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 17:46:26 +08:00
dekun 0760873d9d fix(trend): use mark price for DCA trigger on gate_bot
Poll trend plans with mark price (same as UI) instead of ticker last, add get_symbol_mark_price to gate_bot, tolerate position API blips, and log DCA order failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 17:38:26 +08:00
dekun d56d9050aa fix(trend): use money RR, track DCA fills, snapshot before close
Align running-plan header and DCA table with risk-budget RR, record actual fill prices after each leg, and save pre-close snapshots on stop/TP/handoff across hub and exchanges.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 17:34:50 +08:00
dekun 84abf7e7f7 fix(trend): correct DCA triggers and partial-position PnL across exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 17:09:22 +08:00
dekun 9257a8051f feat(scripts): add backfill for missing trend strategy snapshots
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 16:53:50 +08:00
dekun f976697203 fix(gate_bot): write strategy snapshots when trend plans end
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 16:48:28 +08:00
dekun f51d1c413a feat(hub): show funding, trading account and unrealized PnL on monitor cards
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 10:14:44 +08:00
dekun 67cc084347 fix(gate): align fund transfer between gate and gate_bot
- Extract shared gate_transfer_lib and global transfer form on all pages

- Block auto-transfer when trend pullback plans have open positions

- Redirect manual transfer back to the current page after submit

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 10:01:32 +08:00
dekun bee9539852 fix(hub): use cyan accent in AI chat markdown
Apply accent color to chat headings, bold text, and coach role label instead of purple.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 09:19:37 +08:00
dekun b98efbd27d fix(hub): use cyan accent for AI summary headings in dark theme
Keep wine-red emphasis in light theme; align dark mode with the hub color system.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 09:16:36 +08:00
dekun b828026c23 fix(hub): refine AI summary heading style for light and dark themes
Replace red text with white stroke by theme-aware accent bar and soft background tint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 09:12:29 +08:00
dekun f8220762c0 fix(hub): align AI summary trades with records and restyle headings
- Query trade_records with trading-day window and reviewed fields instead of missing session_date column

- Style summary headings and account names as red text with white stroke

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 09:07:16 +08:00
dekun 62e48dab92 feat(hub): enrich AI coach with fund history, closed trades, and chat uploads
- Add 15-day fund snapshot store and /api/hub/account on all instances

- Summary includes yesterday/today trades, fund columns, and section 5 操作建议

- Chat context distinguishes empty positions from local monitors

- Support image/document attachments in AI chat

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 08:54:20 +08:00
dekun 51c59b073b fix(hub): simplify AI coach header actions
Hide model name labels and style New Chat as a primary button matching Generate Summary.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 00:40:58 +08:00
dekun dfcf0f88fb fix(hub): add section icons and color PnL in AI summary table
Show emoji headings for overview, accounts, alerts, and data notes; apply green/red styling to table PnL columns.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 00:33:44 +08:00
dekun a5f5239be9 feat(hub): render AI summary account breakdown as icon table
Replace pipe-separated account lines with a structured table from stats_snapshot, including exchange icons and position remarks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 00:28:24 +08:00
dekun 8417784dd8 fix(hub): render AI coach summary and chat as Markdown
Reuse shared ai_review_render.js so summaries and coach replies display formatted lists and bold text instead of raw syntax.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 00:23:44 +08:00
dekun 821e260912 fix(hub): keep AI coach page within one viewport
Lock page height and scroll summary/chat inside equal panels instead of extending the document.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 00:15:40 +08:00
dekun ebadcb1119 fix(hub): polish AI coach UI with PnL colors and chat roles
Equal-height summary/chat panels, colored closed/float PnL, owner/coach labels, and optimistic thinking state.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 00:10:50 +08:00
dekun cee641ba5d feat(hub): add AI coach page with daily summary and chat
Aggregate four-account trades via hub_ai module and /api/hub/trades/today; store sessions in JSON; default OpenAI config matches instances.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:51:36 +08:00
dekun 4fad5696df fix(gate_bot): exclude active trend plans from orphan position warning
Trend pullback plans manage positions before order_monitors handoff; treat them as covered and add a pre-deploy DB backup script.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 09:40:14 +08:00
dekun 32f4eec1d3 fix: trend preview uses USDT profit, snapshot risk budget, and money RR across four exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 16:20:59 +08:00
dekun 31756e838d feat: trend pullback preview TP per DCA leg with unified stop loss across exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 16:10:27 +08:00
dekun 674d721072 fix(ui): light-theme contrast for AI daily and weekly review markdown across four exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 14:05:02 +08:00
dekun 3f1bf9905d fix: AI review list UTC filter, vision timeout, and stuck loading state
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 13:59:17 +08:00
dekun 4ac4c062e0 fix: AI review loading UX and Gate preview snapshot time filter SQL
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 13:51:16 +08:00
dekun 86a6081090 fix: AI daily review sqlite3.Row crash and error feedback across four exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 13:39:50 +08:00
dekun 995ee8d2e1 fix: prevent theme flash when navigating instance pages from hub iframe
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 13:23:51 +08:00
dekun e30d24173f fix(ui): light-theme badges, PnL colors, and journal detail across four exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 13:16:29 +08:00
dekun 1b51f73ecd fix: journal detail contrast and unify AI review journal format across four exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 13:09:59 +08:00
dekun 934e48b9a8 fix: show risk amount only in full-margin mode across four exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 09:08:45 +08:00
dekun 673bcbdc70 fix: align unrealized PnL across four exchange instances via hub_position_metrics
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 20:25:06 +08:00
dekun 806350231e fix(hub): show contract-based unrealized PnL in monitor and chart
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 20:20:36 +08:00
dekun ef8656e95d fix(hub): define mo in buildPositionMarketContext
Fixes monitor grid crash ReferenceError mo is not defined when opening market from position card.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 20:15:10 +08:00
dekun fb20a12b02 fix(hub): show monitor page instead of overlapping market page
page-market id contained substring monitor so setActiveNav kept both pages visible and hid the grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 20:13:03 +08:00
dekun c9ca106b81 fix(hub): recalc market floating PnL from live chart mark
Use plan margin x leverage x price change with latest K-line close instead of stale board snapshot floating_pnl.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 20:09:35 +08:00
dekun e6361a7fcc fix(hub): live market PnL and Gate drag SL place
Parse Gate unrealised_pnl in agent; refresh hub market floating PnL from board and trends; clamp Gate TP/SL triggers before place.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 20:02:23 +08:00
dekun 24270944e7 fix(hub,gate): cross-margin TP/SL and dedupe hub conditional orders
Gate hedge position triggers use close=false; stop silent ccxt fallback on cross margin. Hub merges agent and Flask TP/SL by trigger price and labels Gate orders correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 19:48:04 +08:00
dekun ed0805538f fix(hub): sync TP/SL display after trend handoff to order monitor
Use order monitor plan prices on handoff cards and fill exchange TP/SL rows when Gate shows reduce-only orders without algo labels.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 19:39:11 +08:00
dekun e39fac2c16 feat(hub): show floating PnL on market page and drag stop-loss to place TP/SL
Pass unrealized PnL from monitor jump context, refresh from board snapshot, and let users drag the SL price line to call the same place-tpsl API as the monitor entrust dialog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 19:31:45 +08:00
dekun 93e148a3e7 fix: unify order/key focus K-line theme, PnL, RR and exchange price tick
Share focus_chart templates and APIs across four instances; align chart Y-axis, price lines and meta bar with exchange symbol precision and live unrealized PnL.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 16:45:51 +08:00
dekun 3d55aa0975 fix(gate-bot): format mark price with exchange symbol precision
Use price_to_precision in price_snapshot so live mark price matches entry/SL display instead of fixed 8 decimals.
2026-06-04 16:33:09 +08:00
dekun 88fc21e278 fix(gate-bot): allow profit-side stop loss on TP/SL entrust
Skip min planned RR when stop is on the winning side of entry; validate entrust against open price and fall back to plan take-profit when omitted.
2026-06-04 16:28:47 +08:00
dekun 1042f135ed fix(gate-bot): PnL colors, sync exchange TP/SL to plan display
Color floating PnL on position cards, mirror exchange stop/take prices in the grid and DB, and purge false external-close records on monitor relink.
2026-06-04 16:23:52 +08:00
dekun 1618ef8668 fix(gate-bot): show orphan exchange positions and relink monitor
When the exchange still has a position but order_monitors is not active, surface it on the trade page and allow restoring the latest stopped record.
2026-06-04 16:17:34 +08:00
dekun e327f1b1fb feat(gate-bot): align order monitor with Gate main site
Dual-panel trade UI, exchange TP/SL entrust modal, and place/cancel_tpsl APIs so bot manual trading matches Gate.
2026-06-04 16:09:17 +08:00
dekun f9301b92b9 feat(hub): trend plan breakeven and stop from monitor fullscreen
Proxy /api/hub/trend/stop and breakeven to instances; enable offset input and actions in hub UI. Add horizontal padding on strategy records page.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 14:46:24 +08:00
dekun 52d97482f2 feat(strategy): WeChat notify on trend and roll plan start/end
Add shared strategy_wechat_notify helpers; hook trend execute/finalize and roll group open/close across four exchanges and Gate bot.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 13:35:21 +08:00
dekun 3b4120a36e fix(instance): light-theme journal AI buttons and mood checkboxes
Fix secondary button contrast, exclude checkboxes from dark input fill, style mood-grid; bump theme assets to v4.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 13:12:44 +08:00
dekun 21b3e97571 fix(instance): light-theme P/L colors, AI review panels, bot stats
Restore plan floating P/L and st-done greens; fix panel-item and stats-split-col in light mode; observe journal lists; bump theme assets to v3.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 13:07:00 +08:00
dekun be3ce18665 style(instance): unify light theme across four exchanges
Extend instance_theme CSS/JS for trade, strategy, records, journal and stats tabs; remap inline dark colors; bump static assets to v2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 12:59:42 +08:00
dekun d14c629778 feat: add light/dark theme to exchange instances with hub SSO sync
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 12:52:27 +08:00
dekun 6f8f0968c8 feat(hub): background chart poll with SSE for positions and market watch
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 12:39:26 +08:00
dekun 9d12323ce6 style(hub): fix light-theme load button and market position panel colors
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 12:14:48 +08:00
dekun e99c6cef08 style(hub): refine light theme contrast and theme-aware fullscreen UI
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 12:08:35 +08:00
dekun d1914df46f feat(hub): add dark/light theme toggle with moon and sun icons
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 12:03:48 +08:00
dekun b394e495ca chore: remove instance Windows scripts and screen/systemd helpers
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 11:56:32 +08:00
dekun 4569e070fd chore: remove Windows deployment scripts and update hub usage examples
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 11:53:42 +08:00
dekun 1282293e91 docs: unify Ubuntu root /opt PM2 deployment and refresh README
Add docs/ubuntu-server.md; remove Windows and alternate process managers from deployment guides; index strategy, key monitor, TP/SL, breakeven, and replay docs in root README.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 11:48:54 +08:00
dekun 546bc7bcf1 fix(hub): two-column trend plan card; docs for records and hub layout
Left column shows plan metrics, right column DCA table, footer for breakeven and snapshot info; update strategy and exchange usage docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:59:47 +08:00
dekun f1e95afb89 feat: strategy records dual panels with filters and 100-row cap
Split trend and roll snapshot lists with expandable rows, client filters, and DB prune to latest 100 entries.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:55:34 +08:00
dekun 7037dc2334 fix(hub): align trend pullback card with instance layout
Match strategy page plan card: 3x3 metrics, DCA table, breakeven row, snapshot footer; PnL percent on plan margin.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:51:13 +08:00
dekun 3fb2023efb feat: strategy trade snapshots, DCA detail, and hub trend layout
Persist ended trend pullback and roll group snapshots to a unified records page; show replenishment tiers on instance and hub cards with horizontal single-position layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:45:27 +08:00
dekun 1a6b5f55a1 fix: regenerate clean app icons with Pillow (fix corrupted favicon)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:22:08 +08:00
dekun e03cce20d6 feat: add brand icons for Chrome shortcuts and PWA manifest
Dark cyan-green candlestick icon for hub and four exchanges; generate/sync scripts and docs/shortcut-icon.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:17:22 +08:00
dekun ed669fab80 fix(hub): show trend plan leverage, base, ratio, mark and floating PnL
Position and trend plan cards read sizing from trend_pullback_plans; merge agent mark/PnL; compute position_ratio_pct in hub enrich.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:13:44 +08:00
dekun 98c904c2d1 feat(hub): align trend pullback display with instance in fullscreen
Position cards show trend plan source, risk%, program TP price and RR; trend section uses plan grid; hub API enriches floating PnL and planned_rr.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:09:04 +08:00
dekun 02bc3c14bc docs: document four-exchange env sync script usage
Add docs/env-sync-scripts.md; cross-link from deploy README, feature docs, README, and script headers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:02:08 +08:00
dekun 5e694ff795 docs: reference unified env sync script in position-sizing guide
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 09:59:53 +08:00
dekun e6e79215fc chore: add unified four-exchange env sync scripts
sync_four_exchange_env runs position sizing + transfer sync; transfer script preserves existing values and supports --set-amount/--enable-auto-transfer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 09:59:33 +08:00
dekun 29b0634c6d feat: bidirectional daily auto-transfer with position skip
Rebalance swap to AUTO_TRANSFER_AMOUNT at Beijing hour: top up from funding or sweep excess back. Skip and WeChat notify when active positions exist.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 09:57:25 +08:00
383 changed files with 75013 additions and 16951 deletions
+6
View File
@@ -16,6 +16,12 @@
**/.env.bak
**/.env.local
manual_trading_hub/hub_settings.json
manual_trading_hub/hub_backup_state.json
manual_trading_hub/hub_fund_history.json
manual_trading_hub/hub_supervisor_state.json
manual_trading_hub/hub_ai_summaries.json
manual_trading_hub/hub_ai_chat.json
manual_trading_hub/hub_ai_fund_history.json
manual_trading_hub/data/
# 数据库与上传(运行时生成)
+59 -109
View File
@@ -1,138 +1,88 @@
# 复盘交易系统(crypto_monitor
本仓库为 **多交易所 USDT 永续** 的下单监控、关键位监控与交易复盘工具集:四个子项目分别对接 **Binance、Gate.io(主号)、Gate.io(机器人/趋势策略)、OKX**,共享相似的 Flask 架构与本地 SQLite 记账思路,可按账户独立部署、独立端口运行
多交易所 **USDT 永续** 的下单监控、**关键位**、**策略交易**、**止盈止损 / 移动保本** 与 **AI 复盘**,四所独立部署 + 可选 **中控** 聚合监控
**远程仓库(克隆地址)**[https://git.bz121.com/dekun/crypto_monitor.git](https://git.bz121.com/dekun/crypto_monitor.git)
**远程仓库**[https://git.bz121.com/dekun/crypto_monitor.git](https://git.bz121.com/dekun/crypto_monitor.git)
---
## 部署环境(必读)
| 项 | 约定 |
|----|------|
| 系统 | **Ubuntu 22.04 / 24.04** |
| 用户 | **root** |
| 路径 | **`/opt/crypto_monitor`** |
| 进程 | **PM2**(唯一推荐的常驻方式) |
**环境详解**Python 3.10+、Node、PM2 安装与启动顺序):**[docs/ubuntu-server.md](./docs/ubuntu-server.md)**
**一键 venv**`bash deploy/setup_env.sh`**[deploy/README.md](./deploy/README.md)**
```bash
git clone https://git.bz121.com/dekun/crypto_monitor.git
cd crypto_monitor
cd /opt
git clone https://git.bz121.com/dekun/crypto_monitor.git crypto_monitor
cd /opt/crypto_monitor
bash deploy/setup_env.sh --install-system-deps
```
### 一键环境部署
| 系统 | 命令 |
|------|------|
| **Windows** | 双击根目录 **`一键部署.bat`**,或 `.\deploy\setup_env.ps1` |
| **Linux / macOS** | `bash deploy/setup_env.sh` |
会为各子项目创建 `.venv`、安装依赖、从 `.env.example` 生成 `.env`(不覆盖已有)。详见 **[deploy/README.md](./deploy/README.md)**。
计仓模式(以损定仓 / 全仓杠杆,四所统一):见 **[docs/position-sizing-mode.md](./docs/position-sizing-mode.md)**。
配置与运维脚本: **[docs/env-sync-scripts.md](./docs/env-sync-scripts.md)** · **[备份与恢复.md](./备份与恢复.md)**
---
## 一、仓库目录一览
## 功能导航
| 目录 | 交易所 / 角色 | 说明文档 |
|------|-----------------|----------|
| `crypto_monitor_binance/` | Binance USDT-M 永续 | [部署文档.md](./crypto_monitor_binance/部署文档.md) · [README.md](./crypto_monitor_binance/README.md) |
| `crypto_monitor_gate/` | Gate.io 永续(主号) | [部署文档.md](./crypto_monitor_gate/部署文档.md) |
| `crypto_monitor_gate_bot/` | Gate.io 永续(机器人;含趋势回调等) | [部署文档.md](./crypto_monitor_gate_bot/部署文档.md) · [趋势回调策略说明.md](./crypto_monitor_gate_bot/趋势回调策略说明.md) · [策略交易说明.md](./策略交易说明.md) |
| `crypto_monitor_okx/` | OKX 永续(功能对齐币安) | [部署文档.md](./crypto_monitor_okx/部署文档.md) · [使用说明.md](./crypto_monitor_okx/使用说明.md) · [README.md](./crypto_monitor_okx/README.md) |
| `manual_trading_hub/` | 多账户中控(监控 + **行情 K 线** + 紧急全平 + 登录;**不在中控网页下单** | [README.md](./manual_trading_hub/README.md) · [使用说明.md](./manual_trading_hub/使用说明.md) · [行情区说明.md](./manual_trading_hub/行情区说明.md) · [部署文档.md](./manual_trading_hub/部署文档.md) · [常见问题.md](./manual_trading_hub/常见问题.md) |
| 根目录 `strategy_*.py` | **策略交易**(趋势回调 + 顺势加仓共用逻辑) | [策略交易说明.md](./策略交易说明.md) |
| 根目录 `ai_client.py` | **AI 复盘**OpenAI 兼容网关 / Ollama 二选一) | [AI复盘与模型配置说明.md](./AI复盘与模型配置说明.md) |
| 功能 | 说明 | 文档 |
|------|------|------|
| **关键位监控** | 箱体/收敛自动开仓、阻力支撑提醒、斐波限价;止盈止损方案与 **移动保本** 开关 | 各所 [关键位自动下单说明.md](./crypto_monitor_binance/关键位自动下单说明.md)(Gate/OKX 目录内同名);方案细则 **[关键位止盈止损与移动保本更新说明.md](./关键位止盈止损与移动保本更新说明.md)** |
| **实盘下单 / 下单监控** | 首仓、以损定仓;监控内 **止盈 / 止损**、**移动保本**(步进 R、偏移%) | 各所 [使用说明.md](./crypto_monitor_binance/使用说明.md) · 顶栏「实盘下单」`/trade` |
| **策略交易** | **趋势回调** + **顺势加仓**`/strategy` 双栏) | **[策略交易说明.md](./策略交易说明.md)** · 趋势细则 [crypto_monitor_gate_bot/趋势回调策略说明.md](./crypto_monitor_gate_bot/趋势回调策略说明.md) |
| **策略交易记录** | 已结束计划快照(最近 100 条)、筛选与展开详情 | [策略交易说明.md §五](./策略交易说明.md) · 顶栏 `/strategy/records` |
| **交易复盘** | 平仓记录、错过机会、图表;**AI 点评** | **[AI复盘与模型配置说明.md](./AI复盘与模型配置说明.md)** · 顶栏「交易记录与复盘」`/records` |
| **中控** | 多账户持仓/委托聚合、行情 K 线、紧急全平(**不在中控网页下单**) | [manual_trading_hub/使用说明.md](./manual_trading_hub/使用说明.md) · [部署文档.md](./manual_trading_hub/部署文档.md) |
前四列为四个 **`crypto_monitor_*`** 交易/监控应用;`manual_trading_hub` 与四者 **进程独立**,无需改四者代码即可并行使用。
其它专题:[计仓模式](./docs/position-sizing-mode.md) · [每日自动划转](./docs/auto-transfer-daily.md) · [Chrome 快捷方式图标](./docs/shortcut-icon.md)
---
## 二、四个 `crypto_monitor_*` 子项目:共同点
## 仓库目录
- **技术栈**Python 3.10+、`Flask` Web、`ccxt` 调交易所 API、本地 SQLite(默认 `crypto.db`)等。
- **能力类型**(各所细节见各自 README / 部署文档):
- **关键位监控**、**下单监控**(含风控与移动保本等逻辑)、**交易复盘**(AI 点评,见 [AI复盘与模型配置说明.md](./AI复盘与模型配置说明.md));
- **策略交易**(顶栏 `/strategy`:趋势回调 + 顺势加仓双栏,四所共用根目录逻辑,见 [策略交易说明.md](./策略交易说明.md));
- **实盘(可选)**:在对应 `.env` 中开启 `LIVE_TRADING_ENABLED=true` 并配置各所 API 后,由程序发起真实委托(请务必理解风险并做好权限与 IP 白名单控制)。
- **网络**:若本机直连交易所不稳定,可通过 **SSH 动态转发 SOCKS** 或 HTTP/S 代理;经 SOCKS 时依赖中需包含 **`PySocks`**(各《部署文档》中有说明)。
- **进程托管**Linux 上常用 **PM2** 托管 `app.py`;各目录内一般有 `ecosystem.config.cjs` 或文档中的等价命令。
| 目录 | 交易所 / 角色 | 部署文档 |
|------|----------------|----------|
| `crypto_monitor_binance/` | Binance U 本位永续 | [部署文档.md](./crypto_monitor_binance/部署文档.md) |
| `crypto_monitor_gate/` | Gate 主号 | [部署文档.md](./crypto_monitor_gate/部署文档.md) |
| `crypto_monitor_gate_bot/` | Gate 机器人 / 趋势户 | [部署文档.md](./crypto_monitor_gate_bot/部署文档.md) |
| `crypto_monitor_okx/` | OKX 永续 | [部署文档.md](./crypto_monitor_okx/部署文档.md) |
| `manual_trading_hub/` | 中控 + 子代理 | [部署文档.md](./manual_trading_hub/部署文档.md) |
| `lib/` | **共用模块**(策略、关键位、交易、中控库、AI、静态与模板) | **[docs/lib-structure.md](./docs/lib-structure.md)** |
| `brand/` | 各所共用图标与 manifest | — |
| `docs/``deploy/``scripts/``tests/` | 文档、环境、脚本、单元测试 | — |
共用代码 import 示例:`from lib.strategy.strategy_db import init_strategy_tables`(各所启动时仍将仓库根加入 `PYTHONPATH`)。详见 **[docs/lib-structure.md](./docs/lib-structure.md)**。
---
## 三、四个子项目:差异速查
## 技术要点
| 项目 | 环境变量前缀(示例) | 典型用途区分 |
|------|----------------------|--------------|
| `crypto_monitor_binance` | `BINANCE_*` | 币安 U 本位永续;止盈止损以 `STOP_MARKET` / `TAKE_PROFIT_MARKET` 等与币安规则对齐 |
| `crypto_monitor_gate` | `GATE_*` | Gate 主账户监控与交易页面 |
| `crypto_monitor_gate_bot` | `GATE_*` | Gate 侧 **独立子账户 / 机器人**;文档中含 **趋势回调** 等策略说明 |
| `crypto_monitor_okx` | `OKX_*` | OKX 永续;需 API Key / Secret / Passphrase |
各目录根下:
- **`.env.example`**:配置模板(**可** `git pull` 同步),新机执行 `cp .env.example .env` 后编辑。
- **`.env`**:本机真实配置(**勿**提交 Git);`app.py` 只读此文件。`git pull` **不会**覆盖 `.env`;升级前建议 `cp .env .env.backup.$(date +%Y%m%d)`
变量名以前缀区分,**不可混用**(例如在 Gate 项目中写 OKX 变量会导致代理与密钥不生效)。
- **Python 3.10+**、Flask、ccxt、SQLite`crypto.db`
- 四所 `.env` 前缀不同(`BINANCE_*` / `GATE_*` / `OKX_*`),**不可混用**
- 实盘须 `LIVE_TRADING_ENABLED=true` 且理解 API 权限与 IP 白名单风险
-**SOCKS** 访问交易所时配置各所 `*_SOCKS_PROXY` 并安装 PySocks
---
## 四、与 `manual_trading_hub` 的关系(可选)
## 推荐阅读顺序
- **中控** `hub.py``:5100`):多账户 **监控聚合**、**行情 K 线**`/market`)、**紧急全平**、系统设置;可选 **用户名+密码** 登录(反代公网时务必配置)。
- **子代理** `agent.py`:每账户一进程,默认 **`15200``15203`**,与四所 Flask **`APP_PORT`**5000/5001/5002/5004**必须错开**。
- **下单、关键位、策略交易、复盘**:在各 `crypto_monitor_*` 原网页操作(中控 **「实例」** / **「复盘」**);中控**已移除下单区**。增加子账户见 [manual_trading_hub/使用说明.md §4.3](./manual_trading_hub/使用说明.md#43-增加账户例如再挂一个-gate)。
- 账户列表由 **`hub_settings.json`**(网页「系统设置」)维护,**不再使用** `HUB_AGENTS`
- 部署与排障:[manual_trading_hub/README.md](./manual_trading_hub/README.md)、[常见问题.md](./manual_trading_hub/常见问题.md)。
1. [docs/ubuntu-server.md](./docs/ubuntu-server.md) — 装 Python / Node / PM2PM2 启动四所 + 中控
2. 各所 **`.env`**(从 `.env.example` 复制)
3. 所用功能对应上表 **功能导航** 文档
4. [备份与恢复.md](./备份与恢复.md) — 生产机备份习惯
---
## 五、Linux 推荐目录布局(可选)
## 安全
为与仓库内《部署文档》示例一致,可将整个克隆结果置于 **`/opt/crypto_monitor/`** 下,例如:
- **勿** 将 `.env`、API Secret、`.pem` 提交 Git
- 公网暴露中控须配置登录、`HUB_BRIDGE_TOKEN`、HTTPS Cookie
- 实盘风险由使用者自行承担
- `/opt/crypto_monitor/crypto_monitor_binance`
- `/opt/crypto_monitor/crypto_monitor_gate`
- `/opt/crypto_monitor/crypto_monitor_gate_bot`
- `/opt/crypto_monitor/crypto_monitor_okx`
- `/opt/crypto_monitor/manual_trading_hub`
具体 `mkdir``venv``pm2`**SSH SOCKS** 步骤以各子目录 **《部署文档.md》** 为准。
### 备份与恢复(服务器必读)
项目路径 **`/opt/crypto_monitor`**,数据备份 **`/root/backups`**
| 类型 | 说明 |
|------|------|
| **数据库 + 复盘图片** | 每天北京时间 0:00 自动备份,保留 30 天 |
| **`.env`** | 升级 / 改配置前手动备份与恢复 |
**一键复制命令(Ubuntu SSH** 见根目录 **[备份与恢复.md](./备份与恢复.md)**:含安装 cron、手动备份、`.env` 备份/恢复、从备份还原数据库等整段脚本。
---
## 六、推荐阅读顺序
1. 克隆本仓库后,执行 **一键环境部署**(上表),或手动在各子目录 `python -m venv .venv``pip install`
2. 根据实际交易所进入对应 **`crypto_monitor_*`** 目录,编辑 **`.env`**(填入 API 与密码等;部署脚本已可从 `.env.example` 复制)。
3. 阅读该目录下的 **《部署文档.md》**Ubuntu / PM2 / 代理 / 升级说明)。
4. 服务器部署完成后,按 **[备份与恢复.md](./备份与恢复.md)** 配置自动备份与 `.env` 备份习惯。
5. 需要 **策略交易**(趋势回调 / 顺势加仓)时,阅读 [策略交易说明.md](./策略交易说明.md);趋势细则另见 [crypto_monitor_gate_bot/趋势回调策略说明.md](./crypto_monitor_gate_bot/趋势回调策略说明.md)。
6. 需要 **AI 复盘**OpenAI 网关或 Ollama)时,阅读 [AI复盘与模型配置说明.md](./AI复盘与模型配置说明.md),并在各所 `.env` 配置 `AI_PROVIDER` 等。
7. 需要 **多账户一块看 + 紧急全平** 时,阅读 [manual_trading_hub](./manual_trading_hub/) 下 [使用说明](./manual_trading_hub/使用说明.md)、[部署文档](./manual_trading_hub/部署文档.md);遇问题先查 [常见问题](./manual_trading_hub/常见问题.md)。
---
## 七、安全与合规
- **切勿**将 `.env``.env.backup*`、API Secret、SSH 私钥 `.pem` 等提交到版本库或公开渠道;仅 **`.env.example`** 可提交(占位符,无真实密钥)。
### 从旧版仓库升级(曾把 `.env` 提交进 Git
**`git pull` 之前**按 **[备份与恢复.md](./备份与恢复.md)** 备份 `.env` 与数据库;pull 后若本地 `.env` 被误删,用备份恢复;再对照新的 **`.env.example`** 补全可能新增的变量名。
- 实盘前务必在 **`LIVE_TRADING_ENABLED=false`** 下验证页面与网络;API 权限与 IP 白名单遵循各交易所要求。
- 使用本仓库进行实盘交易的风险由使用者自行承担;请遵守当地法律法规与交易所用户协议。
---
## 八、仓库信息摘要
| 项 | 内容 |
|----|------|
| 远程地址 | `https://git.bz121.com/dekun/crypto_monitor.git` |
| 说明 | 复盘交易系统(骆驼比特币私有代码仓库,Gitea) |
| 本说明 | 仓库根目录 `README.md`,仅描述结构与文档索引,不包含业务代码变更说明 |
若各子项目 README 与根说明不一致,以 **子目录内当前代码与《部署文档》** 为准。
若子目录 README 与本文冲突,以 **子目录《部署文档》与当前代码** 为准。
-251
View File
@@ -1,251 +0,0 @@
"""大模型调用:OpenAI 兼容接口(默认)或本机 Ollama 二选一。
配置从 os.environ 惰性读取:各实例 app.py 在 import 本模块后才 load_env_file(.env)
若在 import 时缓存变量会导致 OPENAI_API_KEY 始终为空。
"""
from __future__ import annotations
import base64
import os
from typing import List, Optional, Sequence
import requests
def _env_str(name: str, default: str = "") -> str:
v = os.getenv(name)
if v is None:
return default
return str(v).strip()
def _ai_timeout_seconds() -> int:
try:
return max(10, int(_env_str("AI_TIMEOUT_SECONDS", "120") or "120"))
except ValueError:
return 120
def _ai_provider() -> str:
return (_env_str("AI_PROVIDER", "openai") or "openai").lower()
def _openai_api_base() -> str:
base = _env_str("OPENAI_API_BASE", "https://op.bz121.com/v1") or "https://op.bz121.com/v1"
return base.rstrip("/")
def _openai_api_key() -> str:
return _env_str("OPENAI_API_KEY") or _env_str("AI_API_KEY")
def _openai_model() -> str:
return _env_str("OPENAI_MODEL", "gemma4:e4b") or "gemma4:e4b"
def _ollama_api() -> str:
return _env_str("OLLAMA_API", "http://127.0.0.1:11434/api/generate") or "http://127.0.0.1:11434/api/generate"
def _ollama_model() -> str:
return _env_str("AI_MODEL", "huihui_ai/deepseek-r1-abliterated:latest") or "huihui_ai/deepseek-r1-abliterated:latest"
def _use_openai() -> bool:
return _ai_provider() in ("openai", "openai_compatible", "gateway")
def _image_mime_for_path(path: str) -> str:
ext = os.path.splitext(str(path or ""))[1].lower()
if ext == ".png":
return "image/png"
if ext in (".jpg", ".jpeg"):
return "image/jpeg"
if ext == ".webp":
return "image/webp"
if ext == ".gif":
return "image/gif"
return "image/jpeg"
def _read_image_base64(image_path: str) -> Optional[tuple]:
try:
with open(image_path, "rb") as f:
b64 = base64.b64encode(f.read()).decode("utf-8")
return b64, _image_mime_for_path(image_path)
except Exception:
return None
def _collect_images(
image_paths: Optional[Sequence[str]] = None,
images_b64: Optional[Sequence[str]] = None,
) -> List[tuple]:
out: List[tuple] = []
for p in image_paths or []:
item = _read_image_base64(p)
if item:
out.append(item)
for b in images_b64 or []:
if b:
out.append((str(b), "image/jpeg"))
return out
def _openai_chat_url() -> str:
base = _openai_api_base()
if base.endswith("/chat/completions"):
return base
return f"{base}/chat/completions"
def _generate_openai(prompt: str, images: List[tuple], temperature: float) -> str:
api_key = _openai_api_key()
if not api_key:
return "AI 调用失败:未配置 OPENAI_API_KEY(请在当前实例目录 .env 中设置,修改后需重启服务)"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
if images:
content: List[dict] = [{"type": "text", "text": prompt}]
for b64, mime in images:
content.append(
{
"type": "image_url",
"image_url": {"url": f"data:{mime};base64,{b64}"},
}
)
messages = [{"role": "user", "content": content}]
else:
messages = [{"role": "user", "content": prompt}]
body = {
"model": _openai_model(),
"messages": messages,
"temperature": temperature,
"stream": False,
}
r = requests.post(
_openai_chat_url(),
headers=headers,
json=body,
timeout=_ai_timeout_seconds(),
)
r.raise_for_status()
data = r.json()
choices = data.get("choices") or []
if not choices:
return "AI 生成失败:响应无 choices"
msg = choices[0].get("message") or {}
return (msg.get("content") or "").strip() or "AI 生成失败:空内容"
def _generate_ollama(prompt: str, images: List[tuple], temperature: float) -> str:
payload = {
"model": _ollama_model(),
"prompt": prompt,
"stream": False,
"options": {"temperature": temperature},
}
if images:
payload["images"] = [b64 for b64, _mime in images]
r = requests.post(_ollama_api(), json=payload, timeout=_ai_timeout_seconds())
r.raise_for_status()
return (r.json().get("response") or "").strip() or "AI 生成失败"
def ai_generate(
prompt: str,
*,
image_paths: Optional[Sequence[str]] = None,
images_b64: Optional[Sequence[str]] = None,
temperature: float = 0.2,
) -> str:
"""统一文本生成;失败时返回以「AI 调用失败」开头的说明。"""
images = _collect_images(image_paths, images_b64)
try:
if _use_openai():
return _generate_openai(prompt, images, temperature)
return _generate_ollama(prompt, images, temperature)
except requests.HTTPError as e:
detail = ""
try:
detail = (e.response.text or "")[:500]
except Exception:
pass
prov = "OpenAI" if _use_openai() else "Ollama"
return f"AI 调用失败({prov} HTTP {e.response.status_code if e.response else '?'}):{detail or str(e)}"
except Exception as e:
prov = "OpenAI" if _use_openai() else "Ollama"
return f"AI 调用失败({prov}):{str(e)}"
def ai_review(trades_text: str, period_title: str, image_paths=None) -> str:
n_img = len(image_paths or [])
period_label = "" if "" in str(period_title) else ""
attach_note = (
f"️ 【系统说明:已向模型附带 {n_img} 张复盘附图(自动K线或上传截图),请结合附图分析第5节。】\n\n"
if n_img
else "ℹ️ 【系统说明:本次未附带复盘附图,第5节请写明「无附图,无法看图」;保存复盘记录时可勾选「自动生成K线图」。】\n\n"
)
prompt = f"""
你是一位专业交易教练。下面是用户的{period_title}交易记录,请做简洁、可执行的复盘(中文)。
【硬性规则 — 必须遵守】
- 你只能根据「交易记录」里**明确出现的字段**陈述事实;禁止编造:是否触发止损、是否扛单、亏损是否扩大、图上具体结构/进出场点位等记录里**没有**的信息。
- 「平仓/离场」只是交易员自述摘要,不是客观成交明细;若记录未写明代币是否打到止损价、是否软件平仓等,不要断言执行路径,可用「在记录有限前提下,一种可能是……」或简短写「执行路径记录不足,无法判断」。
- 「提前离场」类结论必须优先依据记录中的「提前离场记录」字段;若该段全为「无」或未出现有效内容,不得写道「明显扛单」「拒不止损」「未执行硬止损」等。
- 实际RR为负只说明结果相对于预期RR不利,不等同于「风控失灵」或「止损纪律崩溃」,除非记录里另有依据。
- 禁止用语:人身攻击、夸张定性(如「致命伤」「灾难」);语气克制、对事不对人。
- 若有截图且你能辨认,再结合图讨论;看不清或无明确定位则明确说「无法从图确认」,不得虚构 K 线故事。
【输出格式 — Markdown,必须严格遵守】
- 第一行:**交易复盘报告({period_label}度)**
- 五个大节标题必须**完全一致**(含 emoji,不要用其它编号或改名):
**1. 📊 总体盈亏结构**
**2. 🧠 心态与执行**
**3. 🏷️ 行为标签**
**4. ✅ 改进建议**
**5. 📈 图表分析**
- 每节正文用 `- **子项名**:内容` 列表;第4节改进建议用有序列表 `1. 2. 3.`
- 第1节至少包含:**笔数/盈亏**、**风险回报比**、**总结**
- 第2节至少包含:**得分**(1–10)、**依据**(对应记录字段)
- 第5节至少包含:**趋势确认**、**执行路径**(记录不足则写明)
- 语气简洁,少形容词;不要输出代码块、不要表格
交易记录:
{trades_text}
""".strip()
return attach_note + ai_generate(prompt, image_paths=image_paths, temperature=0.2)
def ai_short_advice(prompt_text: str) -> str:
prompt = f"""
你是交易风控助理。请用中文给出**最多 3 条**提醒,要求:
- 每条不超过 25 个字
- 语气克制、具体、可执行
- 不要输出 Markdown,不要编号前缀以外的废话
场景:
{prompt_text}
""".strip()
return ai_generate(prompt, temperature=0.2)
def ai_provider_label() -> str:
if _use_openai():
return f"OpenAI 兼容 · {_openai_model()} @ {_openai_api_base()}"
return f"Ollama · {_ollama_model()}"
def ai_config_status() -> dict:
"""调试用:当前进程内读到的 AI 配置(不含密钥明文)。"""
key = _openai_api_key()
return {
"provider": _ai_provider(),
"openai_base": _openai_api_base(),
"openai_model": _openai_model(),
"openai_key_configured": bool(key),
"ollama_api": _ollama_api(),
"ollama_model": _ollama_model(),
}
+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#22d3ee"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="108" fill="#0c1019"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="#141b2d"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="none" stroke="url(#g)" stroke-width="12"/>
<path d="M120 320 L200 248 L280 272 L392 168" fill="none" stroke="url(#g)" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="392" cy="168" r="18" fill="#34d399"/>
<rect x="168" y="268" width="28" height="64" rx="6" fill="#f87171"/>
<line x1="182" y1="248" x2="182" y2="340" stroke="#f87171" stroke-width="10" stroke-linecap="round"/>
<rect x="268" y="220" width="28" height="96" rx="6" fill="#34d399"/>
<line x1="282" y1="200" x2="282" y2="340" stroke="#34d399" stroke-width="10" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#22d3ee"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="108" fill="#0c1019"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="#141b2d"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="none" stroke="url(#g)" stroke-width="12"/>
<path d="M120 320 L200 248 L280 272 L392 168" fill="none" stroke="url(#g)" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="392" cy="168" r="18" fill="#34d399"/>
<rect x="168" y="268" width="28" height="64" rx="6" fill="#f87171"/>
<line x1="182" y1="248" x2="182" y2="340" stroke="#f87171" stroke-width="10" stroke-linecap="round"/>
<rect x="268" y="220" width="28" height="96" rx="6" fill="#34d399"/>
<line x1="282" y1="200" x2="282" y2="340" stroke="#34d399" stroke-width="10" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+23
View File
@@ -0,0 +1,23 @@
{
"name": "交易监控复盘",
"short_name": "监控",
"description": "加密货币永续交易监控与复盘",
"start_url": "/",
"display": "standalone",
"background_color": "#0b0d14",
"theme_color": "#0b0d14",
"icons": [
{
"src": "__ICON_PREFIX__/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "__ICON_PREFIX__/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
+23
View File
@@ -0,0 +1,23 @@
{
"name": "复盘系统中控",
"short_name": "中控",
"description": "四所交易监控与行情中控",
"start_url": "/monitor",
"display": "standalone",
"background_color": "#0b0e18",
"theme_color": "#0b0e18",
"icons": [
{
"src": "__ICON_PREFIX__/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "__ICON_PREFIX__/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
+18 -4
View File
@@ -21,9 +21,9 @@ APP_PORT=5001
APP_DEBUG=false
# 登录账号
APP_USERNAME=dekun
APP_USERNAME=admin
# 登录密码(请改成你自己的强密码)
APP_PASSWORD=ChangeMe123!
APP_PASSWORD=admin123
# 是否关闭登录校验(局域网可设 true;公网务必 false)
APP_AUTH_DISABLED=true
# --- 多账户交易中控 manual_trading_hub ---
@@ -122,6 +122,20 @@ MAX_ACTIVE_POSITIONS=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
@@ -136,7 +150,7 @@ FULL_MARGIN_BUFFER_RATIO=0.98
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
# =============================================================================
AUTO_TRANSFER_ENABLED=false
# 合约/交易账户(AUTO_TRANSFER_TO)补足到的 USDT 总额,非每日开仓基数
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
AUTO_TRANSFER_AMOUNT=30
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
@@ -177,7 +191,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=5
# 详见上文 DAILY_OPEN_ALERT_THRESHOLD / DAILY_OPEN_HARD_LIMIT;说明文档 docs/daily-open-limit.md
# 以损定仓(按交易账户资金的百分比)
# RISK_PERCENT=2
# 移动保本触发(达到多少R触发)与偏移(百分比)
+11 -12
View File
@@ -19,9 +19,10 @@
安装示例:
```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install flask requests ccxt werkzeug PySocks Pillow
# 推荐在 /opt/crypto_monitor 执行仓库根目录 deploy/setup_env.sh
cd /opt/crypto_monitor/crypto_monitor_binance
source .venv/bin/activate
pip install -r ../requirements.txt
```
页面上的 **「当日资金(交易账户)」** 与 **「可开仓」可用 U** 仅统计 **Binance U 本位永续合约账户**`fetch_balance``swap` / FAPI `assets` 中的 USDT),**不会**再用现货余额顶替。
@@ -46,19 +47,17 @@ pip install flask requests ccxt werkzeug PySocks Pillow
其余变量(登录、企业微信、风控参数、**`AI_PROVIDER` / `OPENAI_*` / `OLLAMA_*`**、数据库路径等)见 **`.env.example` 内注释** 或 `app.py` 顶部默认值。
## 本地运行
## 运行
**WindowsUTF-8 控制台)** 可使用
生产环境使用 **PM2**`ecosystem.config.cjs`)。临时调试
```powershell
.\start_utf8.ps1
```bash
cd /opt/crypto_monitor/crypto_monitor_binance
source .venv/bin/activate
python app.py
```
或直接:
```powershell
python .\app.py
```
环境说明见 [docs/ubuntu-server.md](../docs/ubuntu-server.md)。
默认监听端口由 `.env``APP_PORT` 决定(未设置时多为 `5000`)。
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* PM2 进程定义(Ubuntu / Linux)。
*
* 仅托管 Flask 应用。**SSH SOCKS 隧道请在本机用 screen/tmux/systemd 等方式单独常驻**
* 仅托管 Flask 应用。**SSH SOCKS 隧道**用 `ssh -D` 常驻(可用 tmux / autossh),勿交给 PM2。
* 与 `.env` 里 `BINANCE_SOCKS_PROXY` 端口一致即可;不必交给 PM2。
*
* 使用前:项目根目录存在 `.venv`,且已安装依赖(走 SOCKS 时需 PySocks)。
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#22d3ee"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="108" fill="#0c1019"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="#141b2d"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="none" stroke="url(#g)" stroke-width="12"/>
<path d="M120 320 L200 248 L280 272 L392 168" fill="none" stroke="url(#g)" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="392" cy="168" r="18" fill="#34d399"/>
<rect x="168" y="268" width="28" height="64" rx="6" fill="#f87171"/>
<line x1="182" y1="248" x2="182" y2="340" stroke="#f87171" stroke-width="10" stroke-linecap="round"/>
<rect x="268" y="220" width="28" height="96" rx="6" fill="#34d399"/>
<line x1="282" y1="200" x2="282" y2="340" stroke="#34d399" stroke-width="10" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,23 @@
{
"name": "交易监控复盘",
"short_name": "监控",
"description": "加密货币永续交易监控与复盘",
"start_url": "/",
"display": "standalone",
"background_color": "#0b0d14",
"theme_color": "#0b0d14",
"icons": [
{
"src": "/static/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
File diff suppressed because it is too large Load Diff
@@ -1,261 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ exchange_display }} | 关键位放大</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px}
.container{width:min(98vw,1900px);margin:0 auto}
.card{background:#121726;border-radius:10px;padding:12px;border:1px solid #2a3150;margin-bottom:12px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.btn{padding:7px 10px;border-radius:8px;text-decoration:none;border:1px solid #304164;background:#151a2a;color:#8fc8ff;cursor:pointer}
.btn:hover{background:#1f2740}
input,select,button{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff}
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:10px}
.meta-item{background:#141b2f;border:1px solid #27324e;border-radius:8px;padding:8px}
.meta-item .k{font-size:.76rem;color:#9fb0d8}
.meta-item .v{font-size:1rem;margin-top:4px;word-break:break-all}
.status{font-size:.84rem;color:#95a2c2}
.status.err{color:#ff8080}
#chart-wrap{height:580px;background:#0f1320;border:1px solid #2a3150;border-radius:10px;padding:8px}
#chart{width:100%;height:100%}
.exchange-tag{font-size:.72rem;font-weight:600;color:#b8f5d0;background:#14241e;border:1px solid #2d6a4f;padding:4px 10px;border-radius:999px;margin-left:8px}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="row" style="justify-content:space-between">
<div class="row">
<a class="btn" href="/">返回首页</a>
<strong style="color:#dbe4ff">关键位放大(可输入币种)</strong><span class="exchange-tag">{{ exchange_display }}</span>
</div>
<div class="status">最近刷新:<span id="updated-at">--</span></div>
</div>
<div class="row" style="margin-top:10px">
<label>币种</label>
<input id="symbol-input" value="{{ default_symbol }}" placeholder="BTC/USDT">
<label>关键位</label>
<select id="key-id">
<option value="">无(仅看K线)</option>
{% for k in key_list %}
<option value="{{ k.id }}" {% if selected_key and k.id == selected_key.id %}selected{% endif %}>#{{ k.id }} {{ k.symbol }} {{ k.monitor_type }} {{ '做多' if k.direction == 'long' else '做空' }}</option>
{% endfor %}
</select>
<label>周期</label>
<select id="timeframe">
{% for tf in ['1m','3m','5m','15m','30m','1h','4h','1d'] %}
<option value="{{ tf }}" {% if tf == default_timeframe %}selected{% endif %}>{{ tf }}</option>
{% endfor %}
</select>
<label>K线数</label>
<select id="kline-limit">
<option value="100" {% if default_kline_limit == 100 %}selected{% endif %}>100</option>
<option value="200" {% if default_kline_limit == 200 %}selected{% endif %}>200</option>
</select>
<button id="manual-refresh" type="button">刷新</button>
<span id="load-status" class="status"></span>
</div>
</div>
<div class="card">
<div class="meta">
<div class="meta-item"><div class="k">交易对</div><div class="v" id="m-symbol">-</div></div>
<div class="meta-item"><div class="k">监控类型</div><div class="v" id="m-type">-</div></div>
<div class="meta-item"><div class="k">方向</div><div class="v" id="m-direction">-</div></div>
<div class="meta-item"><div class="k">上沿/阻力</div><div class="v" id="m-upper">-</div></div>
<div class="meta-item"><div class="k">下沿/支撑</div><div class="v" id="m-lower">-</div></div>
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
<div class="meta-item"><div class="k">距上沿</div><div class="v" id="m-updiff">-</div></div>
<div class="meta-item"><div class="k">距下沿</div><div class="v" id="m-lowdiff">-</div></div>
</div>
</div>
<div class="card"><div id="chart-wrap"><div id="chart"></div></div></div>
</div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script>
const refreshMs = Math.max({{ price_refresh_seconds * 1000 }}, 5000);
const keySelect = document.getElementById("key-id");
const symbolInput = document.getElementById("symbol-input");
const tfSelect = document.getElementById("timeframe");
const limitSelect = document.getElementById("kline-limit");
const statusEl = document.getElementById("load-status");
const updatedAtEl = document.getElementById("updated-at");
const chartHost = document.getElementById("chart");
const fmt = (v,d=6)=>(v===null||typeof v==="undefined"||Number.isNaN(Number(v)))?"-":Number(v).toFixed(d);
const fmtSigned = (v,d=4)=>{
if(v===null||typeof v==="undefined"||Number.isNaN(Number(v))) return "-";
const n = Number(v);
return `${n>0?"+":""}${n.toFixed(d)}`;
};
let chart = null;
let candleSeries = null;
let priceLines = [];
const keyMap = {};
{% for k in key_list %}
keyMap["{{ k.id }}"] = "{{ k.symbol }}";
{% endfor %}
function ensureChart(){
if(chart && candleSeries) return true;
if(!window.LightweightCharts){
statusEl.className = "status err";
statusEl.innerText = "图表库加载失败";
return false;
}
if(!chart){
chart = LightweightCharts.createChart(chartHost, {
layout:{background:{color:"#0f1320"},textColor:"#d6deff"},
grid:{vertLines:{color:"#1e263d"},horzLines:{color:"#1e263d"}},
rightPriceScale:{borderColor:"#2a3150"},
timeScale:{borderColor:"#2a3150",timeVisible:true,secondsVisible:false},
crosshair:{mode:0}
});
window.addEventListener("resize",()=>{
chart.applyOptions({width:chartHost.clientWidth,height:chartHost.clientHeight});
});
chart.applyOptions({width:chartHost.clientWidth,height:chartHost.clientHeight});
}
const opts = {
upColor: "#4cd97f",
downColor: "#ff6666",
borderVisible: false,
wickUpColor: "#4cd97f",
wickDownColor: "#ff6666"
};
if (typeof chart.addCandlestickSeries === "function") {
candleSeries = chart.addCandlestickSeries(opts);
} else if (typeof chart.addSeries === "function" && window.LightweightCharts && window.LightweightCharts.CandlestickSeries) {
candleSeries = chart.addSeries(window.LightweightCharts.CandlestickSeries, opts);
}
if(!candleSeries){
statusEl.className = "status err";
statusEl.innerText = "K线序列初始化失败";
return false;
}
return true;
}
function resetPriceLines(){
if(!candleSeries) return;
priceLines.forEach(line=>{ try { candleSeries.removePriceLine(line); } catch (_) {} });
priceLines = [];
}
function addLine(price, title, color){
if(!candleSeries || price===null || typeof price==="undefined") return;
const p = Number(price);
if(Number.isNaN(p) || p<=0) return;
priceLines.push(candleSeries.createPriceLine({
price:p,color,lineWidth:1,lineStyle:0,axisLabelVisible:true,title
}));
}
function paintMeta(data){
const key = data.key_monitor || null;
document.getElementById("m-symbol").innerText = data.symbol || "-";
document.getElementById("m-price").innerText = data.current_price_display || fmt(data.current_price,8);
if(!key){
document.getElementById("m-type").innerText = "未匹配到关键位";
document.getElementById("m-direction").innerText = "-";
document.getElementById("m-upper").innerText = "-";
document.getElementById("m-lower").innerText = "-";
document.getElementById("m-updiff").innerText = "-";
document.getElementById("m-lowdiff").innerText = "-";
return;
}
document.getElementById("m-type").innerText = key.monitor_type || "-";
document.getElementById("m-direction").innerText = key.direction === "short" ? "做空" : "做多";
document.getElementById("m-upper").innerText = key.upper_display || fmt(key.upper,8);
document.getElementById("m-lower").innerText = key.lower_display || fmt(key.lower,8);
document.getElementById("m-updiff").innerText = `${fmtSigned(key.upper_diff,4)} (${fmtSigned(key.upper_pct,2)}%)`;
document.getElementById("m-lowdiff").innerText = `${fmtSigned(key.lower_diff,4)} (${fmtSigned(key.lower_pct,2)}%)`;
}
function syncSymbolByKey(){
const keyId = keySelect.value;
if(keyId && keyMap[keyId]) symbolInput.value = keyMap[keyId];
}
async function loadKeyKline(){
if(!ensureChart()) return;
const keyId = keySelect.value;
const symbol = (symbolInput.value || "").trim().toUpperCase();
const timeframe = tfSelect.value;
const limit = limitSelect.value;
if(!symbol && !keyId){
statusEl.className = "status err";
statusEl.innerText = "请先输入币种或选择关键位";
return;
}
statusEl.className = "status";
statusEl.innerText = "加载中...";
try{
const qs = new URLSearchParams();
if(keyId) qs.set("key_id", keyId);
if(symbol) qs.set("symbol", symbol);
qs.set("timeframe", timeframe);
qs.set("limit", limit);
const resp = await fetch(`/api/key_kline?${qs.toString()}`);
const data = await resp.json();
if(!resp.ok || !data.ok) throw new Error(data.msg || "请求失败");
const candles = Array.isArray(data.candles) ? data.candles : [];
if(!candles.length){
statusEl.className = "status err";
statusEl.innerText = "暂无K线数据";
return;
}
if(!candleSeries) throw new Error("Series init failed");
candleSeries.setData(candles);
resetPriceLines();
addLine(data.current_price, "现价", "#42a5f5");
if(data.key_monitor){
addLine(data.key_monitor.upper, "上沿/阻力", "#ffb84d");
addLine(data.key_monitor.lower, "下沿/支撑", "#4cd97f");
}
chart.timeScale().fitContent();
paintMeta(data);
updatedAtEl.innerText = data.updated_at || "--";
statusEl.className = "status";
statusEl.innerText = `已加载 ${candles.length} 根K线`;
}catch(err){
statusEl.className = "status err";
statusEl.innerText = err && err.message ? err.message : "加载失败";
}
}
document.getElementById("manual-refresh").addEventListener("click", loadKeyKline);
keySelect.addEventListener("change", ()=>{ syncSymbolByKey(); loadKeyKline(); });
symbolInput.addEventListener("change", ()=>{
if(symbolInput.value.trim()) keySelect.value = "";
loadKeyKline();
});
tfSelect.addEventListener("change", loadKeyKline);
limitSelect.addEventListener("change", loadKeyKline);
syncSymbolByKey();
loadKeyKline();
setInterval(loadKeyKline, refreshMs);
</script>
</body>
</html>
+136 -118
View File
@@ -1,118 +1,136 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录 · {{ exchange_display }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a10;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
}
.login-box {
background: #12121a;
padding: 2.5rem;
border-radius: 16px;
width: 100%;
max-width: 400px;
border: 1px solid #242435;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.login-box h2 {
margin-bottom: 2rem;
text-align: center;
font-size: 1.5rem;
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #a9a9ff;
}
.form-group input {
width: 100%;
padding: 0.85rem 1rem;
border-radius: 10px;
border: 1px solid #2e2e45;
background: #1a1a29;
color: #fff;
font-size: 0.95rem;
outline: none;
}
.form-group input:focus {
border-color: #4cc2ff;
}
button {
width: 100%;
padding: 0.9rem;
border-radius: 10px;
border: none;
background: linear-gradient(90deg, #4285f4, #7b42ff);
color: #fff;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
}
.flash {
padding: 0.8rem;
margin-bottom: 1rem;
background: #331e24;
color: #ff6666;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
.exchange-line {
text-align: center;
font-size: 0.82rem;
color: #8892b0;
margin: -0.5rem 0 1.25rem;
}
.exchange-line strong {
color: #b8f5d0;
font-weight: 600;
}
</style>
</head>
<body>
<div class="login-box">
<h2>交易监控系统登录</h2>
<p class="exchange-line">交易所:<strong>{{ exchange_display }}</strong></p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="POST">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" required placeholder="请输入账号">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required placeholder="请输入密码">
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8">
<script src="/static/instance_theme.js?v=4"></script>
<title>登录 · {{ exchange_display }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a10;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
}
.login-box {
background: #12121a;
padding: 2.5rem;
border-radius: 16px;
width: 100%;
max-width: 400px;
border: 1px solid #242435;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.login-box h2 {
margin-bottom: 2rem;
text-align: center;
font-size: 1.5rem;
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #a9a9ff;
}
.form-group input {
width: 100%;
padding: 0.85rem 1rem;
border-radius: 10px;
border: 1px solid #2e2e45;
background: #1a1a29;
color: #fff;
font-size: 0.95rem;
outline: none;
}
.form-group input:focus {
border-color: #4cc2ff;
}
button {
width: 100%;
padding: 0.9rem;
border-radius: 10px;
border: none;
background: linear-gradient(90deg, #4285f4, #7b42ff);
color: #fff;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
}
.flash {
padding: 0.8rem;
margin-bottom: 1rem;
background: #331e24;
color: #ff6666;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
.exchange-line {
text-align: center;
font-size: 0.82rem;
color: #8892b0;
margin: -0.5rem 0 1.25rem;
}
.exchange-line strong {
color: #b8f5d0;
font-weight: 600;
}
</style>
<link rel="stylesheet" href="/static/instance_theme.css?v=4">
</head>
<div class="login-theme-bar">
<div class="theme-toggle instance-theme-toggle" role="group" aria-label="界面主题">
<button type="button" class="theme-toggle-btn is-active" data-theme-value="dark" aria-pressed="true" title="暗色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M12.1 3a9 9 0 1 0 8.9 11 6.5 6.5 0 1 1-8.9-11z"/>
</svg>
</button>
<button type="button" class="theme-toggle-btn" data-theme-value="light" aria-pressed="false" title="亮色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
</svg>
</button>
</div>
</div>
<body>
<div class="login-box">
<h2>交易监控系统登录</h2>
<p class="exchange-line">交易所:<strong>{{ exchange_display }}</strong></p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="POST" autocomplete="off">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" required placeholder="请输入账号" autocomplete="off" autocapitalize="off" spellcheck="false">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required placeholder="请输入密码" autocomplete="new-password">
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
@@ -1,214 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ exchange_display }} | 实盘下单放大</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px}
.container{width:min(98vw,1900px);margin:0 auto}
.card{background:#121726;border-radius:10px;padding:12px;border:1px solid #2a3150;margin-bottom:12px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.btn{padding:7px 10px;border-radius:8px;text-decoration:none;border:1px solid #304164;background:#151a2a;color:#8fc8ff;cursor:pointer}
.btn:hover{background:#1f2740}
select,button{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff}
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:10px}
.meta-item{background:#141b2f;border:1px solid #27324e;border-radius:8px;padding:8px}
.meta-item .k{font-size:.76rem;color:#9fb0d8}
.meta-item .v{font-size:1rem;margin-top:4px;word-break:break-all}
.status{font-size:.84rem;color:#95a2c2}
.status.err{color:#ff8080}
#chart-wrap{height:560px;background:#0f1320;border:1px solid #2a3150;border-radius:10px;padding:8px}
#chart{width:100%;height:100%}
.empty{padding:18px;color:#95a2c2}
.exchange-tag{font-size:.72rem;font-weight:600;color:#b8f5d0;background:#14241e;border:1px solid #2d6a4f;padding:4px 10px;border-radius:999px;margin-left:8px}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="row" style="justify-content:space-between">
<div class="row">
<a class="btn" href="/">返回首页</a>
<strong style="color:#dbe4ff">实盘下单放大(100根K线)</strong><span class="exchange-tag">{{ exchange_display }}</span>
</div>
<div class="status">最近刷新:<span id="updated-at">--</span></div>
</div>
{% if orders %}
<div class="row" style="margin-top:10px">
<label>订单</label>
<select id="order-id">
{% for o in orders %}
<option value="{{ o.id }}" {% if selected_order and o.id == selected_order.id %}selected{% endif %}>
#{{ o.id }} {{ o.symbol }} {{ '做多' if o.direction == 'long' else '做空' }}
</option>
{% endfor %}
</select>
<label>周期</label>
<select id="timeframe">
{% for tf in ['1m','3m','5m','15m','30m','1h','4h','1d'] %}
<option value="{{ tf }}" {% if tf == default_timeframe %}selected{% endif %}>{{ tf }}</option>
{% endfor %}
</select>
<button id="manual-refresh" type="button">刷新</button>
<span id="load-status" class="status"></span>
</div>
{% else %}
<div class="empty">当前没有激活订单,无法展示放大K线。</div>
{% endif %}
</div>
{% if orders %}
<div class="card">
<div class="meta">
<div class="meta-item"><div class="k">交易对</div><div class="v" id="m-symbol">-</div></div>
<div class="meta-item"><div class="k">方向</div><div class="v" id="m-direction">-</div></div>
<div class="meta-item"><div class="k">成交价</div><div class="v" id="m-entry">-</div></div>
<div class="meta-item"><div class="k">止损</div><div class="v" id="m-sl">-</div></div>
<div class="meta-item"><div class="k">止盈</div><div class="v" id="m-tp">-</div></div>
<div class="meta-item"><div class="k">盈亏比</div><div class="v" id="m-rr">-</div></div>
<div class="meta-item"><div class="k">移动保本</div><div class="v" id="m-breakeven">-</div></div>
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
<div class="meta-item"><div class="k">浮盈亏</div><div class="v" id="m-pnl">-</div></div>
</div>
</div>
<div class="card">
<div id="chart-wrap"><div id="chart"></div></div>
</div>
{% endif %}
</div>
{% if orders %}
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
<script>
const refreshMs = Math.max({{ price_refresh_seconds * 1000 }}, 5000);
const orderSelect = document.getElementById("order-id");
const tfSelect = document.getElementById("timeframe");
const statusEl = document.getElementById("load-status");
const updatedAtEl = document.getElementById("updated-at");
const chartHost = document.getElementById("chart");
const fmt = (v, d=6) => (v === null || typeof v === "undefined" || Number.isNaN(Number(v))) ? "-" : Number(v).toFixed(d);
let chart = null;
let candleSeries = null;
let priceLines = [];
function ensureChart(){
if(chart){ return true; }
if(!window.LightweightCharts){
statusEl.className = "status err";
statusEl.innerText = "图表库加载失败";
return false;
}
chart = LightweightCharts.createChart(chartHost, {
layout: { background: { color: "#0f1320" }, textColor: "#d6deff" },
grid: { vertLines: { color: "#1e263d" }, horzLines: { color: "#1e263d" } },
rightPriceScale: { borderColor: "#2a3150" },
timeScale: { borderColor: "#2a3150", timeVisible: true, secondsVisible: false },
crosshair: { mode: 0 }
});
candleSeries = chart.addCandlestickSeries({
upColor: "#4cd97f",
downColor: "#ff6666",
borderVisible: false,
wickUpColor: "#4cd97f",
wickDownColor: "#ff6666"
});
window.addEventListener("resize", () => {
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
});
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
return true;
}
function resetPriceLines(){
if(!candleSeries){ return; }
priceLines.forEach(line => {
try { candleSeries.removePriceLine(line); } catch (_) {}
});
priceLines = [];
}
function addLine(price, title, color){
if(!candleSeries || typeof price === "undefined" || price === null){ return; }
const p = Number(price);
if(Number.isNaN(p) || p <= 0){ return; }
priceLines.push(candleSeries.createPriceLine({
price: p, color, lineWidth: 1, lineStyle: 0, axisLabelVisible: true, title
}));
}
function paintOrder(order){
document.getElementById("m-symbol").innerText = order.symbol || "-";
document.getElementById("m-direction").innerText = (order.direction === "short") ? "做空" : "做多";
document.getElementById("m-entry").innerText = order.trigger_price_display || fmt(order.trigger_price, 8);
document.getElementById("m-sl").innerText = order.stop_loss_display || fmt(order.stop_loss, 8);
document.getElementById("m-tp").innerText = order.take_profit_display || fmt(order.take_profit, 8);
document.getElementById("m-rr").innerText = (order.rr_ratio === null || typeof order.rr_ratio === "undefined") ? "-" : `1:${Number(order.rr_ratio).toFixed(2)}`;
document.getElementById("m-breakeven").innerText =
(order.breakeven_enabled === false || order.breakeven_enabled === 0) ? "关闭" : "开启";
document.getElementById("m-price").innerText = order.current_price_display || fmt(order.current_price, 8);
const pnlEl = document.getElementById("m-pnl");
pnlEl.innerText = `${fmt(order.float_pnl, 2)}U (${fmt(order.float_pct, 2)}%)`;
pnlEl.style.color = Number(order.float_pnl || 0) > 0 ? "#4cd97f" : (Number(order.float_pnl || 0) < 0 ? "#ff6666" : "#d6deff");
}
async function loadOrderKline(){
if(!ensureChart()){ return; }
const orderId = orderSelect.value;
const timeframe = tfSelect.value;
if(!orderId){ return; }
statusEl.className = "status";
statusEl.innerText = "加载中...";
try{
const resp = await fetch(`/api/order_kline?order_id=${encodeURIComponent(orderId)}&timeframe=${encodeURIComponent(timeframe)}`);
const data = await resp.json();
if(!resp.ok || !data.ok){ throw new Error(data.msg || "请求失败"); }
const candles = Array.isArray(data.candles) ? data.candles : [];
if(!candles.length){
statusEl.className = "status err";
statusEl.innerText = "暂无K线数据";
return;
}
candleSeries.setData(candles);
resetPriceLines();
addLine(data.order.trigger_price, "成交价", "#42a5f5");
addLine(data.order.stop_loss, "止损", "#ff6666");
addLine(data.order.take_profit, "止盈", "#4cd97f");
chart.timeScale().fitContent();
paintOrder(data.order || {});
updatedAtEl.innerText = data.updated_at || "--";
statusEl.className = "status";
statusEl.innerText = `已加载 ${candles.length} 根K线`;
}catch(err){
statusEl.className = "status err";
statusEl.innerText = err && err.message ? err.message : "加载失败";
}
}
document.getElementById("manual-refresh").addEventListener("click", loadOrderKline);
orderSelect.addEventListener("change", loadOrderKline);
tfSelect.addEventListener("change", loadOrderKline);
loadOrderKline();
setInterval(loadOrderKline, refreshMs);
</script>
{% endif %}
<script>
(function(){
if (typeof ensureChart !== 'function') return;
const oldEnsureChart = ensureChart;
ensureChart = function(){
if (chart && candleSeries) return true;
try { const ok = oldEnsureChart(); if (ok && candleSeries) return true; } catch (_) {}
if (chart && !candleSeries && typeof chart.addSeries === 'function' && window.LightweightCharts && window.LightweightCharts.CandlestickSeries) {
const opts = { upColor:'#4cd97f', downColor:'#ff6666', borderVisible:false, wickUpColor:'#4cd97f', wickDownColor:'#ff6666' };
candleSeries = chart.addSeries(window.LightweightCharts.CandlestickSeries, opts);
return !!candleSeries;
}
return !!candleSeries;
};
})();
</script>
</body>
</html>
+6 -3
View File
@@ -18,6 +18,7 @@
| **实盘下单监控** | 手工填止损/止盈,**以损定仓** 市价开单,挂上条件止盈止损,并在页面跟踪浮盈亏、保本逻辑等。 |
| **交易记录 / 复盘** | 平仓结果、盈亏、错过的单等归档与导出;可选 **AI 复盘**(见仓库根 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md))。 |
| **策略交易** | 顶栏 `/strategy`**趋势回调**(左)与 **顺势加仓**(右)左右并列;细则见 [策略交易说明.md](../策略交易说明.md)。 |
| **策略交易记录** | 顶栏 `/strategy/records`:趋势/顺势分两栏、可筛选,库内保留最近 100 条结束快照。 |
后台按 **`MONITOR_POLL_SECONDS`**(默认几秒)轮询行情与监控逻辑。**切勿**在未理解规则时同时运行两套程序共用一个实盘账户。
@@ -48,7 +49,7 @@
2. 启动 Flask 应用(可用 **`ecosystem.config.cjs`** 交给 PM2,或本地 `python app.py` / `flask run`,以你当前脚本为准)。
3. 浏览器访问站点,打开 **`/login`**,使用 **`.env` 里的 `APP_PASSWORD`** 登录。
登录后顶栏:**关键位监控** | **实盘下单**(默认首页)| **策略交易**`/strategy`,趋势回调 + 顺势加仓双栏)| **交易记录与复盘** | **统计分析**
登录后顶栏:**关键位监控** | **实盘下单**(默认首页)| **策略交易**`/strategy`,趋势回调 + 顺势加仓双栏)| **策略交易记录**`/strategy/records`,最近 100 条结束快照)| **交易记录与复盘** | **统计分析**
---
@@ -65,9 +66,11 @@
| **收敛突破** | 同上(自动开仓类)。 |
| **关键阻力位** | **不自动开仓**;触发后 **发 1 次微信**,然后本条 **结案进历史**。 |
| **关键支撑位** | 同上(仅提醒)。 |
| **回调触价开仓** | **不挂交易所限价**;标记价回调触达 E 后 **下一轮询市价开仓**RR 门槛同 `KEY_AUTO_MIN_PLANNED_RR`);有效期 **24h** |
| **突破触价开仓** | **不挂交易所限价**;标记价 **穿越 E 立即市价开仓**;先触 SL/TP 侧失效;有效期 **24h** |
3. **方向**:做多 / 做空(必选)。
4. **上沿 / 下沿**:必填;保存时会按交易所 **价格精度** 取整
3. **方向**:做多 / 做空(回调/突破触价、箱体 / 收敛 / 斐波必选;阻力/支撑不选)。
4. **价位**:箱体/收敛/阻力/支撑填 **上沿 / 下沿**;触价填 **入场 E / 止损 SL / 止盈 TP**
**限制:**
活跃持仓数达到 **`MAX_ACTIVE_POSITIONS`**(默认 1)时,**不允许**再添加「**箱体突破** / **收敛突破**」;仍可添加「**关键阻力位 / 支撑位**」。
@@ -1,7 +1,7 @@
# 关键位监控说明(自动开仓 + 人工盯盘)
**适用:`crypto_monitor_binance`Binance U 本位永续)**
Gate / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_monitor_lib.py`
**适用:`crypto_monitor_gate`Gate U 本位永续)**
Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_monitor_lib.py`
本文档与 `.env``check_key_monitors``add_key``_key_hard_checks``_process_key_rs_level_alert` 一致。
@@ -16,8 +16,10 @@ Gate / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_
| **关键阻力位** | **不选**`direction=watch` | **否** | 5m 收盘突破上/下沿 → 微信 **3 次**`key_level_alert_done` |
| **关键支撑位** | **不选** | **否** | 同上(与阻力位**相同规则**:填上沿+下沿,程序双向监控) |
| 斐波回调 0.618 / 0.786 | 必选 | 限价挂单逻辑 | 见斐波说明(**不在下文展开**) |
| **回调触价开仓** | **必选** 多/空 | **程序盯价 → 回调触 E 后市价** | 见下文 **§四** |
| **突破触价开仓** | **必选** 多/空 | **程序盯价 → 穿越 E 立即市价** | 见下文 **§四** |
**添加时(所有类型):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30**;上沿 **>** 下沿。
**添加时(箱体/收敛/斐波/触价):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30**;上沿 **>** 下沿(触价开仓填 E/SL/TP,上下沿仅作展示占位)
---
@@ -110,6 +112,7 @@ Gate / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_
| `close_reason` | 含义 |
|----------------|------|
| `box_opposite_break` | 标记价先突破反向边界(多:≤下沿;空:≥上沿) |
| `rr_insufficient` | 门控通过但 RR 不达标或 SL/TP 几何无效 |
| `exchange_failed` | RR 达标但实盘/交易所等原因未开仓 |
| `auto_opened` | RR 达标且市价开仓成功 |
@@ -117,7 +120,37 @@ Gate / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_
---
## 四、环境与参数(`.env` 摘要
## 四、回调 / 突破触价开仓(程序触价,无交易所挂单
### 4.1 录入
- **回调触价开仓**:方向必选多/空;填写 **计划入场价 E**、**止损 SL**、**止盈 TP**(做多须 `SL < E < TP`)。
- **突破触价开仓**:同上;添加时当前价须在突破方向一侧(做多:价低于 E;做空:价高于 E)。
- 计划 RR 以 **E** 为基准,须 **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5)。
- 可选移动保本、时间平仓;**全仓杠杆模式**下可用。
### 4.2 触发与结案
| 类型 | 触发条件(标记价) |
|------|-------------------|
| **回调触价** | 做多 `≤ E`;做空 `≥ E` → 下一轮询市价开仓 |
| **突破触价** | 做多**向上穿越** E;做空**向下穿越** E → **立即**市价开仓 |
- 未成交前标记价先触 **TP 侧**`trigger_tp_invalidate`
- **突破触价**另:未穿越 E 先触 **SL 侧**`trigger_sl_invalidate`
- **24h** 未触发 → `trigger_entry_expired`
- 成功 → `trigger_entry_filled`;触发后开仓失败 → `trigger_exchange_failed`
### 4.3 计仓与占位
- **以损定仓**:按 E、SL 反推保证金,触发时重算;**全仓杠杆**:可用×缓冲比例,BTC/ETH 10x、其它 5x。
- **占当日开仓意图**(已开 + 待触发),未成交不占持仓;同币仅 1 条触价监控(含回调/突破)。
共享逻辑:`trigger_entry_key_monitor_lib.py`;轮询:`check_trigger_entry_key_monitors`
---
## 五、环境与参数(`.env` 摘要)
| 变量 | 箱体/收敛 | 阻力/支撑 |
|------|-----------|-----------|
@@ -130,7 +163,7 @@ Gate / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_
---
## 、相关代码
## 、相关代码
| 说明 | 位置 |
|------|------|
+3 -3
View File
@@ -1,12 +1,12 @@
# `crypto_monitor_binance` 部署指南:SSH SOCKS + Binance + PM2Ubuntu
项目功能、环境变量总览与本地运行说明**[README.md](./README.md)**。
项目功能、环境变量总览见 **[README.md](./README.md)**。Ubuntu 环境(Python / Node / PM2)见 **[docs/ubuntu-server.md](../docs/ubuntu-server.md)**。
本文面向:**在本机或 VPS 上运行本项目**,但 **直连 Binance API 不稳定、超时或被网络策略拦截** 的场景。思路是:
- 本机用 `ssh -D` 做动态转发,把 **SOCKS5 出口**放到能稳定访问 Binance 的机器(常见为一台境外 VPS)
- 项目在 `.env` 中设置 **`BINANCE_SOCKS_PROXY=socks5h://127.0.0.1:1080`**(或你实际端口),`ccxt` 经 SOCKS 访问交易所
- **SSH 隧道**:用 `ssh -D` 在本机常驻即可(screen / tmux / systemd 等),**不必交给 PM2**
- **SSH 隧道**:用 `ssh -D` 在本机常驻(可用 **tmux****autossh** 保持连接),**不要** 把 `ssh` 交给 PM2
- 使用 **PM2** 仅托管 **Flask 应用**;仓库根目录 **`ecosystem.config.cjs`** 默认进程名为 **`crypto-monitor-binance`**
> 安全提醒:不要把 `.env`、私钥 `.pem`、Binance API Key / Secret 提交到 Git;下文只用占位符。
@@ -323,7 +323,7 @@ pm2 startup
### 10.1 SSH SOCKS(自行后台常驻,不推荐用 PM2)
示例(前台;实际可用 `screen`/`tmux`/`-f` 后台化或 systemd):
示例(前台调试;生产请用 **PM2**,见本文 §6 与 [docs/ubuntu-server.md](../docs/ubuntu-server.md)):
```bash
ssh -N -D 127.0.0.1:1080 bn-vps \
+18 -4
View File
@@ -21,9 +21,9 @@ APP_PORT=5000
APP_DEBUG=false
# 登录账号
APP_USERNAME=dekun
APP_USERNAME=admin
# 登录密码(请改成你自己的强密码)
APP_PASSWORD=ChangeMe123!
APP_PASSWORD=admin123
# 是否关闭登录校验(局域网可设 true;公网务必 false)
APP_AUTH_DISABLED=true
# --- 多账户交易中控 manual_trading_hub ---
@@ -124,6 +124,20 @@ MAX_ACTIVE_POSITIONS=1
MANUAL_MIN_PLANNED_RR=1.4
# 【关键位连开计仓】已有持仓时按无仓时资金快照算基数
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
@@ -142,7 +156,7 @@ FULL_MARGIN_BUFFER_RATIO=0.98
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
# =============================================================================
AUTO_TRANSFER_ENABLED=false
# 交易账户(AUTO_TRANSFER_TO)补足到的 USDT 总额,非每日开仓基数
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
AUTO_TRANSFER_AMOUNT=30
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
@@ -181,7 +195,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=5
# 详见 DAILY_OPEN_ALERT_THRESHOLD / DAILY_OPEN_HARD_LIMIT;说明文档 docs/daily-open-limit.md
# 以损定仓(按交易账户资金的百分比)
# RISK_PERCENT=2
# 移动保本触发(达到多少R触发)与偏移(百分比)
+8 -12
View File
@@ -31,9 +31,9 @@
安装示例:
```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install flask requests ccxt werkzeug PySocks Pillow
cd /opt/crypto_monitor/crypto_monitor_gate
source .venv/bin/activate
pip install -r ../requirements.txt
```
## 配置(`.env.example` → `.env`
@@ -56,19 +56,15 @@ pip install flask requests ccxt werkzeug PySocks Pillow
其余见 **`.env.example` 内注释** 或 **`app.py` 顶部默认值**。
## 本地运行
## 运行
**Windows** 推荐使用 UTF-8 控制台脚本
生产使用 **PM2**`ecosystem.config.cjs`)。调试
```powershell
.\start_utf8.ps1
```bash
source .venv/bin/activate && python app.py
```
或直接:
```powershell
python .\app.py
```
见 [docs/ubuntu-server.md](../docs/ubuntu-server.md)。
端口由 **`APP_PORT`** 控制(未设置默认 **5000**)。浏览器登录 **`/login`**,口令为 **`APP_PASSWORD`**。
+1776 -441
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* PM2 进程定义(Ubuntu / Linux)。
*
* 仅托管 Flask 应用。**SSH SOCKS 隧道请在本机用 screen/tmux/systemd 等方式单独常驻**
* 仅托管 Flask 应用。**SSH SOCKS 隧道**用 `ssh -D` 常驻(可用 tmux / autossh),勿交给 PM2。
* 与 `.env` 里 `GATE_SOCKS_PROXY` 端口一致即可;不必交给 PM2。
*
* 使用前:项目根目录存在 `.venv`,且已安装依赖(走 SOCKS 时需 PySocks)。
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#22d3ee"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="108" fill="#0c1019"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="#141b2d"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="none" stroke="url(#g)" stroke-width="12"/>
<path d="M120 320 L200 248 L280 272 L392 168" fill="none" stroke="url(#g)" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="392" cy="168" r="18" fill="#34d399"/>
<rect x="168" y="268" width="28" height="64" rx="6" fill="#f87171"/>
<line x1="182" y1="248" x2="182" y2="340" stroke="#f87171" stroke-width="10" stroke-linecap="round"/>
<rect x="268" y="220" width="28" height="96" rx="6" fill="#34d399"/>
<line x1="282" y1="200" x2="282" y2="340" stroke="#34d399" stroke-width="10" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,23 @@
{
"name": "交易监控复盘",
"short_name": "监控",
"description": "加密货币永续交易监控与复盘",
"start_url": "/",
"display": "standalone",
"background_color": "#0b0d14",
"theme_color": "#0b0d14",
"icons": [
{
"src": "/static/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
File diff suppressed because it is too large Load Diff
@@ -1,261 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ exchange_display }} | 关键位放大</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px}
.container{width:min(98vw,1900px);margin:0 auto}
.card{background:#121726;border-radius:10px;padding:12px;border:1px solid #2a3150;margin-bottom:12px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.btn{padding:7px 10px;border-radius:8px;text-decoration:none;border:1px solid #304164;background:#151a2a;color:#8fc8ff;cursor:pointer}
.btn:hover{background:#1f2740}
input,select,button{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff}
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:10px}
.meta-item{background:#141b2f;border:1px solid #27324e;border-radius:8px;padding:8px}
.meta-item .k{font-size:.76rem;color:#9fb0d8}
.meta-item .v{font-size:1rem;margin-top:4px;word-break:break-all}
.status{font-size:.84rem;color:#95a2c2}
.status.err{color:#ff8080}
#chart-wrap{height:580px;background:#0f1320;border:1px solid #2a3150;border-radius:10px;padding:8px}
#chart{width:100%;height:100%}
.exchange-tag{font-size:.72rem;font-weight:600;color:#b8f5d0;background:#14241e;border:1px solid #2d6a4f;padding:4px 10px;border-radius:999px;margin-left:8px}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="row" style="justify-content:space-between">
<div class="row">
<a class="btn" href="/">返回首页</a>
<strong style="color:#dbe4ff">关键位放大(可输入币种)</strong><span class="exchange-tag">{{ exchange_display }}</span>
</div>
<div class="status">最近刷新:<span id="updated-at">--</span></div>
</div>
<div class="row" style="margin-top:10px">
<label>币种</label>
<input id="symbol-input" value="{{ default_symbol }}" placeholder="BTC/USDT">
<label>关键位</label>
<select id="key-id">
<option value="">无(仅看K线)</option>
{% for k in key_list %}
<option value="{{ k.id }}" {% if selected_key and k.id == selected_key.id %}selected{% endif %}>#{{ k.id }} {{ k.symbol }} {{ k.monitor_type }} {{ '做多' if k.direction == 'long' else '做空' }}</option>
{% endfor %}
</select>
<label>周期</label>
<select id="timeframe">
{% for tf in ['1m','3m','5m','15m','30m','1h','4h','1d'] %}
<option value="{{ tf }}" {% if tf == default_timeframe %}selected{% endif %}>{{ tf }}</option>
{% endfor %}
</select>
<label>K线数</label>
<select id="kline-limit">
<option value="100" {% if default_kline_limit == 100 %}selected{% endif %}>100</option>
<option value="200" {% if default_kline_limit == 200 %}selected{% endif %}>200</option>
</select>
<button id="manual-refresh" type="button">刷新</button>
<span id="load-status" class="status"></span>
</div>
</div>
<div class="card">
<div class="meta">
<div class="meta-item"><div class="k">交易对</div><div class="v" id="m-symbol">-</div></div>
<div class="meta-item"><div class="k">监控类型</div><div class="v" id="m-type">-</div></div>
<div class="meta-item"><div class="k">方向</div><div class="v" id="m-direction">-</div></div>
<div class="meta-item"><div class="k">上沿/阻力</div><div class="v" id="m-upper">-</div></div>
<div class="meta-item"><div class="k">下沿/支撑</div><div class="v" id="m-lower">-</div></div>
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
<div class="meta-item"><div class="k">距上沿</div><div class="v" id="m-updiff">-</div></div>
<div class="meta-item"><div class="k">距下沿</div><div class="v" id="m-lowdiff">-</div></div>
</div>
</div>
<div class="card"><div id="chart-wrap"><div id="chart"></div></div></div>
</div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script>
const refreshMs = Math.max({{ price_refresh_seconds * 1000 }}, 5000);
const keySelect = document.getElementById("key-id");
const symbolInput = document.getElementById("symbol-input");
const tfSelect = document.getElementById("timeframe");
const limitSelect = document.getElementById("kline-limit");
const statusEl = document.getElementById("load-status");
const updatedAtEl = document.getElementById("updated-at");
const chartHost = document.getElementById("chart");
const fmt = (v,d=6)=>(v===null||typeof v==="undefined"||Number.isNaN(Number(v)))?"-":Number(v).toFixed(d);
const fmtSigned = (v,d=4)=>{
if(v===null||typeof v==="undefined"||Number.isNaN(Number(v))) return "-";
const n = Number(v);
return `${n>0?"+":""}${n.toFixed(d)}`;
};
let chart = null;
let candleSeries = null;
let priceLines = [];
const keyMap = {};
{% for k in key_list %}
keyMap["{{ k.id }}"] = "{{ k.symbol }}";
{% endfor %}
function ensureChart(){
if(chart && candleSeries) return true;
if(!window.LightweightCharts){
statusEl.className = "status err";
statusEl.innerText = "图表库加载失败";
return false;
}
if(!chart){
chart = LightweightCharts.createChart(chartHost, {
layout:{background:{color:"#0f1320"},textColor:"#d6deff"},
grid:{vertLines:{color:"#1e263d"},horzLines:{color:"#1e263d"}},
rightPriceScale:{borderColor:"#2a3150"},
timeScale:{borderColor:"#2a3150",timeVisible:true,secondsVisible:false},
crosshair:{mode:0}
});
window.addEventListener("resize",()=>{
chart.applyOptions({width:chartHost.clientWidth,height:chartHost.clientHeight});
});
chart.applyOptions({width:chartHost.clientWidth,height:chartHost.clientHeight});
}
const opts = {
upColor: "#4cd97f",
downColor: "#ff6666",
borderVisible: false,
wickUpColor: "#4cd97f",
wickDownColor: "#ff6666"
};
if (typeof chart.addCandlestickSeries === "function") {
candleSeries = chart.addCandlestickSeries(opts);
} else if (typeof chart.addSeries === "function" && window.LightweightCharts && window.LightweightCharts.CandlestickSeries) {
candleSeries = chart.addSeries(window.LightweightCharts.CandlestickSeries, opts);
}
if(!candleSeries){
statusEl.className = "status err";
statusEl.innerText = "K线序列初始化失败";
return false;
}
return true;
}
function resetPriceLines(){
if(!candleSeries) return;
priceLines.forEach(line=>{ try { candleSeries.removePriceLine(line); } catch (_) {} });
priceLines = [];
}
function addLine(price, title, color){
if(!candleSeries || price===null || typeof price==="undefined") return;
const p = Number(price);
if(Number.isNaN(p) || p<=0) return;
priceLines.push(candleSeries.createPriceLine({
price:p,color,lineWidth:1,lineStyle:0,axisLabelVisible:true,title
}));
}
function paintMeta(data){
const key = data.key_monitor || null;
document.getElementById("m-symbol").innerText = data.symbol || "-";
document.getElementById("m-price").innerText = fmt(data.current_price,8);
if(!key){
document.getElementById("m-type").innerText = "未匹配到关键位";
document.getElementById("m-direction").innerText = "-";
document.getElementById("m-upper").innerText = "-";
document.getElementById("m-lower").innerText = "-";
document.getElementById("m-updiff").innerText = "-";
document.getElementById("m-lowdiff").innerText = "-";
return;
}
document.getElementById("m-type").innerText = key.monitor_type || "-";
document.getElementById("m-direction").innerText = key.direction === "short" ? "做空" : "做多";
document.getElementById("m-upper").innerText = fmt(key.upper,8);
document.getElementById("m-lower").innerText = fmt(key.lower,8);
document.getElementById("m-updiff").innerText = `${fmtSigned(key.upper_diff,4)} (${fmtSigned(key.upper_pct,2)}%)`;
document.getElementById("m-lowdiff").innerText = `${fmtSigned(key.lower_diff,4)} (${fmtSigned(key.lower_pct,2)}%)`;
}
function syncSymbolByKey(){
const keyId = keySelect.value;
if(keyId && keyMap[keyId]) symbolInput.value = keyMap[keyId];
}
async function loadKeyKline(){
if(!ensureChart()) return;
const keyId = keySelect.value;
const symbol = (symbolInput.value || "").trim().toUpperCase();
const timeframe = tfSelect.value;
const limit = limitSelect.value;
if(!symbol && !keyId){
statusEl.className = "status err";
statusEl.innerText = "请先输入币种或选择关键位";
return;
}
statusEl.className = "status";
statusEl.innerText = "加载中...";
try{
const qs = new URLSearchParams();
if(keyId) qs.set("key_id", keyId);
if(symbol) qs.set("symbol", symbol);
qs.set("timeframe", timeframe);
qs.set("limit", limit);
const resp = await fetch(`/api/key_kline?${qs.toString()}`);
const data = await resp.json();
if(!resp.ok || !data.ok) throw new Error(data.msg || "请求失败");
const candles = Array.isArray(data.candles) ? data.candles : [];
if(!candles.length){
statusEl.className = "status err";
statusEl.innerText = "暂无K线数据";
return;
}
if(!candleSeries) throw new Error("Series init failed");
candleSeries.setData(candles);
resetPriceLines();
addLine(data.current_price, "现价", "#42a5f5");
if(data.key_monitor){
addLine(data.key_monitor.upper, "上沿/阻力", "#ffb84d");
addLine(data.key_monitor.lower, "下沿/支撑", "#4cd97f");
}
chart.timeScale().fitContent();
paintMeta(data);
updatedAtEl.innerText = data.updated_at || "--";
statusEl.className = "status";
statusEl.innerText = `已加载 ${candles.length} 根K线`;
}catch(err){
statusEl.className = "status err";
statusEl.innerText = err && err.message ? err.message : "加载失败";
}
}
document.getElementById("manual-refresh").addEventListener("click", loadKeyKline);
keySelect.addEventListener("change", ()=>{ syncSymbolByKey(); loadKeyKline(); });
symbolInput.addEventListener("change", ()=>{
if(symbolInput.value.trim()) keySelect.value = "";
loadKeyKline();
});
tfSelect.addEventListener("change", loadKeyKline);
limitSelect.addEventListener("change", loadKeyKline);
syncSymbolByKey();
loadKeyKline();
setInterval(loadKeyKline, refreshMs);
</script>
</body>
</html>
+136 -118
View File
@@ -1,118 +1,136 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录 · {{ exchange_display }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a10;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
}
.login-box {
background: #12121a;
padding: 2.5rem;
border-radius: 16px;
width: 100%;
max-width: 400px;
border: 1px solid #242435;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.login-box h2 {
margin-bottom: 2rem;
text-align: center;
font-size: 1.5rem;
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #a9a9ff;
}
.form-group input {
width: 100%;
padding: 0.85rem 1rem;
border-radius: 10px;
border: 1px solid #2e2e45;
background: #1a1a29;
color: #fff;
font-size: 0.95rem;
outline: none;
}
.form-group input:focus {
border-color: #4cc2ff;
}
button {
width: 100%;
padding: 0.9rem;
border-radius: 10px;
border: none;
background: linear-gradient(90deg, #4285f4, #7b42ff);
color: #fff;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
}
.flash {
padding: 0.8rem;
margin-bottom: 1rem;
background: #331e24;
color: #ff6666;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
.exchange-line {
text-align: center;
font-size: 0.82rem;
color: #8892b0;
margin: -0.5rem 0 1.25rem;
}
.exchange-line strong {
color: #b8f5d0;
font-weight: 600;
}
</style>
</head>
<body>
<div class="login-box">
<h2>交易监控系统登录</h2>
<p class="exchange-line">交易所:<strong>{{ exchange_display }}</strong></p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="POST">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" required placeholder="请输入账号">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required placeholder="请输入密码">
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8">
<script src="/static/instance_theme.js?v=4"></script>
<title>登录 · {{ exchange_display }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a10;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
}
.login-box {
background: #12121a;
padding: 2.5rem;
border-radius: 16px;
width: 100%;
max-width: 400px;
border: 1px solid #242435;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.login-box h2 {
margin-bottom: 2rem;
text-align: center;
font-size: 1.5rem;
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #a9a9ff;
}
.form-group input {
width: 100%;
padding: 0.85rem 1rem;
border-radius: 10px;
border: 1px solid #2e2e45;
background: #1a1a29;
color: #fff;
font-size: 0.95rem;
outline: none;
}
.form-group input:focus {
border-color: #4cc2ff;
}
button {
width: 100%;
padding: 0.9rem;
border-radius: 10px;
border: none;
background: linear-gradient(90deg, #4285f4, #7b42ff);
color: #fff;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
}
.flash {
padding: 0.8rem;
margin-bottom: 1rem;
background: #331e24;
color: #ff6666;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
.exchange-line {
text-align: center;
font-size: 0.82rem;
color: #8892b0;
margin: -0.5rem 0 1.25rem;
}
.exchange-line strong {
color: #b8f5d0;
font-weight: 600;
}
</style>
<link rel="stylesheet" href="/static/instance_theme.css?v=4">
</head>
<div class="login-theme-bar">
<div class="theme-toggle instance-theme-toggle" role="group" aria-label="界面主题">
<button type="button" class="theme-toggle-btn is-active" data-theme-value="dark" aria-pressed="true" title="暗色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M12.1 3a9 9 0 1 0 8.9 11 6.5 6.5 0 1 1-8.9-11z"/>
</svg>
</button>
<button type="button" class="theme-toggle-btn" data-theme-value="light" aria-pressed="false" title="亮色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
</svg>
</button>
</div>
</div>
<body>
<div class="login-box">
<h2>交易监控系统登录</h2>
<p class="exchange-line">交易所:<strong>{{ exchange_display }}</strong></p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="POST" autocomplete="off">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" required placeholder="请输入账号" autocomplete="off" autocapitalize="off" spellcheck="false">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required placeholder="请输入密码" autocomplete="new-password">
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
@@ -1,214 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ exchange_display }} | 实盘下单放大</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px}
.container{width:min(98vw,1900px);margin:0 auto}
.card{background:#121726;border-radius:10px;padding:12px;border:1px solid #2a3150;margin-bottom:12px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.btn{padding:7px 10px;border-radius:8px;text-decoration:none;border:1px solid #304164;background:#151a2a;color:#8fc8ff;cursor:pointer}
.btn:hover{background:#1f2740}
select,button{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff}
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:10px}
.meta-item{background:#141b2f;border:1px solid #27324e;border-radius:8px;padding:8px}
.meta-item .k{font-size:.76rem;color:#9fb0d8}
.meta-item .v{font-size:1rem;margin-top:4px;word-break:break-all}
.status{font-size:.84rem;color:#95a2c2}
.status.err{color:#ff8080}
#chart-wrap{height:560px;background:#0f1320;border:1px solid #2a3150;border-radius:10px;padding:8px}
#chart{width:100%;height:100%}
.empty{padding:18px;color:#95a2c2}
.exchange-tag{font-size:.72rem;font-weight:600;color:#b8f5d0;background:#14241e;border:1px solid #2d6a4f;padding:4px 10px;border-radius:999px;margin-left:8px}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="row" style="justify-content:space-between">
<div class="row">
<a class="btn" href="/">返回首页</a>
<strong style="color:#dbe4ff">实盘下单放大(100根K线)</strong><span class="exchange-tag">{{ exchange_display }}</span>
</div>
<div class="status">最近刷新:<span id="updated-at">--</span></div>
</div>
{% if orders %}
<div class="row" style="margin-top:10px">
<label>订单</label>
<select id="order-id">
{% for o in orders %}
<option value="{{ o.id }}" {% if selected_order and o.id == selected_order.id %}selected{% endif %}>
#{{ o.id }} {{ o.symbol }} {{ '做多' if o.direction == 'long' else '做空' }}
</option>
{% endfor %}
</select>
<label>周期</label>
<select id="timeframe">
{% for tf in ['1m','3m','5m','15m','30m','1h','4h','1d'] %}
<option value="{{ tf }}" {% if tf == default_timeframe %}selected{% endif %}>{{ tf }}</option>
{% endfor %}
</select>
<button id="manual-refresh" type="button">刷新</button>
<span id="load-status" class="status"></span>
</div>
{% else %}
<div class="empty">当前没有激活订单,无法展示放大K线。</div>
{% endif %}
</div>
{% if orders %}
<div class="card">
<div class="meta">
<div class="meta-item"><div class="k">交易对</div><div class="v" id="m-symbol">-</div></div>
<div class="meta-item"><div class="k">方向</div><div class="v" id="m-direction">-</div></div>
<div class="meta-item"><div class="k">成交价</div><div class="v" id="m-entry">-</div></div>
<div class="meta-item"><div class="k">止损</div><div class="v" id="m-sl">-</div></div>
<div class="meta-item"><div class="k">止盈</div><div class="v" id="m-tp">-</div></div>
<div class="meta-item"><div class="k">盈亏比</div><div class="v" id="m-rr">-</div></div>
<div class="meta-item"><div class="k">移动保本</div><div class="v" id="m-breakeven">-</div></div>
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
<div class="meta-item"><div class="k">浮盈亏</div><div class="v" id="m-pnl">-</div></div>
</div>
</div>
<div class="card">
<div id="chart-wrap"><div id="chart"></div></div>
</div>
{% endif %}
</div>
{% if orders %}
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
<script>
const refreshMs = Math.max({{ price_refresh_seconds * 1000 }}, 5000);
const orderSelect = document.getElementById("order-id");
const tfSelect = document.getElementById("timeframe");
const statusEl = document.getElementById("load-status");
const updatedAtEl = document.getElementById("updated-at");
const chartHost = document.getElementById("chart");
const fmt = (v, d=6) => (v === null || typeof v === "undefined" || Number.isNaN(Number(v))) ? "-" : Number(v).toFixed(d);
let chart = null;
let candleSeries = null;
let priceLines = [];
function ensureChart(){
if(chart){ return true; }
if(!window.LightweightCharts){
statusEl.className = "status err";
statusEl.innerText = "图表库加载失败";
return false;
}
chart = LightweightCharts.createChart(chartHost, {
layout: { background: { color: "#0f1320" }, textColor: "#d6deff" },
grid: { vertLines: { color: "#1e263d" }, horzLines: { color: "#1e263d" } },
rightPriceScale: { borderColor: "#2a3150" },
timeScale: { borderColor: "#2a3150", timeVisible: true, secondsVisible: false },
crosshair: { mode: 0 }
});
candleSeries = chart.addCandlestickSeries({
upColor: "#4cd97f",
downColor: "#ff6666",
borderVisible: false,
wickUpColor: "#4cd97f",
wickDownColor: "#ff6666"
});
window.addEventListener("resize", () => {
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
});
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
return true;
}
function resetPriceLines(){
if(!candleSeries){ return; }
priceLines.forEach(line => {
try { candleSeries.removePriceLine(line); } catch (_) {}
});
priceLines = [];
}
function addLine(price, title, color){
if(!candleSeries || typeof price === "undefined" || price === null){ return; }
const p = Number(price);
if(Number.isNaN(p) || p <= 0){ return; }
priceLines.push(candleSeries.createPriceLine({
price: p, color, lineWidth: 1, lineStyle: 0, axisLabelVisible: true, title
}));
}
function paintOrder(order){
document.getElementById("m-symbol").innerText = order.symbol || "-";
document.getElementById("m-direction").innerText = (order.direction === "short") ? "做空" : "做多";
document.getElementById("m-entry").innerText = order.trigger_price_display || fmt(order.trigger_price, 8);
document.getElementById("m-sl").innerText = order.stop_loss_display || fmt(order.stop_loss, 8);
document.getElementById("m-tp").innerText = order.take_profit_display || fmt(order.take_profit, 8);
document.getElementById("m-rr").innerText = (order.rr_ratio === null || typeof order.rr_ratio === "undefined") ? "-" : `1:${Number(order.rr_ratio).toFixed(2)}`;
document.getElementById("m-breakeven").innerText =
(order.breakeven_enabled === false || order.breakeven_enabled === 0) ? "关闭" : "开启";
document.getElementById("m-price").innerText = order.current_price_display || fmt(order.current_price, 8);
const pnlEl = document.getElementById("m-pnl");
pnlEl.innerText = `${fmt(order.float_pnl, 2)}U (${fmt(order.float_pct, 2)}%)`;
pnlEl.style.color = Number(order.float_pnl || 0) > 0 ? "#4cd97f" : (Number(order.float_pnl || 0) < 0 ? "#ff6666" : "#d6deff");
}
async function loadOrderKline(){
if(!ensureChart()){ return; }
const orderId = orderSelect.value;
const timeframe = tfSelect.value;
if(!orderId){ return; }
statusEl.className = "status";
statusEl.innerText = "加载中...";
try{
const resp = await fetch(`/api/order_kline?order_id=${encodeURIComponent(orderId)}&timeframe=${encodeURIComponent(timeframe)}`);
const data = await resp.json();
if(!resp.ok || !data.ok){ throw new Error(data.msg || "请求失败"); }
const candles = Array.isArray(data.candles) ? data.candles : [];
if(!candles.length){
statusEl.className = "status err";
statusEl.innerText = "暂无K线数据";
return;
}
candleSeries.setData(candles);
resetPriceLines();
addLine(data.order.trigger_price, "成交价", "#42a5f5");
addLine(data.order.stop_loss, "止损", "#ff6666");
addLine(data.order.take_profit, "止盈", "#4cd97f");
chart.timeScale().fitContent();
paintOrder(data.order || {});
updatedAtEl.innerText = data.updated_at || "--";
statusEl.className = "status";
statusEl.innerText = `已加载 ${candles.length} 根K线`;
}catch(err){
statusEl.className = "status err";
statusEl.innerText = err && err.message ? err.message : "加载失败";
}
}
document.getElementById("manual-refresh").addEventListener("click", loadOrderKline);
orderSelect.addEventListener("change", loadOrderKline);
tfSelect.addEventListener("change", loadOrderKline);
loadOrderKline();
setInterval(loadOrderKline, refreshMs);
</script>
{% endif %}
<script>
(function(){
if (typeof ensureChart !== 'function') return;
const oldEnsureChart = ensureChart;
ensureChart = function(){
if (chart && candleSeries) return true;
try { const ok = oldEnsureChart(); if (ok && candleSeries) return true; } catch (_) {}
if (chart && !candleSeries && typeof chart.addSeries === 'function' && window.LightweightCharts && window.LightweightCharts.CandlestickSeries) {
const opts = { upColor:'#4cd97f', downColor:'#ff6666', borderVisible:false, wickUpColor:'#4cd97f', wickDownColor:'#ff6666' };
candleSeries = chart.addSeries(window.LightweightCharts.CandlestickSeries, opts);
return !!candleSeries;
}
return !!candleSeries;
};
})();
</script>
</body>
</html>
+5 -3
View File
@@ -48,7 +48,7 @@
2. 启动 Flask 应用(本仓库可用 **`ecosystem.config.cjs`** 交给 PM2,或本地 `python app.py` / `flask run`,以你当前脚本为准)。
3. 浏览器访问站点,打开 **`/login`**,使用 **`.env` 里的 `APP_PASSWORD`** 登录。
登录后顶栏:**关键位监控** | **实盘下单** | **策略交易** | **交易记录与复盘** | **统计分析**
登录后顶栏:**关键位监控** | **实盘下单** | **策略交易**`/strategy`| **策略交易记录**`/strategy/records`| **交易记录与复盘** | **统计分析**
---
@@ -65,9 +65,11 @@
| **收敛突破** | 同上(自动开仓类)。 |
| **关键阻力位** | **不自动开仓**;触发后 **发 1 次微信**,然后本条 **结案进历史**。 |
| **关键支撑位** | 同上(仅提醒)。 |
| **回调触价开仓** | **不挂交易所限价**;标记价回调触达 E 后 **下一轮询市价开仓**RR 门槛同 `KEY_AUTO_MIN_PLANNED_RR`);有效期 **24h** |
| **突破触价开仓** | **不挂交易所限价**;标记价 **穿越 E 立即市价开仓**;先触 SL/TP 侧失效;有效期 **24h** |
3. **方向**:做多 / 做空(选)。
4. **上沿 / 下沿**:必填;保存时会按交易所 **价格精度** 取整
3. **方向**:做多 / 做空(触价开仓 / 箱体 / 收敛 / 斐波必选;阻力/支撑不选)。
4. **价位**:箱体/收敛/阻力/支撑填 **上沿 / 下沿**;触价开仓填 **入场 E / 止损 SL / 止盈 TP**
**限制:**
活跃持仓数达到 **`MAX_ACTIVE_POSITIONS`**(默认 1)时,**不允许**再添加「**箱体突破** / **收敛突破**」;仍可添加「**关键阻力位 / 支撑位**」。
@@ -16,8 +16,10 @@ Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `k
| **关键阻力位** | **不选**`direction=watch` | **否** | 5m 收盘突破上/下沿 → 微信 **3 次**`key_level_alert_done` |
| **关键支撑位** | **不选** | **否** | 同上(与阻力位**相同规则**:填上沿+下沿,程序双向监控) |
| 斐波回调 0.618 / 0.786 | 必选 | 限价挂单逻辑 | 见斐波说明(**不在下文展开**) |
| **回调触价开仓** | **必选** 多/空 | **程序盯价 → 回调触 E 后市价** | 见下文 **§四** |
| **突破触价开仓** | **必选** 多/空 | **程序盯价 → 穿越 E 立即市价** | 见下文 **§四** |
**添加时(所有类型):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30**;上沿 **>** 下沿。
**添加时(箱体/收敛/斐波/触价):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30**;上沿 **>** 下沿(触价开仓填 E/SL/TP,上下沿仅作展示占位)
---
@@ -110,6 +112,7 @@ Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `k
| `close_reason` | 含义 |
|----------------|------|
| `box_opposite_break` | 标记价先突破反向边界(多:≤下沿;空:≥上沿) |
| `rr_insufficient` | 门控通过但 RR 不达标或 SL/TP 几何无效 |
| `exchange_failed` | RR 达标但实盘/交易所等原因未开仓 |
| `auto_opened` | RR 达标且市价开仓成功 |
@@ -117,7 +120,37 @@ Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `k
---
## 四、环境与参数(`.env` 摘要
## 四、回调 / 突破触价开仓(程序触价,无交易所挂单
### 4.1 录入
- **回调触价开仓**:方向必选多/空;填写 **计划入场价 E**、**止损 SL**、**止盈 TP**(做多须 `SL < E < TP`)。
- **突破触价开仓**:同上;添加时当前价须在突破方向一侧(做多:价低于 E;做空:价高于 E)。
- 计划 RR 以 **E** 为基准,须 **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5)。
- 可选移动保本、时间平仓;**全仓杠杆模式**下可用。
### 4.2 触发与结案
| 类型 | 触发条件(标记价) |
|------|-------------------|
| **回调触价** | 做多 `≤ E`;做空 `≥ E` → 下一轮询市价开仓 |
| **突破触价** | 做多**向上穿越** E;做空**向下穿越** E → **立即**市价开仓 |
- 未成交前标记价先触 **TP 侧**`trigger_tp_invalidate`
- **突破触价**另:未穿越 E 先触 **SL 侧**`trigger_sl_invalidate`
- **24h** 未触发 → `trigger_entry_expired`
- 成功 → `trigger_entry_filled`;触发后开仓失败 → `trigger_exchange_failed`
### 4.3 计仓与占位
- **以损定仓**:按 E、SL 反推保证金,触发时重算;**全仓杠杆**:可用×缓冲比例,BTC/ETH 10x、其它 5x。
- **占当日开仓意图**(已开 + 待触发),未成交不占持仓;同币仅 1 条触价监控(含回调/突破)。
共享逻辑:`trigger_entry_key_monitor_lib.py`;轮询:`check_trigger_entry_key_monitors`
---
## 五、环境与参数(`.env` 摘要)
| 变量 | 箱体/收敛 | 阻力/支撑 |
|------|-----------|-----------|
@@ -130,7 +163,7 @@ Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `k
---
## 、相关代码
## 、相关代码
| 说明 | 位置 |
|------|------|
+4 -2
View File
@@ -1,10 +1,12 @@
# `crypto_monitor_gate` 部署指南:SSH SOCKS + Gate.io + PM2Ubuntu
Ubuntu 环境(Python / Node / PM2、/opt 路径)见 **[docs/ubuntu-server.md](../docs/ubuntu-server.md)**。
本文面向:**在本机运行本项目**,但 **直连 Gate.io API 不稳定或被重置** 的场景。思路是:
- 本机用 `ssh -D` 做动态转发,把 **SOCKS5 出口**放到能正常访问 Gate 的机器(常见为一台境外 VPS)
- 项目在 `.env` 中设置 **`GATE_SOCKS_PROXY=socks5h://127.0.0.1:1080`**(或你实际端口),`ccxt` 经 SOCKS 访问交易所
- **SSH 隧道**:用 `ssh -D` 在本机常驻即可(screen / tmux / systemd 等),**不必交给 PM2**
- **SSH 隧道**:用 `ssh -D` 在本机常驻(可用 **tmux****autossh** 保持连接),**不要** 把 `ssh` 交给 PM2
- 使用 **PM2** 仅托管 **Flask 应用**;仓库根目录 **`ecosystem.config.cjs`** 只定义 `crypto-monitor-gate`
> 安全提醒:不要把 `.env`、私钥 `.pem`、Gate API Key 提交到 Git;下文只用占位符。
@@ -238,7 +240,7 @@ pm2 startup
### 9.1 SSH SOCKS(自行后台常驻,不推荐用 PM2)
示例(前台;实际可用 `screen`/`tmux`/`-f` 后台化或 systemd):
示例(前台调试;生产请用 **PM2**,见本文与 [docs/ubuntu-server.md](../docs/ubuntu-server.md)):
```bash
ssh -N -D 127.0.0.1:1080 gate-vps \
+62 -22
View File
@@ -21,9 +21,9 @@ APP_PORT=5002
APP_DEBUG=false
# 登录账号
APP_USERNAME=dekun
APP_USERNAME=admin
# 登录密码(请改成你自己的强密码)
APP_PASSWORD=ChangeMe123!
APP_PASSWORD=admin123
# 是否关闭登录校验(局域网可设 true;公网务必 false)
APP_AUTH_DISABLED=true
# --- 多账户交易中控 manual_trading_hub ---
@@ -62,9 +62,8 @@ BTC_LEVERAGE=10
ALT_LEVERAGE=5
# 交易日重置小时(北京时间)
TRADING_DAY_RESET_HOUR=8
# Gate 平仓历史:同步「趋势回调」交易记录与交易所已实现盈亏(北京日期 00:00 起,与 APP_TIMEZONE 一致);留空则从近 90 天拉取
# EXCHANGE_POSITION_SYNC_FROM_BJ=2026-05-14
# EXCHANGE_POSITION_HISTORY_LIMIT=200
# 整点前禁止新开仓:true=启用(默认),false=关闭(仍可保留 8 点作为交易日划分)
TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
# 是否开启 Gate 实盘下单(false=只做本地流程,true=真实下单)
LIVE_TRADING_ENABLED=true
@@ -82,27 +81,74 @@ GATE_TPSL_USE_POSITION_ORDER=true
GATE_TPSL_TRIGGER_EXPIRATION=604800
# 触发参考价:0=最新成交 1=标记价 2=指数价(非法值按 0)
GATE_TPSL_PRICE_TYPE=0
# 仓位类 TP/SL 相对现价的最小间距(%),避免 Gate 1026「触发价须高于/低于现价」
GATE_TPSL_LAST_PRICE_GAP_PCT=0.05
# 页面与浏览器标签展示的交易所名称(多环境区分时可改成例如 Gate·模拟)
# EXCHANGE_DISPLAY_NAME=Gate.io
# =============================================================================
# 交易执行 / 开仓限制(与 crypto_monitor_gate 主站一致
# 关键位门控(页面「关键位监控」规则条与 _key_hard_checks 共用
# =============================================================================
# 【最大同时持仓】active 下单监控数达到该值后禁止再开仓(默认 1=单仓)
MAX_ACTIVE_POSITIONS=1
# 整点前禁止新开仓:true=启用(默认),false=关闭(交易日划分仍用 TRADING_DAY_RESET_HOUR
# TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
# 关键位监控:5m收线突破过滤参数
# 【周期】门控 K 线周期,如 5m、15m
KLINE_TIMEFRAME=5m
KEY_BREAKOUT_LIMIT_PCT=1.5
# 【确认K】闭合 K 序列中的棒偏移:突破棒默认 -2,确认棒默认 -1
KEY_CONFIRM_BREAKOUT_BAR=-2
KEY_CONFIRM_BAR=-1
# 【量能】突破棒成交量 > 前 N 根均量 × 倍数
KEY_VOLUME_MA_BARS=20
KEY_VOLUME_RATIO_MIN=1.3
# 【突破K实体幅度】占开盘价百分比区间
# 【箱体/收敛】突破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
# 【关键位自动开仓盈亏比】严格大于该值才市价开仓
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
KEY_ALERT_MAX_TIMES=3
KEY_ALERT_INTERVAL_MINUTES=5
# =============================================================================
# 交易执行 / 人工风控(页面「实盘下单」)
# =============================================================================
# 【最大同时持仓】默认 1=单仓
MAX_ACTIVE_POSITIONS=1
# 【人工下单最低盈亏比】低于该值前后端均拒绝(默认 1.4,即须 >=1.4:1
MANUAL_MIN_PLANNED_RR=1.4
# 【关键位连开计仓】已有持仓时按无仓时资金快照算基数
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
# 重启后多少秒内不做「外部平仓」同步(避免 API 未就绪误判)
RECONCILE_STARTUP_GRACE_SEC=90
# 连续多少次轮询确认交易所空仓后,才记为外部平仓(默认 3 次 ≈ 9 秒)
RECONCILE_FLAT_CONFIRM_POLLS=3
# 使用可用资金时的缓冲比例(如0.98代表用98%)
FULL_MARGIN_BUFFER_RATIO=0.98
@@ -110,7 +156,7 @@ FULL_MARGIN_BUFFER_RATIO=0.98
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
# =============================================================================
AUTO_TRANSFER_ENABLED=false
# 交易账户(AUTO_TRANSFER_TO)补足到的 USDT 总额,非每日开仓基数
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
AUTO_TRANSFER_AMOUNT=30
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
@@ -126,6 +172,7 @@ FORCE_CLOSE_ENABLED=false
WECHAT_TIMEOUT_SECONDS=10
AI_TIMEOUT_SECONDS=120
# AI 提供方:openai(默认)| ollama
AI_PROVIDER=openai
OPENAI_API_BASE=https://op.bz121.com/v1
OPENAI_API_KEY=你的密钥
@@ -148,7 +195,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=5
# 详见 DAILY_OPEN_ALERT_THRESHOLD / DAILY_OPEN_HARD_LIMIT;说明文档 docs/daily-open-limit.md
# 以损定仓(按交易账户资金的百分比)
# RISK_PERCENT=2
# 移动保本触发(达到多少R触发)与偏移(百分比)
@@ -159,12 +206,5 @@ AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
# 开单风格默认值:trend / swing
# DEFAULT_TRADE_STYLE=trend
# 趋势回调策略(可选,见 趋势回调策略说明.md)
# TREND_PULLBACK_DCA_LEGS=5
# TREND_PULLBACK_PREVIEW_TTL_SECONDS=120
# 趋势回调手动保本:相对持仓均价的默认偏移(%);多=均价×(1+pct/100),空=均价×(1-pct/100)
# TREND_PULLBACK_MANUAL_BREAKEVEN_OFFSET_PCT=0.3
# TREND_PREVIEW_MAX_BALANCE_DRIFT_PCT=5
APP_TIMEZONE=Asia/Shanghai
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日;开仓整点限制见 TRADING_DAY_RESET_OPEN_GUARD_ENABLED
+90
View File
@@ -0,0 +1,90 @@
# crypto_monitor_gate
基于 **Flask** 的加密货币 **下单监控 / 关键位监控 / 交易复盘** 小系统,行情与实盘接口统一走 **Gate.io USDT 永续**,通过 **ccxt** 访问。
## 文档导航
| 文档 | 说明 |
|------|------|
| **[使用说明.md](./使用说明.md)** | 日常怎么用:登录、关键位四类、手工开仓、单仓与微信等 |
| **[关键位自动下单说明.md](./关键位自动下单说明.md)** | 关键位自动开仓的 RR、止盈止损、结案原因与 `.env` |
| **[部署文档.md](./部署文档.md)** | Ubuntu、PM2、**SSH SOCKS** 访问 Gate API 等 |
另:**Binance U 本位** 对等实现见同级的 **`crypto_monitor_binance`** 仓库。
---
## 功能概要
- **关键位监控**:5m 收线硬条件、企业微信推送;**箱体 / 收敛** 在 RR 达标时可 **自动市价开仓**(见专门文档);**阻力 / 支撑** 仅单次提醒结案
- **下单监控**:本地风控(含移动保本)、止盈/止损触达后轮询尝试平仓并记账
- **实盘(可选)**`LIVE_TRADING_ENABLED=true` 且配置 **`GATE_API_KEY` / `GATE_API_SECRET`** 时,支持开仓、挂单 TP/SL、余额与划转(权限依账户而定)
- **止盈止损(Gate**:市价成交后经 **`_gate_place_tp_sl_orders`** 挂单;优先 **仓位类 `price_orders`**(受 `GATE_TPSL_USE_POSITION_ORDER``GATE_TPSL_PRICE_TYPE``GATE_POS_MODE` 等影响)
---
## 环境要求
- Python 3.10+(建议)
- 依赖:`flask``requests``ccxt``werkzeug``PySocks`(经 SOCKS 代理时);`Pillow`K 线导出等可选用)
安装示例:
```bash
cd /opt/crypto_monitor/crypto_monitor_gate
source .venv/bin/activate
pip install -r ../requirements.txt
```
## 配置(`.env.example``.env`
- **`.env.example`**:模板(可提交 Git);首次:`cp .env.example .env` 后编辑。
- **`.env`**:本机真实配置(勿提交);`git pull` 不覆盖;升级前建议备份(见《部署文档》§5.2)。
项目启动时加载**仓库根目录**下的 `.env`。常用项:
| 变量 | 说明 |
|------|------|
| `GATE_API_KEY` / `GATE_API_SECRET` | Gate API(需合约与对应权限) |
| `LIVE_TRADING_ENABLED` | `true` 允许真实下单;`false` 仅本地与推送逻辑 |
| `GATE_MARGIN_MODE` / `GATE_POS_MODE` | 保证金与持仓模式 |
| `GATE_TPSL_USE_POSITION_ORDER` / `GATE_TPSL_PRICE_TYPE` 等 | 条件止盈止损行为 |
| `GATE_SOCKS_PROXY` | 可选;直连不稳时 SSH 动态转发(详见部署文档) |
| `APP_PASSWORD` / `FLASK_SECRET_KEY` | Web 登录与 Session |
| `WECHAT_WEBHOOK` | 企业微信机器人 |
| `EXCHANGE_DISPLAY_NAME` / `GATE_ACCOUNT_LABEL` | 页面与推送展示的账户文案 |
其余见 **`.env.example` 内注释** 或 **`app.py` 顶部默认值**。
## 运行
生产使用 **PM2**`ecosystem.config.cjs`)。调试:
```bash
source .venv/bin/activate && python app.py
```
见 [docs/ubuntu-server.md](../docs/ubuntu-server.md)。
端口由 **`APP_PORT`** 控制(未设置默认 **5000**)。浏览器登录 **`/login`**,口令为 **`APP_PASSWORD`**。
## 部署(Linux / PM2 / SSH SOCKS
**[部署文档.md](./部署文档.md)**。
## 自检脚本
```bash
python scripts/verify_gate_funding.py
```
用于核对密钥前缀(不落 Secret)、资金/合约可读性等(需网络与权限)。
## 数据与脚本
- 默认 SQLite:由 **`DB_PATH`** 指定(常见为项目下 `crypto.db`
- `scripts/fix_breakeven_labels.py`:修正「止损」但盈亏为正的记录标签(参见部署文档说明)
## 风险与合规
实盘有亏损风险。请确认 API 权限、IP 白名单、杠杆与保证金模式与 **Gate.io** 后台一致,并遵守当地法律法规与交易所用户协议。
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* PM2 进程定义Ubuntu / Linux
*
* 仅托管 Flask 应用**SSH SOCKS 隧道请在本机用 screen/tmux/systemd 等方式单独常驻**
* 仅托管 Flask 应用**SSH SOCKS 隧道** `ssh -D` 常驻可用 tmux / autossh勿交给 PM2
* `.env` `GATE_SOCKS_PROXY` 端口一致即可不必交给 PM2
*
* 使用前项目根目录存在 `.venv`且已安装依赖 SOCKS 时需 PySocks
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""One-shot SQLite backup before code deploy. Reads DB_PATH from .env (default crypto.db)."""
from __future__ import annotations
import os
import shutil
import sqlite3
from datetime import datetime
from pathlib import Path
PROJECT_DIR = Path(__file__).resolve().parent.parent
def _read_env_db_path() -> Path:
env_file = PROJECT_DIR / ".env"
default = PROJECT_DIR / "crypto.db"
if not env_file.is_file():
return default
for line in env_file.read_text(encoding="utf-8", errors="replace").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, val = line.split("=", 1)
if key.strip() != "DB_PATH":
continue
val = val.strip().strip('"').strip("'")
p = Path(val)
return p if p.is_absolute() else PROJECT_DIR / p
return default
def main() -> int:
db_path = _read_env_db_path()
if not db_path.is_file():
print(f"error: database not found: {db_path}")
return 1
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
dest_dir = PROJECT_DIR / "backups" / stamp
dest_dir.mkdir(parents=True, exist_ok=True)
dest = dest_dir / db_path.name
try:
src = sqlite3.connect(str(db_path))
dst = sqlite3.connect(str(dest))
src.backup(dst)
dst.close()
src.close()
method = "sqlite3 backup"
except Exception:
shutil.copy2(db_path, dest)
method = "file copy"
manifest = dest_dir / "manifest.txt"
manifest.write_text(
"\n".join(
[
f"project_dir={PROJECT_DIR}",
f"source_db={db_path}",
f"backup_file={dest}",
f"method={method}",
f"created_at={stamp}",
]
),
encoding="utf-8",
)
print(f"ok: {dest} ({method})")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#22d3ee"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="108" fill="#0c1019"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="#141b2d"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="none" stroke="url(#g)" stroke-width="12"/>
<path d="M120 320 L200 248 L280 272 L392 168" fill="none" stroke="url(#g)" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="392" cy="168" r="18" fill="#34d399"/>
<rect x="168" y="268" width="28" height="64" rx="6" fill="#f87171"/>
<line x1="182" y1="248" x2="182" y2="340" stroke="#f87171" stroke-width="10" stroke-linecap="round"/>
<rect x="268" y="220" width="28" height="96" rx="6" fill="#34d399"/>
<line x1="282" y1="200" x2="282" y2="340" stroke="#34d399" stroke-width="10" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,23 @@
{
"name": "交易监控复盘",
"short_name": "监控",
"description": "加密货币永续交易监控与复盘",
"start_url": "/",
"display": "standalone",
"background_color": "#0b0d14",
"theme_color": "#0b0d14",
"icons": [
{
"src": "/static/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
File diff suppressed because it is too large Load Diff
@@ -1,261 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ exchange_display }} | 关键位放大</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px}
.container{width:min(98vw,1900px);margin:0 auto}
.card{background:#121726;border-radius:10px;padding:12px;border:1px solid #2a3150;margin-bottom:12px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.btn{padding:7px 10px;border-radius:8px;text-decoration:none;border:1px solid #304164;background:#151a2a;color:#8fc8ff;cursor:pointer}
.btn:hover{background:#1f2740}
input,select,button{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff}
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:10px}
.meta-item{background:#141b2f;border:1px solid #27324e;border-radius:8px;padding:8px}
.meta-item .k{font-size:.76rem;color:#9fb0d8}
.meta-item .v{font-size:1rem;margin-top:4px;word-break:break-all}
.status{font-size:.84rem;color:#95a2c2}
.status.err{color:#ff8080}
#chart-wrap{height:580px;background:#0f1320;border:1px solid #2a3150;border-radius:10px;padding:8px}
#chart{width:100%;height:100%}
.exchange-tag{font-size:.72rem;font-weight:600;color:#b8f5d0;background:#14241e;border:1px solid #2d6a4f;padding:4px 10px;border-radius:999px;margin-left:8px}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="row" style="justify-content:space-between">
<div class="row">
<a class="btn" href="/">返回首页</a>
<strong style="color:#dbe4ff">关键位放大(可输入币种)</strong><span class="exchange-tag">{{ exchange_display }}</span>
</div>
<div class="status">最近刷新:<span id="updated-at">--</span></div>
</div>
<div class="row" style="margin-top:10px">
<label>币种</label>
<input id="symbol-input" value="{{ default_symbol }}" placeholder="BTC/USDT">
<label>关键位</label>
<select id="key-id">
<option value="">无(仅看K线)</option>
{% for k in key_list %}
<option value="{{ k.id }}" {% if selected_key and k.id == selected_key.id %}selected{% endif %}>#{{ k.id }} {{ k.symbol }} {{ k.monitor_type }} {{ '做多' if k.direction == 'long' else '做空' }}</option>
{% endfor %}
</select>
<label>周期</label>
<select id="timeframe">
{% for tf in ['1m','3m','5m','15m','30m','1h','4h','1d'] %}
<option value="{{ tf }}" {% if tf == default_timeframe %}selected{% endif %}>{{ tf }}</option>
{% endfor %}
</select>
<label>K线数</label>
<select id="kline-limit">
<option value="100" {% if default_kline_limit == 100 %}selected{% endif %}>100</option>
<option value="200" {% if default_kline_limit == 200 %}selected{% endif %}>200</option>
</select>
<button id="manual-refresh" type="button">刷新</button>
<span id="load-status" class="status"></span>
</div>
</div>
<div class="card">
<div class="meta">
<div class="meta-item"><div class="k">交易对</div><div class="v" id="m-symbol">-</div></div>
<div class="meta-item"><div class="k">监控类型</div><div class="v" id="m-type">-</div></div>
<div class="meta-item"><div class="k">方向</div><div class="v" id="m-direction">-</div></div>
<div class="meta-item"><div class="k">上沿/阻力</div><div class="v" id="m-upper">-</div></div>
<div class="meta-item"><div class="k">下沿/支撑</div><div class="v" id="m-lower">-</div></div>
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
<div class="meta-item"><div class="k">距上沿</div><div class="v" id="m-updiff">-</div></div>
<div class="meta-item"><div class="k">距下沿</div><div class="v" id="m-lowdiff">-</div></div>
</div>
</div>
<div class="card"><div id="chart-wrap"><div id="chart"></div></div></div>
</div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script>
const refreshMs = Math.max({{ price_refresh_seconds * 1000 }}, 5000);
const keySelect = document.getElementById("key-id");
const symbolInput = document.getElementById("symbol-input");
const tfSelect = document.getElementById("timeframe");
const limitSelect = document.getElementById("kline-limit");
const statusEl = document.getElementById("load-status");
const updatedAtEl = document.getElementById("updated-at");
const chartHost = document.getElementById("chart");
const fmt = (v,d=6)=>(v===null||typeof v==="undefined"||Number.isNaN(Number(v)))?"-":Number(v).toFixed(d);
const fmtSigned = (v,d=4)=>{
if(v===null||typeof v==="undefined"||Number.isNaN(Number(v))) return "-";
const n = Number(v);
return `${n>0?"+":""}${n.toFixed(d)}`;
};
let chart = null;
let candleSeries = null;
let priceLines = [];
const keyMap = {};
{% for k in key_list %}
keyMap["{{ k.id }}"] = "{{ k.symbol }}";
{% endfor %}
function ensureChart(){
if(chart && candleSeries) return true;
if(!window.LightweightCharts){
statusEl.className = "status err";
statusEl.innerText = "图表库加载失败";
return false;
}
if(!chart){
chart = LightweightCharts.createChart(chartHost, {
layout:{background:{color:"#0f1320"},textColor:"#d6deff"},
grid:{vertLines:{color:"#1e263d"},horzLines:{color:"#1e263d"}},
rightPriceScale:{borderColor:"#2a3150"},
timeScale:{borderColor:"#2a3150",timeVisible:true,secondsVisible:false},
crosshair:{mode:0}
});
window.addEventListener("resize",()=>{
chart.applyOptions({width:chartHost.clientWidth,height:chartHost.clientHeight});
});
chart.applyOptions({width:chartHost.clientWidth,height:chartHost.clientHeight});
}
const opts = {
upColor: "#4cd97f",
downColor: "#ff6666",
borderVisible: false,
wickUpColor: "#4cd97f",
wickDownColor: "#ff6666"
};
if (typeof chart.addCandlestickSeries === "function") {
candleSeries = chart.addCandlestickSeries(opts);
} else if (typeof chart.addSeries === "function" && window.LightweightCharts && window.LightweightCharts.CandlestickSeries) {
candleSeries = chart.addSeries(window.LightweightCharts.CandlestickSeries, opts);
}
if(!candleSeries){
statusEl.className = "status err";
statusEl.innerText = "K线序列初始化失败";
return false;
}
return true;
}
function resetPriceLines(){
if(!candleSeries) return;
priceLines.forEach(line=>{ try { candleSeries.removePriceLine(line); } catch (_) {} });
priceLines = [];
}
function addLine(price, title, color){
if(!candleSeries || price===null || typeof price==="undefined") return;
const p = Number(price);
if(Number.isNaN(p) || p<=0) return;
priceLines.push(candleSeries.createPriceLine({
price:p,color,lineWidth:1,lineStyle:0,axisLabelVisible:true,title
}));
}
function paintMeta(data){
const key = data.key_monitor || null;
document.getElementById("m-symbol").innerText = data.symbol || "-";
document.getElementById("m-price").innerText = fmt(data.current_price,8);
if(!key){
document.getElementById("m-type").innerText = "未匹配到关键位";
document.getElementById("m-direction").innerText = "-";
document.getElementById("m-upper").innerText = "-";
document.getElementById("m-lower").innerText = "-";
document.getElementById("m-updiff").innerText = "-";
document.getElementById("m-lowdiff").innerText = "-";
return;
}
document.getElementById("m-type").innerText = key.monitor_type || "-";
document.getElementById("m-direction").innerText = key.direction === "short" ? "做空" : "做多";
document.getElementById("m-upper").innerText = fmt(key.upper,8);
document.getElementById("m-lower").innerText = fmt(key.lower,8);
document.getElementById("m-updiff").innerText = `${fmtSigned(key.upper_diff,4)} (${fmtSigned(key.upper_pct,2)}%)`;
document.getElementById("m-lowdiff").innerText = `${fmtSigned(key.lower_diff,4)} (${fmtSigned(key.lower_pct,2)}%)`;
}
function syncSymbolByKey(){
const keyId = keySelect.value;
if(keyId && keyMap[keyId]) symbolInput.value = keyMap[keyId];
}
async function loadKeyKline(){
if(!ensureChart()) return;
const keyId = keySelect.value;
const symbol = (symbolInput.value || "").trim().toUpperCase();
const timeframe = tfSelect.value;
const limit = limitSelect.value;
if(!symbol && !keyId){
statusEl.className = "status err";
statusEl.innerText = "请先输入币种或选择关键位";
return;
}
statusEl.className = "status";
statusEl.innerText = "加载中...";
try{
const qs = new URLSearchParams();
if(keyId) qs.set("key_id", keyId);
if(symbol) qs.set("symbol", symbol);
qs.set("timeframe", timeframe);
qs.set("limit", limit);
const resp = await fetch(`/api/key_kline?${qs.toString()}`);
const data = await resp.json();
if(!resp.ok || !data.ok) throw new Error(data.msg || "请求失败");
const candles = Array.isArray(data.candles) ? data.candles : [];
if(!candles.length){
statusEl.className = "status err";
statusEl.innerText = "暂无K线数据";
return;
}
if(!candleSeries) throw new Error("Series init failed");
candleSeries.setData(candles);
resetPriceLines();
addLine(data.current_price, "现价", "#42a5f5");
if(data.key_monitor){
addLine(data.key_monitor.upper, "上沿/阻力", "#ffb84d");
addLine(data.key_monitor.lower, "下沿/支撑", "#4cd97f");
}
chart.timeScale().fitContent();
paintMeta(data);
updatedAtEl.innerText = data.updated_at || "--";
statusEl.className = "status";
statusEl.innerText = `已加载 ${candles.length} 根K线`;
}catch(err){
statusEl.className = "status err";
statusEl.innerText = err && err.message ? err.message : "加载失败";
}
}
document.getElementById("manual-refresh").addEventListener("click", loadKeyKline);
keySelect.addEventListener("change", ()=>{ syncSymbolByKey(); loadKeyKline(); });
symbolInput.addEventListener("change", ()=>{
if(symbolInput.value.trim()) keySelect.value = "";
loadKeyKline();
});
tfSelect.addEventListener("change", loadKeyKline);
limitSelect.addEventListener("change", loadKeyKline);
syncSymbolByKey();
loadKeyKline();
setInterval(loadKeyKline, refreshMs);
</script>
</body>
</html>
+136 -118
View File
@@ -1,118 +1,136 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录 · {{ exchange_display }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a10;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
}
.login-box {
background: #12121a;
padding: 2.5rem;
border-radius: 16px;
width: 100%;
max-width: 400px;
border: 1px solid #242435;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.login-box h2 {
margin-bottom: 2rem;
text-align: center;
font-size: 1.5rem;
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #a9a9ff;
}
.form-group input {
width: 100%;
padding: 0.85rem 1rem;
border-radius: 10px;
border: 1px solid #2e2e45;
background: #1a1a29;
color: #fff;
font-size: 0.95rem;
outline: none;
}
.form-group input:focus {
border-color: #4cc2ff;
}
button {
width: 100%;
padding: 0.9rem;
border-radius: 10px;
border: none;
background: linear-gradient(90deg, #4285f4, #7b42ff);
color: #fff;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
}
.flash {
padding: 0.8rem;
margin-bottom: 1rem;
background: #331e24;
color: #ff6666;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
.exchange-line {
text-align: center;
font-size: 0.82rem;
color: #8892b0;
margin: -0.5rem 0 1.25rem;
}
.exchange-line strong {
color: #b8f5d0;
font-weight: 600;
}
</style>
</head>
<body>
<div class="login-box">
<h2>交易监控系统登录</h2>
<p class="exchange-line">交易所:<strong>{{ exchange_display }}</strong></p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="POST">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" required placeholder="请输入账号">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required placeholder="请输入密码">
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8">
<script src="/static/instance_theme.js?v=4"></script>
<title>登录 · {{ exchange_display }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a10;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
}
.login-box {
background: #12121a;
padding: 2.5rem;
border-radius: 16px;
width: 100%;
max-width: 400px;
border: 1px solid #242435;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.login-box h2 {
margin-bottom: 2rem;
text-align: center;
font-size: 1.5rem;
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #a9a9ff;
}
.form-group input {
width: 100%;
padding: 0.85rem 1rem;
border-radius: 10px;
border: 1px solid #2e2e45;
background: #1a1a29;
color: #fff;
font-size: 0.95rem;
outline: none;
}
.form-group input:focus {
border-color: #4cc2ff;
}
button {
width: 100%;
padding: 0.9rem;
border-radius: 10px;
border: none;
background: linear-gradient(90deg, #4285f4, #7b42ff);
color: #fff;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
}
.flash {
padding: 0.8rem;
margin-bottom: 1rem;
background: #331e24;
color: #ff6666;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
.exchange-line {
text-align: center;
font-size: 0.82rem;
color: #8892b0;
margin: -0.5rem 0 1.25rem;
}
.exchange-line strong {
color: #b8f5d0;
font-weight: 600;
}
</style>
<link rel="stylesheet" href="/static/instance_theme.css?v=4">
</head>
<div class="login-theme-bar">
<div class="theme-toggle instance-theme-toggle" role="group" aria-label="界面主题">
<button type="button" class="theme-toggle-btn is-active" data-theme-value="dark" aria-pressed="true" title="暗色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M12.1 3a9 9 0 1 0 8.9 11 6.5 6.5 0 1 1-8.9-11z"/>
</svg>
</button>
<button type="button" class="theme-toggle-btn" data-theme-value="light" aria-pressed="false" title="亮色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
</svg>
</button>
</div>
</div>
<body>
<div class="login-box">
<h2>交易监控系统登录</h2>
<p class="exchange-line">交易所:<strong>{{ exchange_display }}</strong></p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="POST" autocomplete="off">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" required placeholder="请输入账号" autocomplete="off" autocapitalize="off" spellcheck="false">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required placeholder="请输入密码" autocomplete="new-password">
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
@@ -140,13 +140,13 @@ function addLine(price, title, color){
function paintOrder(order){
document.getElementById("m-symbol").innerText = order.symbol || "-";
document.getElementById("m-direction").innerText = (order.direction === "short") ? "做空" : "做多";
document.getElementById("m-entry").innerText = fmt(order.trigger_price, 8);
document.getElementById("m-sl").innerText = fmt(order.stop_loss, 8);
document.getElementById("m-tp").innerText = fmt(order.take_profit, 8);
document.getElementById("m-entry").innerText = order.trigger_price_display || fmt(order.trigger_price, 8);
document.getElementById("m-sl").innerText = order.stop_loss_display || fmt(order.stop_loss, 8);
document.getElementById("m-tp").innerText = order.take_profit_display || fmt(order.take_profit, 8);
document.getElementById("m-rr").innerText = (order.rr_ratio === null || typeof order.rr_ratio === "undefined") ? "-" : `1:${Number(order.rr_ratio).toFixed(2)}`;
document.getElementById("m-price").innerText = fmt(order.current_price, 8);
document.getElementById("m-price").innerText = order.current_price_display || fmt(order.current_price, 8);
const pnlEl = document.getElementById("m-pnl");
pnlEl.innerText = `${fmt(order.float_pnl, 4)}U (${fmt(order.float_pct, 2)}%)`;
pnlEl.innerText = `${fmt(order.float_pnl, 2)}U (${fmt(order.float_pct, 2)}%)`;
pnlEl.style.color = Number(order.float_pnl || 0) > 0 ? "#4cd97f" : (Number(order.float_pnl || 0) < 0 ? "#ff6666" : "#d6deff");
}
@@ -1,214 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ exchange_display }} | 实盘下单放大</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px}
.container{width:min(98vw,1900px);margin:0 auto}
.card{background:#121726;border-radius:10px;padding:12px;border:1px solid #2a3150;margin-bottom:12px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.btn{padding:7px 10px;border-radius:8px;text-decoration:none;border:1px solid #304164;background:#151a2a;color:#8fc8ff;cursor:pointer}
.btn:hover{background:#1f2740}
select,button{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff}
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:10px}
.meta-item{background:#141b2f;border:1px solid #27324e;border-radius:8px;padding:8px}
.meta-item .k{font-size:.76rem;color:#9fb0d8}
.meta-item .v{font-size:1rem;margin-top:4px;word-break:break-all}
.status{font-size:.84rem;color:#95a2c2}
.status.err{color:#ff8080}
#chart-wrap{height:560px;background:#0f1320;border:1px solid #2a3150;border-radius:10px;padding:8px}
#chart{width:100%;height:100%}
.empty{padding:18px;color:#95a2c2}
.exchange-tag{font-size:.72rem;font-weight:600;color:#b8f5d0;background:#14241e;border:1px solid #2d6a4f;padding:4px 10px;border-radius:999px;margin-left:8px}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="row" style="justify-content:space-between">
<div class="row">
<a class="btn" href="/">返回首页</a>
<strong style="color:#dbe4ff">实盘下单放大(100根K线)</strong><span class="exchange-tag">{{ exchange_display }}</span>
</div>
<div class="status">最近刷新:<span id="updated-at">--</span></div>
</div>
{% if orders %}
<div class="row" style="margin-top:10px">
<label>订单</label>
<select id="order-id">
{% for o in orders %}
<option value="{{ o.id }}" {% if selected_order and o.id == selected_order.id %}selected{% endif %}>
#{{ o.id }} {{ o.symbol }} {{ '做多' if o.direction == 'long' else '做空' }}
</option>
{% endfor %}
</select>
<label>周期</label>
<select id="timeframe">
{% for tf in ['1m','3m','5m','15m','30m','1h','4h','1d'] %}
<option value="{{ tf }}" {% if tf == default_timeframe %}selected{% endif %}>{{ tf }}</option>
{% endfor %}
</select>
<button id="manual-refresh" type="button">刷新</button>
<span id="load-status" class="status"></span>
</div>
{% else %}
<div class="empty">当前没有激活订单,无法展示放大K线。</div>
{% endif %}
</div>
{% if orders %}
<div class="card">
<div class="meta">
<div class="meta-item"><div class="k">交易对</div><div class="v" id="m-symbol">-</div></div>
<div class="meta-item"><div class="k">方向</div><div class="v" id="m-direction">-</div></div>
<div class="meta-item"><div class="k">成交价</div><div class="v" id="m-entry">-</div></div>
<div class="meta-item"><div class="k">止损</div><div class="v" id="m-sl">-</div></div>
<div class="meta-item"><div class="k">止盈</div><div class="v" id="m-tp">-</div></div>
<div class="meta-item"><div class="k">盈亏比</div><div class="v" id="m-rr">-</div></div>
<div class="meta-item"><div class="k">移动保本</div><div class="v" id="m-breakeven">-</div></div>
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
<div class="meta-item"><div class="k">浮盈亏</div><div class="v" id="m-pnl">-</div></div>
</div>
</div>
<div class="card">
<div id="chart-wrap"><div id="chart"></div></div>
</div>
{% endif %}
</div>
{% if orders %}
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
<script>
const refreshMs = Math.max({{ price_refresh_seconds * 1000 }}, 5000);
const orderSelect = document.getElementById("order-id");
const tfSelect = document.getElementById("timeframe");
const statusEl = document.getElementById("load-status");
const updatedAtEl = document.getElementById("updated-at");
const chartHost = document.getElementById("chart");
const fmt = (v, d=6) => (v === null || typeof v === "undefined" || Number.isNaN(Number(v))) ? "-" : Number(v).toFixed(d);
let chart = null;
let candleSeries = null;
let priceLines = [];
function ensureChart(){
if(chart){ return true; }
if(!window.LightweightCharts){
statusEl.className = "status err";
statusEl.innerText = "图表库加载失败";
return false;
}
chart = LightweightCharts.createChart(chartHost, {
layout: { background: { color: "#0f1320" }, textColor: "#d6deff" },
grid: { vertLines: { color: "#1e263d" }, horzLines: { color: "#1e263d" } },
rightPriceScale: { borderColor: "#2a3150" },
timeScale: { borderColor: "#2a3150", timeVisible: true, secondsVisible: false },
crosshair: { mode: 0 }
});
candleSeries = chart.addCandlestickSeries({
upColor: "#4cd97f",
downColor: "#ff6666",
borderVisible: false,
wickUpColor: "#4cd97f",
wickDownColor: "#ff6666"
});
window.addEventListener("resize", () => {
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
});
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
return true;
}
function resetPriceLines(){
if(!candleSeries){ return; }
priceLines.forEach(line => {
try { candleSeries.removePriceLine(line); } catch (_) {}
});
priceLines = [];
}
function addLine(price, title, color){
if(!candleSeries || typeof price === "undefined" || price === null){ return; }
const p = Number(price);
if(Number.isNaN(p) || p <= 0){ return; }
priceLines.push(candleSeries.createPriceLine({
price: p, color, lineWidth: 1, lineStyle: 0, axisLabelVisible: true, title
}));
}
function paintOrder(order){
document.getElementById("m-symbol").innerText = order.symbol || "-";
document.getElementById("m-direction").innerText = (order.direction === "short") ? "做空" : "做多";
document.getElementById("m-entry").innerText = fmt(order.trigger_price, 8);
document.getElementById("m-sl").innerText = fmt(order.stop_loss, 8);
document.getElementById("m-tp").innerText = fmt(order.take_profit, 8);
document.getElementById("m-rr").innerText = (order.rr_ratio === null || typeof order.rr_ratio === "undefined") ? "-" : `1:${Number(order.rr_ratio).toFixed(2)}`;
document.getElementById("m-breakeven").innerText =
(order.breakeven_enabled === false || order.breakeven_enabled === 0) ? "关闭" : "开启";
document.getElementById("m-price").innerText = fmt(order.current_price, 8);
const pnlEl = document.getElementById("m-pnl");
pnlEl.innerText = `${fmt(order.float_pnl, 4)}U (${fmt(order.float_pct, 2)}%)`;
pnlEl.style.color = Number(order.float_pnl || 0) > 0 ? "#4cd97f" : (Number(order.float_pnl || 0) < 0 ? "#ff6666" : "#d6deff");
}
async function loadOrderKline(){
if(!ensureChart()){ return; }
const orderId = orderSelect.value;
const timeframe = tfSelect.value;
if(!orderId){ return; }
statusEl.className = "status";
statusEl.innerText = "加载中...";
try{
const resp = await fetch(`/api/order_kline?order_id=${encodeURIComponent(orderId)}&timeframe=${encodeURIComponent(timeframe)}`);
const data = await resp.json();
if(!resp.ok || !data.ok){ throw new Error(data.msg || "请求失败"); }
const candles = Array.isArray(data.candles) ? data.candles : [];
if(!candles.length){
statusEl.className = "status err";
statusEl.innerText = "暂无K线数据";
return;
}
candleSeries.setData(candles);
resetPriceLines();
addLine(data.order.trigger_price, "成交价", "#42a5f5");
addLine(data.order.stop_loss, "止损", "#ff6666");
addLine(data.order.take_profit, "止盈", "#4cd97f");
chart.timeScale().fitContent();
paintOrder(data.order || {});
updatedAtEl.innerText = data.updated_at || "--";
statusEl.className = "status";
statusEl.innerText = `已加载 ${candles.length} 根K线`;
}catch(err){
statusEl.className = "status err";
statusEl.innerText = err && err.message ? err.message : "加载失败";
}
}
document.getElementById("manual-refresh").addEventListener("click", loadOrderKline);
orderSelect.addEventListener("change", loadOrderKline);
tfSelect.addEventListener("change", loadOrderKline);
loadOrderKline();
setInterval(loadOrderKline, refreshMs);
</script>
{% endif %}
<script>
(function(){
if (typeof ensureChart !== 'function') return;
const oldEnsureChart = ensureChart;
ensureChart = function(){
if (chart && candleSeries) return true;
try { const ok = oldEnsureChart(); if (ok && candleSeries) return true; } catch (_) {}
if (chart && !candleSeries && typeof chart.addSeries === 'function' && window.LightweightCharts && window.LightweightCharts.CandlestickSeries) {
const opts = { upColor:'#4cd97f', downColor:'#ff6666', borderVisible:false, wickUpColor:'#4cd97f', wickDownColor:'#ff6666' };
candleSeries = chart.addSeries(window.LightweightCharts.CandlestickSeries, opts);
return !!candleSeries;
}
return !!candleSeries;
};
})();
</script>
</body>
</html>
+147
View File
@@ -0,0 +1,147 @@
# 使用说明
**本文件对应仓库:`crypto_monitor_gate`Gate.io USDT 永续)。**
功能、界面与 **Binance U 本位版**(目录 `crypto_monitor_binance`)基本一致,差异主要在 **`.env` 里交易所密钥与部分参数名**`GATE_*` / `BINANCE_*`),文末有对照。
**更细的部署(SSH 代理、PM2、依赖安装)** 见同目录 **`部署文档.md`**。
**关键位自动开仓的规则、RR、结案原因** 见 **`关键位自动下单说明.md`**。
---
## 1. 它能做什么
面向个人盘面的 **Web 控制台**,主要能力包括:
| 模块 | 说明 |
|------|------|
| **关键位监控** | 录入上/下沿与类型,按 **5m 收线** 做硬条件过滤;符合条件后 **企业微信** 提醒,部分类型可 **自动市价开仓**(见第 4 节与专门文档)。 |
| **实盘下单监控** | 手工填止损/止盈,**以损定仓** 市价开单,挂上条件止盈止损,并在页面跟踪浮盈亏、保本逻辑等。 |
| **交易记录 / 复盘** | 平仓结果、盈亏、错过的单等归档与导出;可选 **AI 复盘**(见 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md))。 |
| **策略交易** | 顶栏 `/strategy`:趋势回调 + 顺势加仓双栏;见 [策略交易说明.md](../策略交易说明.md)。 |
后台按 **`MONITOR_POLL_SECONDS`**(默认几秒)轮询行情与监控逻辑。**切勿**在未理解规则时同时运行两套程序共用一个实盘账户。
---
## 2. 运行前必须配置(`.env`
首次在本目录执行 **`cp .env.example .env`**,再编辑 `.env``.env` 勿提交 Git`git pull` 不会改你的 `.env`,升级前建议 `cp .env .env.backup.$(date +%Y%m%d)`)。
至少检查以下项(具体键名以 **`.env.example`** 为准):
| 类别 | 说明 |
|------|------|
| **登录网页** | `APP_PASSWORD`:打开站点后的登录口令。`FLASK_SECRET_KEY`:Session 密钥,请勿使用默认值。 |
| **企业微信** | `WECHAT_WEBHOOK`:告警与关键位推送机器人的 Webhook。 |
| **是否真下单** | `LIVE_TRADING_ENABLED=false`:**不会**向交易所发送开仓指令(适合测试流程)。改为 `true` 且密钥正确才会实盘。 |
| **交易所 API** | **本仓库:** `GATE_API_KEY``GATE_API_SECRET`;合约相关见 `GATE_MARGIN_MODE``GATE_POS_MODE``GATE_TPSL_*` 等。**勿**把 `.env` 提交到 Git。 |
| **关键位 RR / 止损外扩** | `KEY_AUTO_MIN_PLANNED_RR``KEY_STOP_OUTSIDE_BREAKOUT_PCT`(详见 `关键位自动下单说明.md`)。 |
| **AI 复盘** | `AI_PROVIDER=openai`(默认)或 `ollama`;变量见 `.env.example` 与 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)。 |
网络不稳定时可为 Gate 配置 **`GATE_SOCKS_PROXY`** 等(见 **`部署文档.md`**)。
---
## 3. 如何启动与登录
1. 按 **`部署文档.md`** 建好虚拟环境、安装依赖(如 `flask``requests``ccxt`、按需 `Pillow``PySocks` 等),配置好 `.env`
2. 启动 Flask 应用(本仓库可用 **`ecosystem.config.cjs`** 交给 PM2,或本地 `python app.py` / `flask run`,以你当前脚本为准)。
3. 浏览器访问站点,打开 **`/login`**,使用 **`.env` 里的 `APP_PASSWORD`** 登录。
登录后顶栏:**关键位监控** | **实盘下单** | **策略交易**`/strategy`| **策略交易记录**`/strategy/records`| **交易记录与复盘** | **统计分析**
---
## 4. 关键位监控(顶栏「关键位监控」→ `/key_monitor`
### 4.1 添加一条关键位
1. **币种**:如 `BTC``BTC/USDT`(会规范成内部符号)。
2. **类型**(必选其一):
| 类型 | 行为摘要 |
|------|----------|
| **箱体突破** | 通过门控且计划 RR 达标 → **自动市价开仓**(需 `LIVE_TRADING_ENABLED=true` 且无其他持仓占位)。结案后本条从列表消失并记入历史。 |
| **收敛突破** | 同上(自动开仓类)。 |
| **关键阻力位** | **不自动开仓**;触发后 **发 1 次微信**,然后本条 **结案进历史**。 |
| **关键支撑位** | 同上(仅提醒)。 |
| **回调触价开仓** | **不挂交易所限价**;标记价回调触达 E 后 **下一轮询市价开仓**RR 门槛同 `KEY_AUTO_MIN_PLANNED_RR`);有效期 **24h** |
| **突破触价开仓** | **不挂交易所限价**;标记价 **穿越 E 立即市价开仓**;先触 SL/TP 侧失效;有效期 **24h** |
3. **方向**:做多 / 做空(触价开仓 / 箱体 / 收敛 / 斐波必选;阻力/支撑不选)。
4. **价位**:箱体/收敛/阻力/支撑填 **上沿 / 下沿**;触价开仓填 **入场 E / 止损 SL / 止盈 TP**
**限制:**
活跃持仓数达到 **`MAX_ACTIVE_POSITIONS`**(默认 1)时,**不允许**再添加「**箱体突破** / **收敛突破**」;仍可添加「**关键阻力位 / 支撑位**」。
**4h EMA55** 与你的方向逆势,页面会 **额外 Flash 提示****不阻挡**提交。
### 4.2 触发后会发生什么(简版)
- **箱体 / 收敛**:门控通过后计算计划 SL/TP 与 RR;不达标则 **微信说明 + `rr_insufficient` 结案**;达标则尝试 **市价开仓**,成功 **`auto_opened`**,失败 **`exchange_failed`**——均 **不重试同一关键位**
- **阻力 / 支撑**:仅 **单次推送****`key_level_alert_only`** 结案。
详细公式、结案字段、与企业微信文案口径见 **`关键位自动下单说明.md`**。
### 4.3 列表与历史
- 当前条目可 **删除**(会按规则记入历史的情形见页面说明)。
- **关键位历史**:已结案记录;可配合导出链接(若有)做备份。
---
## 5. 实盘下单(顶栏「实盘下单」→ `/trade`
用于 **自己点按钮** 开单:
- 持仓上限由 **`MAX_ACTIVE_POSITIONS`** 控制(默认 1,与关键位自动单共用)。
- **人工开仓**时计划盈亏比不得低于 **`MANUAL_MIN_PLANNED_RR`**(默认 1.4:1),否则页面弹窗且后端拒绝。
- 填写币种、方向、杠杆(可选)、止损/止盈(价格或百分比按表单说明)。
- 勾选是否启用 **移动保本** 等行为以 `.env`/页面默认值为准。
平仓通过页面 **平仓**(或等价入口),会从交易所市价处理并更新记录。**删除/误操作可能造成真实盈亏**,请先确认环境与方向。
开仓成功后持仓卡片上会显示 **「来源」**:手工单一般为 **下单监控**;来自关键位自动单的为 **关键位监控**
---
## 6. 企业微信会看到什么
- 关键位:按类型与结案结果推送(RR 不足、下单失败、自动开仓成功、仅阻力支撑提醒等),**每条关键位结案路径原则上一条主推送**(详见 `关键位自动下单说明.md`)。
- 手工开仓、平仓、部分异常也会在规则满足时推送(以代码与配置为准)。
若未配置 **`WECHAT_WEBHOOK`** 或网络失败,可能只是看不到推送,不代表逻辑未执行;要紧操作请以 **交易所端持仓与挂单** 为准核对。
---
## 7. 强烈建议的风险与运维习惯
1. **先用 `LIVE_TRADING_ENABLED=false`** 验证页面、录入、推送,再开小资金开实盘。
2. **API 权限**:仅开所需合约权限;勿泄露密钥;定期轮换。
3. **单进程控盘**:同一账户避免本程序与其他机器人 **重复开仓**
4. **自动备份**:服务器上执行 `bash scripts/install_backup_cron.sh`(每天北京时间 0:00 → `/root/backups`,保留 30 天);升级前也可 `bash scripts/backup_data.sh` 手动跑一次。
5. **升级代码后**:启动时会跑 **数据库迁移**(如新列 `order_monitors.monitor_type`);首次启动关注一下日志或无报错页面。
---
## 8. 常见问题(简要)
| 现象 | 可自查 |
|------|--------|
| 关键位永远不触发 | 5m 门控是否全通过(页面门控摘要)、币种日成交量是否在规则内、`KLINE_TIMEFRAME`。 |
| 有信号但不自动开仓 | `LIVE_TRADING_ENABLED``KEY_AUTO_MIN_PLANNED_RR`、计划 RR、是否已有持仓、API/余额报错(微信或日志)。 |
| 加不了箱体/收敛 | 是否已有活跃持仓;先平仓或改用「阻力/支撑位」仅提醒。 |
| 推送收不到 | `WECHAT_WEBHOOK`、企业微信机器人配额与网络。 |
---
## 9. Binance 版(`crypto_monitor_binance`)差异速查
| 项目 | Gate 本仓库 | Binance 版 |
|------|-------------|------------|
| API 变量 | `GATE_API_KEY``GATE_API_SECRET``GATE_*` | `BINANCE_API_KEY``BINANCE_API_SECRET``BINANCE_*` |
| 实盘开关 | `LIVE_TRADING_ENABLED`(通用) | 同上 |
| 止盈止损挂载路径 | `_gate_place_tp_sl_orders``GATE_TPSL_*` | `_binance_place_tp_sl_orders`U 本位条件单) |
| 资金显示舍入 | 以本仓库为准 | 与 **`FUNDS_DECIMALS`** 等一致 |
| 专门文档 | **`关键位自动下单说明.md`**(各仓库有一份,开头标明交易所) | 同左 |
操作流程(登录、关键位四类、手工单、单仓)**两份程序一致**:换目录、换 `.env` 即可对照使用。
@@ -0,0 +1,143 @@
# 关键位监控说明(自动开仓 + 人工盯盘)
**适用:`crypto_monitor_gate`Gate U 本位永续)**
Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_monitor_lib.py`
本文档与 `.env``check_key_monitors``add_key``_key_hard_checks``_process_key_rs_level_alert` 一致。
---
## 一、监控类型总览
| 录入类型 | 录入时选方向 | 自动市价开仓 | 触发与结案 |
|----------|--------------|--------------|------------|
| **箱体突破** | **必选** 多/空 | **是**(门控 + RR) | 条件满足 → 开仓或 `rr_insufficient` / `exchange_failed`**一次性删除** |
| **收敛突破** | **必选** 多/空 | **是**(同上) | 同上 |
| **关键阻力位** | **不选**`direction=watch` | **否** | 5m 收盘突破上/下沿 → 微信 **3 次**`key_level_alert_done` |
| **关键支撑位** | **不选** | **否** | 同上(与阻力位**相同规则**:填上沿+下沿,程序双向监控) |
| 斐波回调 0.618 / 0.786 | 必选 | 限价挂单逻辑 | 见斐波说明(**不在下文展开**) |
**添加时(所有类型):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30**;上沿 **>** 下沿。
---
## 二、关键阻力位 / 关键支撑位(人工盯盘)
### 2.1 录入
- 填写 **上沿 `upper`****下沿 `lower`**(程序同时监控两侧,**无法预先判定**做多还是做空)。
- 页面 **不显示、不要求** 方向;库中 `direction` 初始为 `watch`**首次突破后** 写入 `long`(向上突破上沿)或 `short`(向下突破下沿)。
### 2.2 触发(极简)
- 周期:**`KLINE_TIMEFRAME`(默认 5m)最近一根已闭合 K** 的 **收盘价**(非影线)。
- **向上突破上沿:** `收盘 > upper` → 推断方向 **多 / 向上**,本次监控任务开始按节奏提醒。
- **向下突破下沿:** `收盘 < lower` → 推断方向 **空 / 向下**,本次任务同样开始提醒。
- **任一侧突破即结束本条监控周期**(不会在突破后再等待另一侧;上沿、下沿谁先满足用谁,同根 K 仅可能满足一侧)。
**不参与:** 量能、二确 K、越过幅度下限、日成交排名(运行时)、计划 RR、自动开仓。
### 2.3 微信提醒次数
| 配置 | 默认 | 含义 |
|------|------|------|
| `KEY_ALERT_MAX_TIMES` | `3` | 突破后最多推送 3 次 |
| `KEY_ALERT_INTERVAL_MINUTES` | `5` | 相邻两次推送至少间隔 5 分钟 |
- 第 1 次:首次检测到突破的当次轮询(若已闭合 5m 满足条件)。
- 第 2、3 次:仅按间隔推送(**不要求**价格仍在箱外)。
- 第 3 次推送后:写入 `key_monitor_history``close_reason=**key_level_alert_done**`,从 `key_monitors` **删除**
### 2.4 与箱体/收敛的区别
| 项目 | 阻力/支撑 | 箱体/收敛 |
|------|-----------|-----------|
| 方向 | 程序推断 | 人工选择 |
| K 线根数 | 1 根闭合 5m | 2 根(突破 K + 确认 K |
| 提醒次数 | 3 次后结案 | 自动单:触发后 1 次业务推送并结案 |
---
## 三、箱体突破 / 收敛突破(自动开仓)
### 3.1 K 线结构(默认索引)
| 角色 | 环境变量 | 默认 | 含义 |
|------|----------|------|------|
| 突破 K | `KEY_CONFIRM_BREAKOUT_BAR` | `-2` | 倒数第 2 根闭合 K |
| 确认 K | `KEY_CONFIRM_BAR` | `-1` | 倒数第 1 根闭合 K |
### 3.2 硬门控(须全部通过)
1. **有效突破(收盘越界)**
- 多:`突破 K 收盘 > upper`
- 空:`突破 K 收盘 < lower`
2. **突破越过幅度(仅下限)**
- 多:`(突破 K 收盘 upper) / upper × 100 > KEY_BREAKOUT_AMP_MIN_PCT`(默认 **0.03%**
- 空:`(lower 突破 K 收盘) / lower × 100 >` 同上
- **无上限**;突破过猛由 **计划 RR** 过滤。
- **不再**使用 K 线实体占开盘价比例;`KEY_BREAKOUT_AMP_MAX_PCT` **已不参与门控**
3. **确认 K 不进箱体**
- 多:确认 K 收盘 **`> upper`**(不得在 `[lower, upper]` 内)
- 空:确认 K 收盘 **`< lower`**
4. **量能:** 突破 K 成交量 > 前 `KEY_VOLUME_MA_BARS`(默认 20)根均量 × `KEY_VOLUME_RATIO_MIN`(默认 1.3
5. **日成交量排名:** 运行时仍须前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30
6. **计划 RR(最后经济门控):** 按确认 K 收盘 **E** 计算 SL/TP 后,`RR` **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5)才市价开仓
### 3.3 止损 / 止盈(确认 K 收盘为 E)
箱体高 **H = |upper lower|**。止损锚在 **突破 K 极值** 外侧:
| 方向 | 止损(标准/趋势方案) |
|------|------------------------|
| 多 | 突破 K **最低价** × (1 `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
| 空 | 突破 K **最高价** × (1 + `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
止盈方案见下表(与改版前一致):
| 方案 | `sl_tp_mode` | 多:SL / TP | 空:SL / TP |
|------|--------------|-------------|-------------|
| 标准突破 | `standard` | 突破 K 低外侧% / **E+H** | 突破 K 高外侧% / **EH** |
| 箱体 1R·止盈 1.5H | `box_1p5` | **EH** / **E+1.5×H** | **E+H** / **E1.5×H** |
| 趋势单·自填止盈 | `trend_manual` | 突破 K 低 × (1`KEY_TREND_STOP_OUTSIDE_PCT`%) / **录入止盈** | 突破 K 高外侧% / **录入止盈** |
### 3.4 一次性结案(`close_reason`
| `close_reason` | 含义 |
|----------------|------|
| `box_opposite_break` | 标记价先突破反向边界(多:≤下沿;空:≥上沿) |
| `rr_insufficient` | 门控通过但 RR 不达标或 SL/TP 几何无效 |
| `exchange_failed` | RR 达标但实盘/交易所等原因未开仓 |
| `auto_opened` | RR 达标且市价开仓成功 |
| `key_level_alert_done` | 阻力/支撑 **3 次提醒** 完成 |
---
## 四、环境与参数(`.env` 摘要)
| 变量 | 箱体/收敛 | 阻力/支撑 |
|------|-----------|-----------|
| `KEY_BREAKOUT_AMP_MIN_PCT` | 突破越过下限(默认 0.03) | 不用 |
| `KEY_BREAKOUT_AMP_MAX_PCT` | **已废弃门控** | 不用 |
| `KEY_VOLUME_*` / `KEY_CONFIRM_*` | 用 | 不用 |
| `KEY_AUTO_MIN_PLANNED_RR` | 用 | 不用 |
| `KEY_ALERT_MAX_TIMES` / `KEY_ALERT_INTERVAL_MINUTES` | 不用 | 用(默认 3 次 / 5 分钟) |
| `KEY_DAILY_VOLUME_RANK_MAX` | 添加时 + 运行时 | **仅添加时** |
---
## 五、相关代码
| 说明 | 位置 |
|------|------|
| 共享判定 | `key_monitor_lib.py` |
| 主循环 | `check_key_monitors` |
| 自动门控 | `_key_hard_checks` |
| 阻力/支撑提醒 | `_process_key_rs_level_alert` |
| 录入 | `add_key` |
| 开仓 | `_market_open_for_key_monitor` |
+148
View File
@@ -0,0 +1,148 @@
# 界面与风控更新说明(Gate 实例)
## 顶栏导航(4 项)
| 顺序 | 名称 | 路由 | 说明 |
|------|------|------|------|
| 1 | 关键位监控 | `/key_monitor` | 关键位添加、实时门控、历史 |
| 2 | 实盘下单 | `/trade` | 人工开仓、划转、实时持仓(**默认首页** `/``/trade` |
| 3 | 交易记录与复盘 | `/records` | 交易记录、复盘表单、AI 历史(受顶栏 UTC 时间窗筛选) |
| 4 | 统计分析 | `/stats` | 按北京时间交易日切日 + 分品类统计块 |
## 关键位监控页
- 标题去掉「5m」;规则条从 `.env` 读取(周期、确认K、量能、自动开仓盈亏比、日成交量排名)。
- 左列:活跃关键位,**pos-card** 样式展示现价/距上沿/距下沿/门控。
- 右列:关键位历史(失效/结案),与左列等高滚动;**受顶栏 UTC 列表时间窗筛选**(默认 UTC 当日)。
- 监控类型新增:**斐波回调0.618**、**斐波回调0.786**(与 Binance 主站同一套规则,计算逻辑见仓库根目录 `fib_key_monitor_lib.py`)。
### 斐波关键位监控(方案 A:交易所限价)
| 项 | 说明 |
|----|------|
| 同币互斥 | 每个币种只能有一条斐波监控(0.618 与 0.786 不可并存) |
| 上下沿 | 上沿 **H**、下沿 **L**(须 H > L |
| 挂单价 E | **做多** `E = H ratio × (H L)`(自 H 向下回撤);**做空** `E = L + ratio × (H L)`(自 L 向上反弹) |
| 做多 | 限价 @ E,止损 L,止盈 H |
| 做空 | 限价 @ E,止损 H,止盈 L |
| 添加后 | **立即**在 Gate 挂限价单;卡片显示 **挂E**、限价单 ID |
| 失效 | 以**标记价**判断:做多且标记价 ≥ H、做空且标记价 ≤ L,且限价**未成交** → 撤销该限价单并结案(不写历史开仓) |
| 成交后 | 按仓位挂交易所 TP/SL → 写入 **实盘下单监控**`monitor_type=关键位监控``key_signal_type=斐波回调0.618/0.786`)→ 从关键位列表移除 |
| 撤单 | 仅撤本条斐波的 `fib_limit_order_id`**不会** `cancel_all`,避免误伤其他委托 |
| 盈亏比 | 计划 RR 须 > `KEY_AUTO_MIN_PLANNED_RR`(与箱体/收敛一致);0.618 理论约 1.6:10.786 约 3.7:1 |
| 日成交量 | 与箱体/收敛相同,须在前 `KEY_DAILY_VOLUME_RANK_MAX` 名内方可添加 |
后台轮询:`check_fib_key_monitors()`(标记价失效 / 成交检测);箱体/收敛仍走 `check_key_monitors()`,互不干扰。
手动删除关键位时,若斐波限价尚未成交,会先撤交易所限价再删库记录。
### 箱体 / 收敛自动开仓(来源标注)
- 自动开仓写入 `order_monitors.key_signal_type``箱体突破``收敛突破`
- 持仓卡片、交易记录列表会显示「来源 · 信号类型」。
## 列表时间窗(UTC,全站顶栏)
共用模块:仓库根目录 `history_window_lib.py`Gate / Binance 主站一致)。
| 项 | 说明 |
|----|------|
| 默认 | **UTC 当日**`win_preset=utc_today`,从 UTC 0:00 至当前时刻) |
| 可选 | 近 24 小时、近 7 天、自定义起止(UTC,`datetime-local` |
| 作用范围 | 关键位历史、交易记录列表、复盘记录 API、AI 历史 API、导出「交易记录」「关键位历史」 |
| 与统计的关系 | **仅影响列表/导出****统计分析页仍按北京时间 `TRADING_DAY_RESET_HOUR`(默认 8:00)切交易日** |
| 库内时间 | DB 存北京时间字符串;后端用 `utc_window_to_bj_sql_strings()` 换算后再 SQL 比较 |
| 切换方式 | 顶栏「列表筛选(UTC)」→ 选预设 → **应用**(保留当前路由,如 `/records?win_preset=…` |
查询参数示例:
- `?win_preset=utc_today`
- `?win_preset=utc_last24h` / `utc_last7d`
- `?win_preset=custom&from_utc=2026-05-18 00:00:00&to_utc=2026-05-19 12:00:00`
## 交易记录与复盘
- 平仓记录可同步交易所已实现盈亏(Gate 仓位历史等);列表盈亏列优先显示交易所数据,标注 **所** / **估**
- 记录页提供 **立即同步**`POST /api/sync_exchange_pnl`),用于补全或刷新 `exchange_realized_pnl` 等字段。
- 未做人工复盘时,展示以交易所盈亏为准(有同步数据时)。
- **列表默认只显示当前 UTC 时间窗内**的记录(见上节);导出 CSV 同步该时间窗。
- 表头 **「止损(开仓)」**:展示开仓快照 `initial_stop_loss`(无则回退 `stop_loss`);核对/复盘仍可用有效止损字段。
- 平仓写入 `trade_records` 时:`stop_loss``initial_stop_loss` 均写入**开仓时止损快照**`key_signal_type` 保留箱体/收敛/斐波来源(`fib_key_monitor_lib.key_signal_type_for_trade_record`)。
- **开仓类型**`entry_reason`):机器单平仓入库时,若未手填,按 `key_signal_type` 自动映射(见下表);列表/导出「开仓类型」列 = 复盘核对值优先,否则入库值,否则按信号映射。
| `key_signal_type` | 自动写入的 `entry_reason` |
|-------------------|---------------------------|
| 箱体突破 | 关键位箱体突破 |
| 收敛突破 | 关键位收敛突破 |
| 斐波回调0.618 | 关键位斐波0.618 |
| 斐波回调0.786 | 关键位斐波0.786 |
- 复盘表单 **开仓类型** 下拉新增上述四条固定文案(与趋势/波段类并列)。
- 复盘 **离场触发** 新增 **「止盈」**;从交易记录「填入复盘」时,若结果为「止盈/保本止盈/移动止盈/止损/手动平仓」会自动选中对应触发项,并按 `key_signal_type` 预填开仓类型。
- 勾选「保存时自动生成多周期 K 线图」时:以 **平仓时间** 为锚点,各周期向前约 `ORDER_CHART_LIMIT`(默认 100)根 K 线(`_fetch_ohlcv_ending_at`),不再固定拉「最近 100 根」。
- `/api/journals``/api/reviews` 支持同一时间窗 query,与列表一致。
### 导出(交易记录 v3
- 文件名:`trade_records_v3_YYYYMMDD.csv`
- 相对 v2 增加:`key_signal_type``initial_stop_loss`(及开仓快照列)、`planned_rr``actual_rr``risk_amount`、交易所盈亏与时间字段等;末列「开仓类型」为有效展示文案。
- 「关键位历史」导出同样受 UTC 时间窗限制。
## 实盘下单页
- 左列:实盘下单监控(表单、划转、规则)。
- 右列:实时持仓(独立模块)。
- **人工开仓门控**:计划盈亏比 &lt; `MANUAL_MIN_PLANNED_RR`(默认 **1.4**)时前端弹窗 + 后端拒绝。
- **移动保本**(勾选启用):监控轮询达到触发 RR 后,止损阶梯上移时**同步交易所**——调用与页面「挂止盈止损」相同的 **先撤后挂**`replace_active_monitor_tpsl_on_exchange`:撤该合约全部 TP/SL 条件单 → 按新止损 + 原止盈重挂)。仅交易所成功后才写库;失败发企业微信告警,本地止损不变。未配置实盘 API 时仍只更新本地(与旧行为一致)。
## 统计分析页(`/stats`
| 项 | 说明 |
|----|------|
| 切日 | **北京时间**;交易日边界 = 每日 `TRADING_DAY_RESET_HOUR:00``.env` 默认 **8** |
| 品类下拉 | 页顶 **「统计品类」** 下拉切换(默认「全部交易」):全部交易、下单监控、关键位箱体突破、关键位收敛结构、关键位斐波0.618、关键位斐波0.786;一次只显示所选品类的日/周/月 |
| URL | 切换后写入 `stats_segment=`(如 `all``manual``key_box``key_conv``key_fib618``key_fib786`),刷新 `/stats` 可保持选项 |
| 每块指标 | 日 / 周 / 月:开单次数、平仓笔数、胜率、净盈亏、回撤、连续亏损等(与原口径一致) |
| 开单次数 | 人工块:`monitor_type=下单监控` 且无 `key_signal_type`;关键位块:按 `order_monitors.key_signal_type` 计数 |
| 不受 UTC 窗影响 | 统计始终基于库内全部已平仓记录,按北京交易日归类,**不**随顶栏 UTC 列表窗切换 |
## 持仓与计仓
- `MAX_ACTIVE_POSITIONS` 默认 **1**(可在 `.env` 调大)。
- 关键位自动开仓:在已有持仓时,若 `KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT=true`,按**首笔开仓前**交易账户资金快照计仓(`trading_sessions.key_sizing_capital_snapshot`)。
## 配置
详见 `.env.example` 中「关键位门控」「交易执行 / 人工风控」注释段。Gate 专用项(`GATE_*`、止盈止损触发等)保持原有段落不变。
## 自动备份(服务器)
- 脚本:`scripts/backup_data.sh``crypto.db` + `static/images`
- 定时:`scripts/install_backup_cron.sh` → 每天 **北京时间 0:00**,目录 **`/root/backups/<实例名>/YYYY-MM-DD/`**,保留 **30**
- 详见 `部署文档.md` 第 5.4 节(自动备份)
## 数据库(启动时自动迁移)
`key_monitors` 新增斐波字段(示例):`fib_limit_order_id``fib_entry_price``fib_stop_loss``fib_take_profit``fib_order_amount``fib_margin_capital``fib_leverage`
`trade_records` / `order_monitors` 新增或沿用:`key_signal_type``exchange_realized_pnl``exchange_opened_at``exchange_closed_at``exchange_sync_key``entry_reason``reviewed_entry_reason``initial_stop_loss`
**历史数据**:本次**不做**旧记录的批量回填(`entry_reason` / `initial_stop_loss` / `key_signal_type` 等);仅**新产生**的平仓与复盘按新逻辑写入。旧行展示可回退已有字段。
## 涉及文件(便于排查)
| 路径 | 说明 |
|------|------|
| `history_window_lib.py` | UTC 时间窗解析与转北京时间 SQL 字符串 |
| `fib_key_monitor_lib.py` | 斐波计算、`KEY_ENTRY_REASON_BY_SIGNAL``entry_reason_from_key_signal` |
| `crypto_monitor_gate/app.py` | 列表筛选、统计分块、导出 v3、复盘 K 线锚点、入库逻辑 |
| `crypto_monitor_gate/templates/index.html` | 顶栏时间窗、统计分块 UI、止损(开仓)列、复盘预填 |
## 升级步骤
1. `git pull` 后对比 `.env.example`,把新增变量合并进本地 `.env`
2. 在 VPS 上为 Binance / Gate / Gate Bot **各执行一次** `bash scripts/install_backup_cron.sh`(若尚未安装)。
3. 重启 Gate 实例服务(如 `pm2 restart crypto_gate`);首次启动会自动 `ALTER TABLE` 缺列(斐波、交易所盈亏、`entry_reason` 等)。
4. 浏览器强刷(Ctrl+F5)避免旧版 `index.html` 缓存。
5. 打开任意页确认顶栏出现 **「列表筛选(UTC)」**`/stats` 可见分品类统计与「北京 8:00 切日」说明。
6. 建议在测试币上先添加一条斐波监控,确认:限价已挂出、标记价失效会撤单、成交后出现持仓监控且 TP/SL 已挂上;平仓后交易记录止损(开仓)与开仓类型是否正确。
@@ -4,6 +4,8 @@
**四所主站**Binance / Gate / OKX / 本目录 `crypto_monitor_gate_bot`)均在顶栏 **策略交易 → `/strategy`** 左栏提供同一套逻辑(共用 `strategy_trend_register.py`);本目录侧重 **Gate 子账户 / 机器人** 实例,可与主 Gate 账户隔离部署。
**检阅备忘**(中控平仓、交易记录、补仓展示、漏记补录):[docs/trend-hub-close-and-trade-records.md](../docs/trend-hub-close-and-trade-records.md)
---
## 1. 适用场景
+5 -3
View File
@@ -1,10 +1,12 @@
# `crypto_monitor_gate` 部署指南:SSH SOCKS + Gate.io + PM2Ubuntu
# `crypto_monitor_gate_bot` 部署指南:SSH SOCKS + Gate.io + PM2Ubuntu
Ubuntu 环境总览见 **[docs/ubuntu-server.md](../docs/ubuntu-server.md)**。
本文面向:**在本机运行本项目**,但 **直连 Gate.io API 不稳定或被重置** 的场景。思路是:
- 本机用 `ssh -D` 做动态转发,把 **SOCKS5 出口**放到能正常访问 Gate 的机器(常见为一台境外 VPS)
- 项目在 `.env` 中设置 **`GATE_SOCKS_PROXY=socks5h://127.0.0.1:1080`**(或你实际端口),`ccxt` 经 SOCKS 访问交易所
- **SSH 隧道**:用 `ssh -D` 在本机常驻即可(screen / tmux / systemd 等),**不必交给 PM2**
- **SSH 隧道**:用 `ssh -D` 在本机常驻(可用 **tmux****autossh** 保持连接),**不要** 把 `ssh` 交给 PM2
- 使用 **PM2** 仅托管 **Flask 应用**;仓库根目录 **`ecosystem.config.cjs`** 只定义 `crypto-monitor-gate`
> 安全提醒:不要把 `.env`、私钥 `.pem`、Gate API Key 提交到 Git;下文只用占位符。
@@ -272,7 +274,7 @@ pm2 startup
### 9.1 SSH SOCKS(自行后台常驻,不推荐用 PM2)
示例(前台;实际可用 `screen`/`tmux`/`-f` 后台化或 systemd):
示例(前台调试;生产请用 **PM2**,见本文与 [docs/ubuntu-server.md](../docs/ubuntu-server.md)):
```bash
ssh -N -D 127.0.0.1:1080 gate-vps \
+18 -4
View File
@@ -21,9 +21,9 @@ APP_PORT=5004
APP_DEBUG=false
# 登录账号
APP_USERNAME=dekun
APP_USERNAME=admin
# 登录密码(请改成你自己的强密码)
APP_PASSWORD=ChangeMe123!
APP_PASSWORD=admin123
# 是否关闭登录校验(局域网可设 true;公网务必 false)
APP_AUTH_DISABLED=true
# --- 多账户交易中控 manual_trading_hub ---
@@ -101,7 +101,7 @@ FULL_MARGIN_BUFFER_RATIO=0.98
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
# =============================================================================
AUTO_TRANSFER_ENABLED=false
# 交易账户(AUTO_TRANSFER_TO)补足到的 USDT 总额,非每日开仓基数
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
AUTO_TRANSFER_AMOUNT=30
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
@@ -140,7 +140,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=5
# 详见 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
@@ -162,6 +162,20 @@ 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
+5 -5
View File
@@ -28,15 +28,15 @@
完整模板见 **`.env.example`**。
## 本地运行
## 运行
```powershell
cd crypto_monitor_okx
$env:PYTHONPATH=".."
```bash
cd /opt/crypto_monitor/crypto_monitor_okx
source .venv/bin/activate
python app.py
```
默认端口 **`APP_PORT`**常为 `5004`,与中控登记一致)
生产使用 **PM2**;见 [docs/ubuntu-server.md](../docs/ubuntu-server.md)。默认 **`APP_PORT`** 常为 `5004`
## 部署
+1665 -475
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* PM2 进程定义Ubuntu / Linux
*
* 仅托管 Flask 应用**SSH SOCKS 隧道请在本机用 screen/tmux/systemd 等方式单独常驻**
* 仅托管 Flask 应用**SSH SOCKS 隧道** `ssh -D` 常驻可用 tmux / autossh勿交给 PM2
* `.env` `OKX_SOCKS_PROXY` 端口一致即可不必交给 PM2
*
* 使用前项目根目录存在 `.venv`且已安装依赖 SOCKS 时需 PySocks
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#22d3ee"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="108" fill="#0c1019"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="#141b2d"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="none" stroke="url(#g)" stroke-width="12"/>
<path d="M120 320 L200 248 L280 272 L392 168" fill="none" stroke="url(#g)" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="392" cy="168" r="18" fill="#34d399"/>
<rect x="168" y="268" width="28" height="64" rx="6" fill="#f87171"/>
<line x1="182" y1="248" x2="182" y2="340" stroke="#f87171" stroke-width="10" stroke-linecap="round"/>
<rect x="268" y="220" width="28" height="96" rx="6" fill="#34d399"/>
<line x1="282" y1="200" x2="282" y2="340" stroke="#34d399" stroke-width="10" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,23 @@
{
"name": "交易监控复盘",
"short_name": "监控",
"description": "加密货币永续交易监控与复盘",
"start_url": "/",
"display": "standalone",
"background_color": "#0b0d14",
"theme_color": "#0b0d14",
"icons": [
{
"src": "/static/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
File diff suppressed because it is too large Load Diff
+125 -107
View File
@@ -1,107 +1,125 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>系统登录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a10;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
}
.login-box {
background: #12121a;
padding: 2.5rem;
border-radius: 16px;
width: 100%;
max-width: 400px;
border: 1px solid #242435;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.login-box h2 {
margin-bottom: 2rem;
text-align: center;
font-size: 1.5rem;
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #a9a9ff;
}
.form-group input {
width: 100%;
padding: 0.85rem 1rem;
border-radius: 10px;
border: 1px solid #2e2e45;
background: #1a1a29;
color: #fff;
font-size: 0.95rem;
outline: none;
}
.form-group input:focus {
border-color: #4cc2ff;
}
button {
width: 100%;
padding: 0.9rem;
border-radius: 10px;
border: none;
background: linear-gradient(90deg, #4285f4, #7b42ff);
color: #fff;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
}
.flash {
padding: 0.8rem;
margin-bottom: 1rem;
background: #331e24;
color: #ff6666;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
</style>
</head>
<body>
<div class="login-box">
<h2>交易监控系统登录</h2>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="POST">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" required placeholder="请输入账号">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required placeholder="请输入密码">
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8">
<script src="/static/instance_theme.js?v=4"></script>
<title>系统登录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a10;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
}
.login-box {
background: #12121a;
padding: 2.5rem;
border-radius: 16px;
width: 100%;
max-width: 400px;
border: 1px solid #242435;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.login-box h2 {
margin-bottom: 2rem;
text-align: center;
font-size: 1.5rem;
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #a9a9ff;
}
.form-group input {
width: 100%;
padding: 0.85rem 1rem;
border-radius: 10px;
border: 1px solid #2e2e45;
background: #1a1a29;
color: #fff;
font-size: 0.95rem;
outline: none;
}
.form-group input:focus {
border-color: #4cc2ff;
}
button {
width: 100%;
padding: 0.9rem;
border-radius: 10px;
border: none;
background: linear-gradient(90deg, #4285f4, #7b42ff);
color: #fff;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
button:hover {
opacity: 0.9;
}
.flash {
padding: 0.8rem;
margin-bottom: 1rem;
background: #331e24;
color: #ff6666;
border-radius: 8px;
text-align: center;
font-size: 0.85rem;
}
</style>
<link rel="stylesheet" href="/static/instance_theme.css?v=4">
</head>
<div class="login-theme-bar">
<div class="theme-toggle instance-theme-toggle" role="group" aria-label="界面主题">
<button type="button" class="theme-toggle-btn is-active" data-theme-value="dark" aria-pressed="true" title="暗色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M12.1 3a9 9 0 1 0 8.9 11 6.5 6.5 0 1 1-8.9-11z"/>
</svg>
</button>
<button type="button" class="theme-toggle-btn" data-theme-value="light" aria-pressed="false" title="亮色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
</svg>
</button>
</div>
</div>
<body>
<div class="login-box">
<h2>交易监控系统登录</h2>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="POST" autocomplete="off">
<div class="form-group">
<label>账号</label>
<input type="text" name="username" required placeholder="请输入账号" autocomplete="off" autocapitalize="off" spellcheck="false">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required placeholder="请输入密码" autocomplete="new-password">
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
@@ -1,211 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>实盘下单放大 | 100根K线</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px}
.container{width:min(98vw,1900px);margin:0 auto}
.card{background:#121726;border-radius:10px;padding:12px;border:1px solid #2a3150;margin-bottom:12px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.btn{padding:7px 10px;border-radius:8px;text-decoration:none;border:1px solid #304164;background:#151a2a;color:#8fc8ff;cursor:pointer}
.btn:hover{background:#1f2740}
select,button{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff}
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:10px}
.meta-item{background:#141b2f;border:1px solid #27324e;border-radius:8px;padding:8px}
.meta-item .k{font-size:.76rem;color:#9fb0d8}
.meta-item .v{font-size:1rem;margin-top:4px;word-break:break-all}
.status{font-size:.84rem;color:#95a2c2}
.status.err{color:#ff8080}
#chart-wrap{height:560px;background:#0f1320;border:1px solid #2a3150;border-radius:10px;padding:8px}
#chart{width:100%;height:100%}
.empty{padding:18px;color:#95a2c2}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="row" style="justify-content:space-between">
<div class="row">
<a class="btn" href="/">返回首页</a>
<strong style="color:#dbe4ff">实盘下单放大(100根K线)</strong>
</div>
<div class="status">最近刷新:<span id="updated-at">--</span></div>
</div>
{% if orders %}
<div class="row" style="margin-top:10px">
<label>订单</label>
<select id="order-id">
{% for o in orders %}
<option value="{{ o.id }}" {% if selected_order and o.id == selected_order.id %}selected{% endif %}>
#{{ o.id }} {{ o.symbol }} {{ '做多' if o.direction == 'long' else '做空' }}
</option>
{% endfor %}
</select>
<label>周期</label>
<select id="timeframe">
{% for tf in ['1m','3m','5m','15m','30m','1h','4h','1d'] %}
<option value="{{ tf }}" {% if tf == default_timeframe %}selected{% endif %}>{{ tf }}</option>
{% endfor %}
</select>
<button id="manual-refresh" type="button">刷新</button>
<span id="load-status" class="status"></span>
</div>
{% else %}
<div class="empty">当前没有激活订单,无法展示放大K线。</div>
{% endif %}
</div>
{% if orders %}
<div class="card">
<div class="meta">
<div class="meta-item"><div class="k">交易对</div><div class="v" id="m-symbol">-</div></div>
<div class="meta-item"><div class="k">方向</div><div class="v" id="m-direction">-</div></div>
<div class="meta-item"><div class="k">成交价</div><div class="v" id="m-entry">-</div></div>
<div class="meta-item"><div class="k">止损</div><div class="v" id="m-sl">-</div></div>
<div class="meta-item"><div class="k">止盈</div><div class="v" id="m-tp">-</div></div>
<div class="meta-item"><div class="k">盈亏比</div><div class="v" id="m-rr">-</div></div>
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
<div class="meta-item"><div class="k">浮盈亏</div><div class="v" id="m-pnl">-</div></div>
</div>
</div>
<div class="card">
<div id="chart-wrap"><div id="chart"></div></div>
</div>
{% endif %}
</div>
{% if orders %}
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
<script>
const refreshMs = Math.max({{ price_refresh_seconds * 1000 }}, 5000);
const orderSelect = document.getElementById("order-id");
const tfSelect = document.getElementById("timeframe");
const statusEl = document.getElementById("load-status");
const updatedAtEl = document.getElementById("updated-at");
const chartHost = document.getElementById("chart");
const fmt = (v, d=6) => (v === null || typeof v === "undefined" || Number.isNaN(Number(v))) ? "-" : Number(v).toFixed(d);
let chart = null;
let candleSeries = null;
let priceLines = [];
function ensureChart(){
if(chart){ return true; }
if(!window.LightweightCharts){
statusEl.className = "status err";
statusEl.innerText = "图表库加载失败";
return false;
}
chart = LightweightCharts.createChart(chartHost, {
layout: { background: { color: "#0f1320" }, textColor: "#d6deff" },
grid: { vertLines: { color: "#1e263d" }, horzLines: { color: "#1e263d" } },
rightPriceScale: { borderColor: "#2a3150" },
timeScale: { borderColor: "#2a3150", timeVisible: true, secondsVisible: false },
crosshair: { mode: 0 }
});
candleSeries = chart.addCandlestickSeries({
upColor: "#4cd97f",
downColor: "#ff6666",
borderVisible: false,
wickUpColor: "#4cd97f",
wickDownColor: "#ff6666"
});
window.addEventListener("resize", () => {
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
});
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
return true;
}
function resetPriceLines(){
if(!candleSeries){ return; }
priceLines.forEach(line => {
try { candleSeries.removePriceLine(line); } catch (_) {}
});
priceLines = [];
}
function addLine(price, title, color){
if(!candleSeries || typeof price === "undefined" || price === null){ return; }
const p = Number(price);
if(Number.isNaN(p) || p <= 0){ return; }
priceLines.push(candleSeries.createPriceLine({
price: p, color, lineWidth: 1, lineStyle: 0, axisLabelVisible: true, title
}));
}
function paintOrder(order){
document.getElementById("m-symbol").innerText = order.symbol || "-";
document.getElementById("m-direction").innerText = (order.direction === "short") ? "做空" : "做多";
document.getElementById("m-entry").innerText = fmt(order.trigger_price, 8);
document.getElementById("m-sl").innerText = fmt(order.stop_loss, 8);
document.getElementById("m-tp").innerText = fmt(order.take_profit, 8);
const rr = order.rr_ratio;
document.getElementById("m-rr").innerText = (rr === null || typeof rr === "undefined") ? "-:1" : `${Number(rr).toFixed(2)}:1`;
document.getElementById("m-price").innerText = fmt(order.current_price, 8);
const pnlEl = document.getElementById("m-pnl");
pnlEl.innerText = `${fmt(order.float_pnl, 4)}U (${fmt(order.float_pct, 2)}%)`;
pnlEl.style.color = Number(order.float_pnl || 0) > 0 ? "#4cd97f" : (Number(order.float_pnl || 0) < 0 ? "#ff6666" : "#d6deff");
}
async function loadOrderKline(){
if(!ensureChart()){ return; }
const orderId = orderSelect.value;
const timeframe = tfSelect.value;
if(!orderId){ return; }
statusEl.className = "status";
statusEl.innerText = "加载中...";
try{
const resp = await fetch(`/api/order_kline?order_id=${encodeURIComponent(orderId)}&timeframe=${encodeURIComponent(timeframe)}`);
const data = await resp.json();
if(!resp.ok || !data.ok){ throw new Error(data.msg || "请求失败"); }
const candles = Array.isArray(data.candles) ? data.candles : [];
if(!candles.length){
statusEl.className = "status err";
statusEl.innerText = "暂无K线数据";
return;
}
candleSeries.setData(candles);
resetPriceLines();
addLine(data.order.trigger_price, "成交价", "#42a5f5");
addLine(data.order.stop_loss, "止损", "#ff6666");
addLine(data.order.take_profit, "止盈", "#4cd97f");
chart.timeScale().fitContent();
paintOrder(data.order || {});
updatedAtEl.innerText = data.updated_at || "--";
statusEl.className = "status";
statusEl.innerText = `已加载 ${candles.length} 根K线`;
}catch(err){
statusEl.className = "status err";
statusEl.innerText = err && err.message ? err.message : "加载失败";
}
}
document.getElementById("manual-refresh").addEventListener("click", loadOrderKline);
orderSelect.addEventListener("change", loadOrderKline);
tfSelect.addEventListener("change", loadOrderKline);
loadOrderKline();
setInterval(loadOrderKline, refreshMs);
</script>
{% endif %}
<script>
(function(){
if (typeof ensureChart !== 'function') return;
const oldEnsureChart = ensureChart;
ensureChart = function(){
if (chart && candleSeries) return true;
try { const ok = oldEnsureChart(); if (ok && candleSeries) return true; } catch (_) {}
if (chart && !candleSeries && typeof chart.addSeries === 'function' && window.LightweightCharts && window.LightweightCharts.CandlestickSeries) {
const opts = { upColor:'#4cd97f', downColor:'#ff6666', borderVisible:false, wickUpColor:'#4cd97f', wickDownColor:'#ff6666' };
candleSeries = chart.addSeries(window.LightweightCharts.CandlestickSeries, opts);
return !!candleSeries;
}
return !!candleSeries;
};
})();
</script>
</body>
</html>
+5 -3
View File
@@ -48,7 +48,7 @@
2. 启动 Flask 应用(可用 **`ecosystem.config.cjs`** 交给 PM2,或本地 `python app.py` / `flask run`,以你当前脚本为准)。
3. 浏览器访问站点,打开 **`/login`**,使用 **`.env` 里的 `APP_PASSWORD`** 登录。
登录后顶栏:**关键位监控** | **实盘下单**(默认首页)| **策略交易**`/strategy`,趋势回调 + 顺势加仓双栏)| **交易记录与复盘** | **统计分析**
登录后顶栏:**关键位监控** | **实盘下单**(默认首页)| **策略交易**`/strategy`,趋势回调 + 顺势加仓双栏)| **策略交易记录**`/strategy/records`| **交易记录与复盘** | **统计分析**
---
@@ -65,9 +65,11 @@
| **收敛突破** | 同上(自动开仓类)。 |
| **关键阻力位** | **不自动开仓**;触发后 **发 1 次微信**,然后本条 **结案进历史**。 |
| **关键支撑位** | 同上(仅提醒)。 |
| **回调触价开仓** | **不挂交易所限价**;标记价回调触达 E 后 **下一轮询市价开仓**RR 门槛同 `KEY_AUTO_MIN_PLANNED_RR`);有效期 **24h** |
| **突破触价开仓** | **不挂交易所限价**;标记价 **穿越 E 立即市价开仓**;先触 SL/TP 侧失效;有效期 **24h** |
3. **方向**:做多 / 做空(选)。
4. **上沿 / 下沿**:必填;保存时会按交易所 **价格精度** 取整
3. **方向**:做多 / 做空(触价开仓 / 箱体 / 收敛 / 斐波必选;阻力/支撑不选)。
4. **价位**:箱体/收敛/阻力/支撑填 **上沿 / 下沿**;触价开仓填 **入场 E / 止损 SL / 止盈 TP**
**限制:**
活跃持仓数达到 **`MAX_ACTIVE_POSITIONS`**(默认 1)时,**不允许**再添加「**箱体突破** / **收敛突破**」;仍可添加「**关键阻力位 / 支撑位**」。
@@ -1,7 +1,7 @@
# 关键位监控说明(自动开仓 + 人工盯盘)
**适用:`crypto_monitor_okx`OKX 永续)**
箱体/收敛与 Binance、Gate 相同:**门控通过后自动市价开仓**(须 `LIVE_TRADING_ENABLED=true`)。阻力/支撑仍为微信提醒。共享逻辑见 `key_monitor_lib.py`
**适用:`crypto_monitor_gate`Gate U 本位永续)**
Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_monitor_lib.py`
本文档与 `.env``check_key_monitors``add_key``_key_hard_checks``_process_key_rs_level_alert` 一致。
@@ -16,8 +16,10 @@
| **关键阻力位** | **不选**`direction=watch` | **否** | 5m 收盘突破上/下沿 → 微信 **3 次**`key_level_alert_done` |
| **关键支撑位** | **不选** | **否** | 同上(与阻力位**相同规则**:填上沿+下沿,程序双向监控) |
| 斐波回调 0.618 / 0.786 | 必选 | 限价挂单逻辑 | 见斐波说明(**不在下文展开**) |
| **回调触价开仓** | **必选** 多/空 | **程序盯价 → 回调触 E 后市价** | 见下文 **§四** |
| **突破触价开仓** | **必选** 多/空 | **程序盯价 → 穿越 E 立即市价** | 见下文 **§四** |
**添加时(所有类型):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30**;上沿 **>** 下沿。
**添加时(箱体/收敛/斐波/触价):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30**;上沿 **>** 下沿(触价开仓填 E/SL/TP,上下沿仅作展示占位)
---
@@ -110,6 +112,7 @@
| `close_reason` | 含义 |
|----------------|------|
| `box_opposite_break` | 标记价先突破反向边界(多:≤下沿;空:≥上沿) |
| `rr_insufficient` | 门控通过但 RR 不达标或 SL/TP 几何无效 |
| `exchange_failed` | RR 达标但实盘/交易所等原因未开仓 |
| `auto_opened` | RR 达标且市价开仓成功 |
@@ -117,7 +120,37 @@
---
## 四、环境与参数(`.env` 摘要
## 四、回调 / 突破触价开仓(程序触价,无交易所挂单
### 4.1 录入
- **回调触价开仓**:方向必选多/空;填写 **计划入场价 E**、**止损 SL**、**止盈 TP**(做多须 `SL < E < TP`)。
- **突破触价开仓**:同上;添加时当前价须在突破方向一侧(做多:价低于 E;做空:价高于 E)。
- 计划 RR 以 **E** 为基准,须 **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5)。
- 可选移动保本、时间平仓;**全仓杠杆模式**下可用。
### 4.2 触发与结案
| 类型 | 触发条件(标记价) |
|------|-------------------|
| **回调触价** | 做多 `≤ E`;做空 `≥ E` → 下一轮询市价开仓 |
| **突破触价** | 做多**向上穿越** E;做空**向下穿越** E → **立即**市价开仓 |
- 未成交前标记价先触 **TP 侧**`trigger_tp_invalidate`
- **突破触价**另:未穿越 E 先触 **SL 侧**`trigger_sl_invalidate`
- **24h** 未触发 → `trigger_entry_expired`
- 成功 → `trigger_entry_filled`;触发后开仓失败 → `trigger_exchange_failed`
### 4.3 计仓与占位
- **以损定仓**:按 E、SL 反推保证金,触发时重算;**全仓杠杆**:可用×缓冲比例,BTC/ETH 10x、其它 5x。
- **占当日开仓意图**(已开 + 待触发),未成交不占持仓;同币仅 1 条触价监控(含回调/突破)。
共享逻辑:`trigger_entry_key_monitor_lib.py`;轮询:`check_trigger_entry_key_monitors`
---
## 五、环境与参数(`.env` 摘要)
| 变量 | 箱体/收敛 | 阻力/支撑 |
|------|-----------|-----------|
@@ -130,7 +163,7 @@
---
## 、相关代码
## 、相关代码
| 说明 | 位置 |
|------|------|
+4 -2
View File
@@ -1,6 +1,6 @@
# `crypto_monitor_okx` 部署文档(Ubuntu
**功能与页面操作** 见同目录 **[使用说明.md](./使用说明.md)**。策略与 AI 见仓库根 **[策略交易说明.md](../策略交易说明.md)**、**[AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)**。
**功能与页面操作** 见同目录 **[使用说明.md](./使用说明.md)**。Ubuntu 环境(Python / Node / PM2)见 **[docs/ubuntu-server.md](../docs/ubuntu-server.md)**。策略与 AI 见 **[策略交易说明.md](../策略交易说明.md)**、**[AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)**。
---
@@ -10,7 +10,7 @@
- 本机启动 `ssh -D` 动态转发,把 **SOCKS5 出口**放到你可正常访问 OKX 的 VPS 上
- 项目通过环境变量 `OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080``ccxt` 走 SOCKS
- `pm2` 托管 **SSH 隧道****Flask 应用**(你也可以只用 `screen`,但本文按你要求用 PM2
- **SSH 隧道**用 `ssh -D` 常驻(可用 tmux / autossh);**Flask 应用** 仅用 **PM2** 托管(见 [docs/ubuntu-server.md](../docs/ubuntu-server.md)
> 安全提醒:不要把 `.env`、私钥 `.pem`、OKX API Key 提交到 Git;文档里只用占位符。
@@ -182,6 +182,8 @@ OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080
# 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
+39 -74
View File
@@ -1,65 +1,48 @@
# 环境一键部署
# 环境一键部署Ubuntu / root /opt
为仓库内各子项目创建 Python 虚拟环境、安装依赖、初始化 `.env` 与静态目录
**`/opt/crypto_monitor`** 下以 **root** 为各子项目创建 Python **`.venv`**、安装依赖、 `.env.example` 生成 `.env`(不覆盖已有),并可选安装 **PM2**
## Windows(推荐)
完整系统要求(Python / Node / PM2 版本、启动顺序)见 **[docs/ubuntu-server.md](../docs/ubuntu-server.md)**。
双击仓库根目录 **`一键部署.bat`**,或在 PowerShell 中:
---
```powershell
cd C:\path\to\crypto_monitor
.\deploy\setup_env.ps1
```
## 前置条件
仅部署部分项目:
```powershell
.\deploy\setup_env.ps1 -Only binance,gate_bot
```
重建虚拟环境:
```powershell
.\deploy\setup_env.ps1 -RecreateVenv
```
跳过 PM2、跳过复制 `.env`
```powershell
.\deploy\setup_env.ps1 -SkipPm2 -SkipEnvCopy
```
## Linux / macOS
**Ubuntu / Debian 首次部署**(若 `python -m venv``ensurepip is not available`):
- **Ubuntu 22.04 / 24.04**,用户 **root**
- 已安装 **git**,仓库位于 **`/opt/crypto_monitor`**
```bash
apt update
apt install -y python3.10-venv python3-pip curl # 版本号与 python3 --version 一致
bash deploy/setup_env.sh
apt install -y python3 python3-pip python3-venv curl git ca-certificates
# Node 20 + PM2 见 docs/ubuntu-server.md §3
```
脚本在 **root** 下会自动尝试 `apt install python*-venv`;非 root 请先装系统包或使用:
---
```bash
sudo bash deploy/setup_env.sh --install-system-deps
```
## 一键执行
```bash
cd /opt/crypto_monitor
bash deploy/setup_env.sh
bash deploy/setup_env.sh --only binance,gate
bash deploy/setup_env.sh --recreate-venv
bash deploy/setup_env.sh --install-system-deps
```
若在 Windows 编辑过脚本后在 Linux 报错 `set: pipefail: invalid option name`,先去掉 CRLF 再执行
常用参数
```bash
bash deploy/setup_env.sh --only binance,gate_bot # 仅部分子项目
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
```
若在其它环境编辑过脚本后报 `pipefail` 错误,先转 LF
```bash
sed -i 's/\r$//' deploy/setup_env.sh
# 或: apt install -y dos2unix && dos2unix deploy/setup_env.sh
bash deploy/setup_env.sh
```
---
## 脚本会做什么
| 步骤 | 说明 |
@@ -67,47 +50,29 @@ bash deploy/setup_env.sh
| 检查 Python | 需要 **3.10+** |
| `crypto_monitor_*` | 各目录 `.venv` + `pip install -r ../requirements.txt` |
| `manual_trading_hub` | 独立 `requirements.txt` |
| `.env` | 不存在则从 `.env.example` 复制**不覆盖**已有) |
| 目录 | 创建 `static/images``static/images/order_charts` |
| PM2 | 已装 Node.js 且未 `-SkipPm2`,尝试 `npm install -g pm2` |
| `.env` | 不存在则从 `.env.example` 复制 |
| 目录 | `static/images``static/images/order_charts` |
| PM2 | 已装 Node `npm install -g pm2` |
---
## 部署之后
1. 编辑各子目录 **`.env`**API、登录密码、SOCKS 代理、**AI 复盘** 等)。AI 默认走 OpenAI 兼容网关 `https://op.bz121.com/v1``AI_PROVIDER=openai``OPENAI_API_KEY` 等),详见根目录 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)
2. 本地试运行(以 Binance 为例):
1. 编辑各子目录 **`.env`**API、登录密码、SOCKS、AI 复盘等)
2. **仅用 PM2 常驻**(见 [docs/ubuntu-server.md](../docs/ubuntu-server.md) §3):
```bash
cd crypto_monitor_binance
source .venv/bin/activate # Windows: .\.venv\Scripts\activate
python app.py
cd /opt/crypto_monitor/crypto_monitor_binance && pm2 start ecosystem.config.cjs
# … 其余三所 …
cd /opt/crypto_monitor/manual_trading_hub && pm2 start ecosystem.config.cjs
pm2 save
```
3. 服务器长期运行见各目录 **《部署文档.md》**SSH SOCKS、PM2
4. **多账户中控**`manual_trading_hub`):编辑 `manual_trading_hub/.env``HUB_PASSWORD``HUB_BRIDGE_TOKEN` 等与四实例一致),再 `pm2 start ecosystem.config.cjs`;验收 `bash manual_trading_hub/scripts/verify_hub_deploy.sh`。详见 [manual_trading_hub/部署文档.md](../manual_trading_hub/部署文档.md)、[常见问题.md](../manual_trading_hub/常见问题.md)。
3. 四所 `.env` 同步脚本见 **[docs/env-sync-scripts.md](../docs/env-sync-scripts.md)**
## 四所 `.env` 自动划转项(已有 .env 时)
`AUTO_TRANSFER_AMOUNT` 等与 `DAILY_START_CAPITAL` **独立**;四所 `.env.example` 已统一注释。若服务器上已有 `.env`,可合并写入(不覆盖 API 密钥):
```bash
python scripts/sync_four_exchange_transfer_env.py
# 缺 AUTO_TRANSFER_AMOUNT 时会沿用该文件中的 DAILY_START_CAPITAL
pm2 restart crypto-monitor-binance crypto-monitor-okx crypto-monitor-gate crypto-monitor-gate-bot
```
## 四所 `.env` 计仓模式项(已有 .env 时)
`POSITION_SIZING_MODE` / `FULL_MARGIN_BUFFER_RATIO` 仅能通过 env 切换;切换模式前须**无持仓**:
```bash
python scripts/sync_four_exchange_position_sizing_env.py
# 无仓后切全仓:python scripts/sync_four_exchange_position_sizing_env.py --set-mode full_margin
pm2 restart crypto-monitor-binance crypto-monitor-okx crypto-monitor-gate crypto-monitor-gate-bot
```
详见 [docs/position-sizing-mode.md](../docs/position-sizing-mode.md)。
---
## 依赖说明
- 四个监控子项目共用仓库根目录 **[requirements.txt](../requirements.txt)**。
- 走 SOCKS 代理时必须安装 **PySocks**(已包含在 requirements 中)。
- 四个监控子项目共用根目录 **[requirements.txt](../requirements.txt)**。
- 走 SOCKS **PySocks**(已包含在 requirements 中)。
-218
View File
@@ -1,218 +0,0 @@
#Requires -Version 5.1
<#
.SYNOPSIS
crypto_monitor 一键环境部署Windows PowerShell
.DESCRIPTION
- 为各子项目创建 Python venv 并安装依赖
- .env.example 复制 .env不覆盖已有
- 创建 static/images 等运行时目录
- 可选安装 PM2需已安装 Node.js
.EXAMPLE
.\deploy\setup_env.ps1
.\deploy\setup_env.ps1 -Only binance,gate_bot
.\deploy\setup_env.ps1 -SkipPm2
#>
param(
[string]$Only = "all",
[switch]$SkipPm2,
[switch]$SkipEnvCopy,
[switch]$RecreateVenv
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$DeployDir = $PSScriptRoot
$RepoRoot = (Resolve-Path (Join-Path $DeployDir "..")).Path
$ReqFile = Join-Path $RepoRoot "requirements.txt"
$HubReqFile = Join-Path $RepoRoot "manual_trading_hub\requirements.txt"
$MonitorProjects = @(
@{ Key = "binance"; Dir = "crypto_monitor_binance" },
@{ Key = "gate"; Dir = "crypto_monitor_gate" },
@{ Key = "gate_bot"; Dir = "crypto_monitor_gate_bot" },
@{ Key = "okx"; Dir = "crypto_monitor_okx" }
)
$HubProject = @{ Key = "hub"; Dir = "manual_trading_hub" }
function Write-Step([string]$Msg) {
Write-Host ""
Write-Host "==> $Msg" -ForegroundColor Cyan
}
function Test-Python310 {
$py = Get-Command python -ErrorAction SilentlyContinue
if (-not $py) {
throw "未找到 python。请安装 Python 3.10+ 并加入 PATHhttps://www.python.org/downloads/"
}
$verText = & python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
$parts = $verText.Trim() -split "\."
$major = [int]$parts[0]
$minor = [int]$parts[1]
if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 10)) {
throw "需要 Python 3.10+,当前: $verText"
}
Write-Host "Python: $(python --version 2>&1)" -ForegroundColor DarkGray
}
function Should-Include([string]$Key, [string[]]$Selected) {
if ($Selected -contains "all") { return $true }
return $Selected -contains $Key
}
# 复制 .env 时统一为 LF,避免上传到 Linux 后 PM2 source 报 $'\r': command not found
function Copy-EnvFileLf([string]$Src, [string]$Dst) {
$raw = [System.IO.File]::ReadAllText($Src)
$lf = ($raw -replace "`r`n", "`n") -replace "`r", "`n"
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($Dst, $lf, $utf8NoBom)
}
function Setup-MonitorProject([hashtable]$Proj) {
$projPath = Join-Path $RepoRoot $Proj.Dir
if (-not (Test-Path $projPath)) {
Write-Host " 跳过(目录不存在): $($Proj.Dir)" -ForegroundColor Yellow
return
}
Write-Step "$($Proj.Dir)"
Push-Location $projPath
try {
$venvDir = Join-Path $projPath ".venv"
$venvPy = Join-Path $venvDir "Scripts\python.exe"
$venvPip = Join-Path $venvDir "Scripts\pip.exe"
if ($RecreateVenv -and (Test-Path $venvDir)) {
Write-Host " 删除旧 venv ..."
Remove-Item -Recurse -Force $venvDir
}
if (-not (Test-Path $venvPy)) {
Write-Host " 创建 venv ..."
& python -m venv .venv
}
Write-Host " 升级 pip ..."
& $venvPy -m pip install -U pip setuptools wheel -q
Write-Host " 安装依赖 (requirements.txt) ..."
& $venvPip install -r $ReqFile -q
if (-not $SkipEnvCopy) {
$envExample = Join-Path $projPath ".env.example"
$envFile = Join-Path $projPath ".env"
if ((Test-Path $envExample) -and -not (Test-Path $envFile)) {
Copy-EnvFileLf $envExample $envFile
Write-Host " 已复制 .env.example -> .env (LF)" -ForegroundColor Green
} elseif (Test-Path $envFile) {
Write-Host " 保留已有 .env" -ForegroundColor DarkGray
} else {
Write-Host " 无 .env.example,请手动配置 .env" -ForegroundColor Yellow
}
}
$staticDirs = @(
"static\images",
"static\images\order_charts"
)
foreach ($d in $staticDirs) {
$full = Join-Path $projPath $d
if (-not (Test-Path $full)) {
New-Item -ItemType Directory -Path $full -Force | Out-Null
}
}
Write-Host " 完成: $venvPy" -ForegroundColor Green
} finally {
Pop-Location
}
}
function Setup-HubProject() {
$projPath = Join-Path $RepoRoot $HubProject.Dir
if (-not (Test-Path $projPath)) {
Write-Host " 跳过 hub(目录不存在)" -ForegroundColor Yellow
return
}
Write-Step $HubProject.Dir
Push-Location $projPath
try {
$venvDir = Join-Path $projPath ".venv"
$venvPy = Join-Path $venvDir "Scripts\python.exe"
$venvPip = Join-Path $venvDir "Scripts\pip.exe"
if ($RecreateVenv -and (Test-Path $venvDir)) {
Remove-Item -Recurse -Force $venvDir
}
if (-not (Test-Path $venvPy)) {
& python -m venv .venv
}
& $venvPy -m pip install -U pip setuptools wheel -q
if (Test-Path $HubReqFile) {
& $venvPip install -r $HubReqFile -q
}
if (-not $SkipEnvCopy) {
$envExample = Join-Path $projPath ".env.example"
$envFile = Join-Path $projPath ".env"
if ((Test-Path $envExample) -and -not (Test-Path $envFile)) {
Copy-EnvFileLf $envExample $envFile
Write-Host " 已复制 .env.example -> .env (LF)" -ForegroundColor Green
}
}
Write-Host " 完成: $venvPy" -ForegroundColor Green
} finally {
Pop-Location
}
}
function Install-Pm2IfNeeded() {
if ($SkipPm2) { return }
$node = Get-Command node -ErrorAction SilentlyContinue
if (-not $node) {
Write-Host "未检测到 Node.js,跳过 PM2。安装 Node 后执行: npm install -g pm2" -ForegroundColor Yellow
return
}
Write-Step "PM2(可选进程托管)"
$pm2 = Get-Command pm2 -ErrorAction SilentlyContinue
if ($pm2) {
Write-Host " PM2 已安装: $(pm2 -v)" -ForegroundColor Green
return
}
Write-Host " 正在全局安装 pm2 ..."
& npm install -g pm2
Write-Host " PM2 安装完成。在各子目录执行: pm2 start ecosystem.config.cjs" -ForegroundColor Green
}
# --- main ---
Write-Host "crypto_monitor 环境部署" -ForegroundColor White
Write-Host "仓库根目录: $RepoRoot" -ForegroundColor DarkGray
if (-not (Test-Path $ReqFile)) {
throw "缺少 $ReqFile"
}
Test-Python310
$selected = ($Only -split "[,;\s]+" | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ })
if (-not $selected -or $selected.Count -eq 0) { $selected = @("all") }
foreach ($p in $MonitorProjects) {
if (Should-Include $p.Key $selected) {
Setup-MonitorProject $p
}
}
if (Should-Include $HubProject.Key $selected) {
Setup-HubProject
}
Install-Pm2IfNeeded
Write-Host ""
Write-Host "部署完成。下一步:" -ForegroundColor Green
Write-Host " 1. 编辑各子目录 .env(API Key、密码、代理等)"
Write-Host " 2. 启动示例(Binance):"
Write-Host " cd crypto_monitor_binance"
Write-Host " .\.venv\Scripts\activate"
Write-Host " python app.py"
Write-Host " 3. Linux 服务器可用: bash deploy/setup_env.sh"
Write-Host ""

Some files were not shown because too many files have changed in this diff Show More