Bắt đầuStart here
Kiến trúc hệ thốngSystem architecture
Mọi thành phần của dự án GoldenRobber, chúng chạy ở đâu, nói chuyện với nhau qua cổng nào, dữ liệu và secret nằm ở đâu. Mọi con số trên trang này được đối chiếu với code và file cấu hình trong repo. Every component of the GoldenRobber project, where it runs, which ports it talks over, and where data and secrets live. Every number on this page was checked against the code and config files in the repo.
Tổng quanOverview
Hệ thống có bốn khối:
- Terminal MT5 trên các VPS Windows (Proxmox trên OVH Dedicated Server 02…N, sau các OpenWRT, xem Hạ tầng máy chủ): EA giao dịch
GoldenRobber, EA giám sátAccountStatusUpdater, EA phát tickTickStreamerPub, vàvps-agent(HeartbeatAgent + CmdAgent + stunnel) quản lý máy. - Docker host (OVH Dedicated Server 01) chạy stack
fxconsole(Laravel + React, nginx, Redis, TimescaleDB, scheduler, claude-cli, Guacamole) và bốn broker Node.js realtime (Tick, AccountStatus, VpsStream, EaLink), tất cả cùng mạng Dockerfxconsole_default. - MySQL từ xa trên host aaPanel (database
trade_copier): nguồn chân lý cho user, account, config EA, TMap, bảngsettings. - Biên mạng: openresty (aaPanel, chứng chỉ Let's Encrypt) cho HTTPS; OVH Network Firewall + IPFire chuyển thẳng các cổng TLS của broker vào Docker host; mỗi server VPS có một CT WireGuard (wg-easy), server fxconsole là client của tất cả.
Nguyên tắc xuyên suốt: giá trị nhạy cảm (config, mật khẩu MT5, gói cài) chỉ đi qua HTTPS có chứng chỉ thật; các socket TLS tới broker dùng chứng chỉ tự ký nên chỉ mang tín hiệu, telemetry hoặc con trỏ. Broker chết thì mọi thành phần vẫn có đường HTTPS dự phòng.
The system has four blocks:
- MT5 terminals on Windows VPSes (Proxmox on OVH Dedicated Servers 02…N, behind OpenWRT routers, see Server infrastructure): the trading EA
GoldenRobber, the monitoring EAAccountStatusUpdater, the tick publisherTickStreamerPub, andvps-agent(HeartbeatAgent + CmdAgent + stunnel) managing the machine. - Docker host (OVH Dedicated Server 01) running the
fxconsolestack (Laravel + React, nginx, Redis, TimescaleDB, scheduler, claude-cli, Guacamole) and four realtime Node.js brokers (Tick, AccountStatus, VpsStream, EaLink), all on the Docker networkfxconsole_default. - Remote MySQL on the aaPanel host (database
trade_copier): source of truth for users, accounts, EA config, TMap and thesettingstable. - Network edge: openresty (aaPanel, Let's Encrypt certificate) for HTTPS; OVH Network Firewall + IPFire forward the brokers' TLS ports straight to the Docker host; each VPS server has a WireGuard CT (wg-easy), and the fxconsole server is a client of all of them.
One rule runs through everything: sensitive values (config, MT5 passwords, install packages) only travel over HTTPS with a real certificate; TLS sockets to the brokers use self-signed certificates, so they carry only signals, telemetry or pointers. If a broker dies, every component still has an HTTPS fallback path.
flowchart LR
subgraph VPS["VPS Windows (MT5)"]
GR["GoldenRobber EA"]
ASU["AccountStatusUpdater EA"]
TSP["TickStreamerPub EA"]
VA["HeartbeatAgent + CmdAgent"]
ST["stunnel"]
VNC["TightVNC"]
end
subgraph EDGE["Biên mạng"]
OR["openresty :443 (aaPanel, Let's Encrypt)"]
FW["OVH firewall + IPFire (port forward)"]
end
subgraph HOST["Docker host · mạng fxconsole_default"]
WEB["fxconsole-web nginx :8080"]
APP["fxconsole-app php-fpm"]
SCH["fxconsole-scheduler"]
RED[("fxconsole-redis")]
TS[("fxconsole-timescale")]
CC["fxconsole-claude-cli :8788"]
GUA["guacamole :8282 + guacd"]
TB["tick-broker :9600 / :9601"]
AB["account-status-broker :9602 / :9603"]
VB["vps-stream-broker :9605 / :9606"]
EB["ea-link-broker :9607"]
end
MY[("MySQL trade_copier (host aaPanel)")]
CF["Cloudflare Worker + D1 (tuỳ chọn)"]
ADM(["Trình duyệt admin / user"])
GR -- "HTTPS heartbeat, TMap" --> OR
ASU -- "HTTPS /api/account-status" --> OR
TSP -- "HTTPS /api/sync-*" --> OR
VA -- "HTTPS dự phòng + payload" --> OR
ADM -- "HTTPS + WSS" --> OR
OR --> WEB
GR -- "TLS 9607 tín hiệu" --> FW
ASU -- "TLS 9602" --> FW
TSP -- "TLS 9600" --> FW
VA --> ST
ST -- "TLS 9605" --> FW
FW --> EB
FW --> AB
FW --> TB
FW --> VB
WEB --> APP
WEB -- "/ws /status-ws /vps-ws" --> TB
WEB --> AB
WEB --> VB
APP --> RED
SCH --> RED
APP --> TS
SCH --> TS
APP --> MY
SCH --> MY
SCH --> CC
APP --> CC
TB --> RED
AB --> RED
VB --> RED
EB --> RED
EB -- "settings" --> MY
VB -- "vpses, settings" --> MY
AB -- "settings" --> MY
EB -- "POST /api/internal/ea-link/key" --> WEB
VB -- "POST /api/internal/broker/command-ack" --> WEB
GR -. "tmap (bỏ trống ở production)" .-> CF
ADM -- "?data=token" --> GUA
GUA -- "VNC" --> VNCToàn cảnh: nét liền là luồng đang dùng, nét đứt là tuỳ chọn. Mỗi luồng được vẽ chi tiết ở mục Luồng dữ liệu.
flowchart LR
subgraph VPS["Windows VPS (MT5)"]
GR["GoldenRobber EA"]
ASU["AccountStatusUpdater EA"]
TSP["TickStreamerPub EA"]
VA["HeartbeatAgent + CmdAgent"]
ST["stunnel"]
VNC["TightVNC"]
end
subgraph EDGE["Network edge"]
OR["openresty :443 (aaPanel, Let's Encrypt)"]
FW["OVH firewall + IPFire (port forward)"]
end
subgraph HOST["Docker host · network fxconsole_default"]
WEB["fxconsole-web nginx :8080"]
APP["fxconsole-app php-fpm"]
SCH["fxconsole-scheduler"]
RED[("fxconsole-redis")]
TS[("fxconsole-timescale")]
CC["fxconsole-claude-cli :8788"]
GUA["guacamole :8282 + guacd"]
TB["tick-broker :9600 / :9601"]
AB["account-status-broker :9602 / :9603"]
VB["vps-stream-broker :9605 / :9606"]
EB["ea-link-broker :9607"]
end
MY[("MySQL trade_copier (aaPanel host)")]
CF["Cloudflare Worker + D1 (optional)"]
ADM(["Admin / user browser"])
GR -- "HTTPS heartbeat, TMap" --> OR
ASU -- "HTTPS /api/account-status" --> OR
TSP -- "HTTPS /api/sync-*" --> OR
VA -- "HTTPS fallback + payload" --> OR
ADM -- "HTTPS + WSS" --> OR
OR --> WEB
GR -- "TLS 9607 signal" --> FW
ASU -- "TLS 9602" --> FW
TSP -- "TLS 9600" --> FW
VA --> ST
ST -- "TLS 9605" --> FW
FW --> EB
FW --> AB
FW --> TB
FW --> VB
WEB --> APP
WEB -- "/ws /status-ws /vps-ws" --> TB
WEB --> AB
WEB --> VB
APP --> RED
SCH --> RED
APP --> TS
SCH --> TS
APP --> MY
SCH --> MY
SCH --> CC
APP --> CC
TB --> RED
AB --> RED
VB --> RED
EB --> RED
EB -- "settings" --> MY
VB -- "vpses, settings" --> MY
AB -- "settings" --> MY
EB -- "POST /api/internal/ea-link/key" --> WEB
VB -- "POST /api/internal/broker/command-ack" --> WEB
GR -. "tmap (empty in production)" .-> CF
ADM -- "?data=token" --> GUA
GUA -- "VNC" --> VNCThe whole picture: solid lines are flows in use, dashed lines are optional. Each flow is drawn in detail under Data flows.
Hạ tầng máy chủServer infrastructure
Hệ thống chạy trên nhiều OVH Dedicated Server với hai vai trò: server 01 chạy fxconsole; các server 02, 03 … N chứa VPS, và mỗi VPS chạy các tài khoản MT5.
The system runs on several OVH Dedicated Servers with two roles: server 01 runs fxconsole; servers 02, 03 … N host VPSes, and each VPS runs MT5 accounts.
| Server | Vai tròRole | Chạy gìWhat runs there |
|---|---|---|
| OVH Dedicated Server 01 | fxconsole | Docker host: stack fxconsole, bốn broker, Guacamole. Là WireGuard client của mọi server VPS.Docker host: the fxconsole stack, the four brokers, Guacamole. It is a WireGuard client of every VPS server. |
| OVH Dedicated Server 02, 03 … N | Chứa VPS MT5MT5 VPS hosts | Proxmox, được cấp nhiều IP WAN; mỗi IP gán cho một OpenWRT. Các VPS Windows chạy tài khoản MT5 nằm trong LAN của các OpenWRT. Một CT WireGuard (wg-easy) nối vào mọi LAN của server đó.Proxmox with several WAN IPs, each assigned to an OpenWRT router. The Windows VPSes running MT5 accounts sit in the OpenWRT LANs. One WireGuard CT (wg-easy) is attached to every LAN of that server. |
Server 01 là WireGuard client, mỗi server VPS một tunnel. Tunnel vào IP WAN chính của server VPS, OpenWRT của LAN đầu tiên chuyển tiếp UDP 51820 vào CT WireGuard; CT có chân trên mọi LAN nên guacd VNC được tới bất kỳ VPS nào. Các OpenWRT khác dùng IP phụ, nên mỗi LAN ra Internet bằng một IP riêng.
Server 01 is a WireGuard client with one tunnel per VPS server. The tunnel reaches the VPS server's main WAN IP, and the first LAN's OpenWRT forwards UDP 51820 to the WireGuard CT; the CT has a leg on every LAN, so guacd can VNC into any VPS. The other OpenWRTs use additional IPs, so each LAN goes out through its own IP.
Vì sao mỗi server VPS có nhiều IP WANWhy each VPS server has several WAN IPs
VPS nằm sau OpenWRT nào thì ra Internet, kể cả kết nối tới broker MT5, bằng IP WAN của OpenWRT đó. Chia VPS vào nhiều LAN giúp các tài khoản MT5 được dàn trải ra nhiều IP khác nhau, để broker không thấy hàng loạt tài khoản đi ra từ cùng một IP rồi sinh nghi ngờ. Các VPS trong cùng một LAN dùng chung một IP WAN.
A VPS reaches the Internet, including its MT5 broker connection, through the WAN IP of the OpenWRT it sits behind. Splitting VPSes across many LANs spreads the MT5 accounts over many different IPs, so a broker never sees a batch of accounts coming from one IP and grows suspicious. VPSes in the same LAN share one WAN IP.
Cách server fxconsole tới được mọi VPSHow the fxconsole server reaches every VPS
- Mỗi server VPS có một CT WireGuard (wg-easy) với một NIC trên bridge LAN của từng OpenWRT. CT MASQUERADE lưu lượng của client ra đúng LAN đích.
- Server 01 là client của CT WireGuard trên từng server 02 … N, mỗi server một tunnel. Endpoint của tunnel là IP WAN của OpenWRT đang port-forward UDP
51820vào CT. Trên nodefxbot(server 02) đó là IP chính của server (fxbot.sv02.gamelau.uk): host Proxmox chuyển IP chính về OpenWRT của LAN 101, OpenWRT này forward UDP 51820 tới CT192.168.101.211. OVH Network Firewall của server đó phải cho UDP 51820 đi qua. - Tunnel trên server 01 phải là split-tunnel: AllowedIPs chỉ gồm các subnet LAN của server đó. File client wg-easy tạo ra mặc định có
0.0.0.0/0; để nguyên thì mọi lưu lượng của server 01, kể cả fxconsole, bị kéo qua server VPS. - Kết quả: từ server 01 ping được IP LAN của mọi dải, tức mọi VPS trên mọi server. Đây là mô hình hình sao với server 01 ở giữa; các server VPS không nối với nhau qua đường này.
- Guacamole dựa vào đường này: guacd (container trên server 01) mở VNC tới
lan_ip:5900mà vps-agent báo qua heartbeat (Guacamole); gói đi theo bảng định tuyến của host vào đúng tunnel. Nhờ MASQUERADE trong CT, VPS thấy kết nối đến từ IP của CT trong chính LAN của nó, nên không cần route ngược về server 01. - Chiều ngược lại không đi qua VPN: EA và vps-agent trên VPS gọi fxconsole qua Internet công khai (
fxconsole.gamelau.uk: HTTPS 443 và các cổng TLS của broker), đi ra bằng IP WAN của OpenWRT. - Ví dụ node
fxbot: CT 107, 3 NIC vmbr101/102/103 (IP.211trong 192.168.101/102/103, chỉ NIC đầu có gateway), client10.8.2.0/24. Sau mỗi lần tạo hoặc xoá client phải restart Docker trong CT.
- Each VPS server has one WireGuard CT (wg-easy) with one NIC on the LAN bridge of every OpenWRT. The CT MASQUERADEs client traffic into the target LAN.
- Server 01 is a client of the WireGuard CT on each server 02 … N, one tunnel per server. The tunnel endpoint is the WAN IP of the OpenWRT that port-forwards UDP
51820to the CT. On nodefxbot(server 02) that is the server's main IP (fxbot.sv02.gamelau.uk): the Proxmox host hands the main IP to the OpenWRT of LAN 101, which forwards UDP 51820 to the CT at192.168.101.211. That server's OVH Network Firewall must let UDP 51820 through. - The tunnels on server 01 must be split-tunnel: AllowedIPs lists only that server's LAN subnets. The client file wg-easy generates contains
0.0.0.0/0by default; left as is, all of server 01's traffic, fxconsole included, would be pulled through the VPS server. - Result: server 01 can ping LAN IPs in every range, i.e. every VPS on every server. It is a star with server 01 at the centre; the VPS servers are not connected to each other through this path.
- Guacamole relies on this path: guacd (a container on server 01) opens VNC to the
lan_ip:5900that vps-agent reports in its heartbeat (Guacamole); packets follow the host's routing table into the right tunnel. Because of MASQUERADE in the CT, the VPS sees the connection coming from the CT's IP in its own LAN, so no return route to server 01 is needed. - The other direction does not use the VPN: EAs and vps-agent on the VPS call fxconsole over the public Internet (
fxconsole.gamelau.uk: HTTPS 443 and the broker TLS ports), leaving through the OpenWRT's WAN IP. - Node
fxbotexample: CT 107, 3 NICs vmbr101/102/103 (IP.211in 192.168.101/102/103, only the first NIC has a gateway), clients10.8.2.0/24. After every client create or delete, restart Docker inside the CT.
- Mỗi OpenWRT có LAN riêng, và subnet LAN không được trùng với bất kỳ OpenWRT nào khác, dù cùng server hay khác server. Server 01 chọn tunnel theo subnet đích, nên hai LAN trùng subnet thì chỉ một cái tới được.
- Thêm LAN: gắn thêm NIC cho CT WireGuard trên bridge của LAN đó (không đặt gateway) và MASQUERADE ra NIC mới, rồi thêm subnet mới vào AllowedIPs của tunnel trên server 01. Hệ thống không tự cập nhật bước này.
- Thêm server: dựng CT WireGuard nối vào mọi LAN, port-forward UDP 51820 trên một OpenWRT, mở UDP 51820 ở OVH Network Firewall, tạo client cho server 01 rồi restart Docker trong CT. Sửa AllowedIPs của file client thành các subnet LAN của server đó trước khi nạp lên server 01.
- Kiểm tra: từ server 01 ping IP LAN của từng OpenWRT (ví dụ
192.168.101.1), rồi bấm Connect một VPS thuộc LAN đó trên/vps.
- Each OpenWRT has its own LAN, and its subnet must not overlap with any other OpenWRT, on the same server or another one. Server 01 picks a tunnel by destination subnet, so of two LANs sharing a subnet only one is reachable.
- Adding a LAN: give the WireGuard CT another NIC on that LAN's bridge (no gateway) and MASQUERADE out of the new NIC, then add the new subnet to the tunnel's AllowedIPs on server 01. Nothing updates that automatically.
- Adding a server: build a WireGuard CT attached to every LAN, port-forward UDP 51820 on one OpenWRT, open UDP 51820 on the OVH Network Firewall, create a client for server 01, then restart Docker inside the CT. Change the client file's AllowedIPs to that server's LAN subnets before loading it on server 01.
- Check: from server 01, ping each OpenWRT's LAN IP (for example
192.168.101.1), then click Connect on a VPS in that LAN under/vps.
Các thành phầnComponents
| Thành phầnComponent | Chạy ở đâuRuns where | Vai tròRole |
|---|---|---|
fxconsole-web | Docker host, nginx, cổng host 8080 → 80Docker host, nginx, host port 8080 → 80 | Phục vụ Laravel qua php-fpm, file build Vite, và proxy WebSocket /ws, /status-ws, /vps-ws tới broker (proxy_ssl_verify off). Broker gọi ngược Laravel qua http://fxconsole-web (cổng nội bộ 80).Serves Laravel through php-fpm, the Vite build files, and proxies the /ws, /status-ws, /vps-ws WebSockets to the brokers (proxy_ssl_verify off). Brokers call back into Laravel via http://fxconsole-web (internal port 80). |
fxconsole-app | Docker host, php-fpm 9000 (chỉ nội bộ)Docker host, php-fpm 9000 (internal only) | Laravel + Inertia + React. Khi khởi động: cài composer/npm nếu manifest đổi, npm run build, tạo APP_KEY chỉ khi chưa có, php artisan migrate --force. Đọc cert của VpsStreamBroker ở /broker-certs (mount chỉ đọc).Laravel + Inertia + React. On boot: installs composer/npm deps when manifests changed, npm run build, generates APP_KEY only if missing, php artisan migrate --force. Reads the VpsStreamBroker cert at /broker-certs (read-only mount). |
fxconsole-scheduler | Docker host | php artisan schedule:clear-cache rồi schedule:work: ohlc:aggregate, brokers:sync, calendar:sync, news:sync, fx:sync-usdt-vnd, report:generate-all, analysis:run, fxconsole:scan-lost-vpses, fxconsole:detect-mt5-drift, fxconsole:close-stale-remote-sessions, proxmox:finish-provisions…php artisan schedule:clear-cache then schedule:work: ohlc:aggregate, brokers:sync, calendar:sync, news:sync, fx:sync-usdt-vnd, report:generate-all, analysis:run, fxconsole:scan-lost-vpses, fxconsole:detect-mt5-drift, fxconsole:close-stale-remote-sessions, proxmox:finish-provisions… |
fxconsole-redis | Docker host, 6379 nội bộDocker host, 6379 internal | Redis 7, không persistence (--save "", --appendonly no, allkeys-lru). Trạng thái nóng, tick stream, pub/sub giữa Laravel và broker.Redis 7, no persistence (--save "", --appendonly no, allkeys-lru). Hot state, tick streams, pub/sub between Laravel and the brokers. |
fxconsole-timescale | Docker host, 5432 publish mọi interfaceDocker host, 5432 published on all interfaces | TimescaleDB (pg16), chỉ cho bảng ohlc_data. Volume timescale_data.TimescaleDB (pg16), only for the ohlc_data table. Volume timescale_data. |
fxconsole-claude-cli | Docker host, 8788 nội bộDocker host, 8788 internal | Sidecar HTTP bọc Claude Code CLI, chạy phân tích thị trường bằng subscription OAuth. Credential trên volume claude_creds, claude_config. Xem claude-cli.HTTP sidecar wrapping the Claude Code CLI; runs market analysis on the OAuth subscription. Credentials on volumes claude_creds, claude_config. See claude-cli. |
fxconsole-guacamole + fxconsole-guacd | Docker host, 8282 → 8080; guacd 4822 trong mạng guac_internalDocker host, 8282 → 8080; guacd 4822 on network guac_internal | Remote desktop trong trình duyệt cho admin (JSON auth, giao thức VNC). Xem Guacamole.In-browser remote desktop for admins (JSON auth, VNC protocol). See Guacamole. |
fxconsole-tick-broker | Docker host, 9600 TLS, 9601 WSSDocker host, 9600 TLS, 9601 WSS | Nhận tick + lịch kinh tế từ TickStreamerPub, ghi Redis stream, fan-out cho trang Charts.Receives ticks + the economic calendar from TickStreamerPub, writes Redis streams, fans out to the Charts pages. |
fxconsole-account-status-broker | Docker host, 9602 TLS, 9603 WSSDocker host, 9602 TLS, 9603 WSS | Nhận trạng thái account từ AccountStatusUpdater, ghi Redis giống AccountStatusStore, stream cho dashboard, chuyển report_request.Receives account status from AccountStatusUpdater, writes Redis like AccountStatusStore, streams to the dashboard, routes report_request. |
fxconsole-vps-stream-broker | Docker host, 9605 TLS, 9606 WSS (chỉ trong mạng Docker)Docker host, 9605 TLS, 9606 WSS (Docker network only) | Heartbeat + đẩy lệnh cho vps-agent, ghi vps_commands, callback Laravel, stream cho /vps và /deployments.Heartbeats + command push for vps-agent, writes vps_commands, calls back into Laravel, streams to /vps and /deployments. |
fxconsole-ea-link-broker | Docker host, 9607 TLSDocker host, 9607 TLS | Kênh EA Link: báo EA "config đã đổi, kéo lại qua HTTPS". Xem EaLinkBroker.EA Link channel: tells the EA "config changed, pull it over HTTPS". See EaLinkBroker. |
GoldenRobber EA | Terminal MT5 (production 1.42 b7; bản cũ 1.41.4 b21)MT5 terminal (production 1.42 b7; previous 1.41.4 b21) | Giao dịch XAUUSD. Heartbeat HTTPS tới /easupported/goldenrobber/api/tmap/* (config hot-reload, leader election, TMap); từ 1.42 thêm EA Link TLS 9607.Trades XAUUSD. HTTPS heartbeat to /easupported/goldenrobber/api/tmap/* (hot-reload config, leader election, TMap); from 1.42 also EA Link over TLS 9607. |
AccountStatusUpdater EA | Terminal MT5 (#property version "1.16")MT5 terminal (#property version "1.16") | Đẩy trạng thái account: HTTP /api/account-status và/hoặc TLS 9602 (HELLO v2). Xem AccountStatusUpdater.Pushes account status: HTTP /api/account-status and/or TLS 9602 (HELLO v2). See AccountStatusUpdater. |
TickStreamerPub EA | Terminal MT5 (#property version "1.04")MT5 terminal (#property version "1.04") | Phát tick Market Watch + lịch kinh tế MT5 tới TickBroker; backfill OHLC qua HTTPS.Publishes Market Watch ticks + the MT5 economic calendar to TickBroker; backfills OHLC over HTTPS. |
vps-agent | Mỗi VPS Windows (HeartbeatAgent 1.3.2-phase9, CmdAgent, stunnel, TightVNC)Each Windows VPS (HeartbeatAgent 1.3.2-phase9, CmdAgent, stunnel, TightVNC) | Telemetry máy, thực thi lệnh deploy/remove/upgrade tài khoản MT5. Xem VPS agent.Machine telemetry, executes deploy/remove/upgrade commands for MT5 accounts. See VPS agent. |
cloudflare-tmap-worker | Cloudflare Workers + D1 (tuỳ chọn)Cloudflare Workers + D1 (optional) | Backup TMap thứ hai. Config ổn định hiện tại để InpCloudCfDomain trống, tức là không dùng. Xem Cloudflare worker.Second TMap backup. The current stable config leaves InpCloudCfDomain empty, so it is not used. See Cloudflare worker. |
MySQL trade_copier | Host aaPanel từ xa (mysql.khaihoanexim.com:3217)Remote aaPanel host (mysql.khaihoanexim.com:3217) | CSDL chính của fxconsole. Ba broker (EaLink, VpsStream, AccountStatus) đọc thẳng bảng settings.fxconsole's main database. Three brokers (EaLink, VpsStream, AccountStatus) read the settings table directly. |
| Binance P2P (bên ngoài)(external) | Internet, p2p.binance.com (HTTPS gọi ra từ fxconsole-scheduler)Internet, p2p.binance.com (outbound HTTPS from fxconsole-scheduler) | Nguồn tỷ giá USDT/VND cho nút đổi tiền: fx:sync-usdt-vnd lấy median 10 quảng cáo SELL, mặc định 5 phút một lần, lưu fx_rates. Endpoint không chính thức, lỗi thì giữ giá cũ. Xem fxconsole.USDT/VND rate source for the currency switch: fx:sync-usdt-vnd takes the median of 10 SELL ads, every 5 minutes by default, into fx_rates. Unofficial endpoint; on failure the last rate is kept. See fxconsole. |
Mạng và đường vàoNetwork and entry points
- HTTPS công khai đi vào openresty trên aaPanel (chứng chỉ Let's Encrypt), rồi tới
fxconsole-webcổng8080. Laravel đứng sau proxy nên cầnAPP_URLdạng https vàTRUSTED_PROXIESđể tin headerX-Forwarded-Proto. - Cổng TLS của broker (9600, 9602, 9605, 9607) được mở trên OVH Network Firewall và port-forward trên IPFire thẳng vào Docker host, không qua openresty.
- WebSocket cho trình duyệt không cần mở cổng riêng: trình duyệt nói
wss://fxconsole.gamelau.uk/ws,/status-ws,/vps-wsvới chứng chỉ thật của openresty; nginx trongfxconsole-webproxy tiếp tớifxconsole-tick-broker:9601,fxconsole-account-status-broker:9603,fxconsole-vps-stream-broker:9606vớiproxy_ssl_verify off(chứng chỉ tự ký chỉ đi qua trong mạng Docker), timeout đọc/gửi 3600 s. - Mạng Docker: stack fxconsole tạo mạng
fxconsole_default(project namefxconsole). Bốn stack broker khai báo mạng này làexternal, nên phải bật fxconsole trước để broker phân giải đượcfxconsole-redisvàfxconsole-web. Guacamole và guacd dùng thêm mạng riêngguac_internal. - VPN: mỗi server VPS có một CT WireGuard (wg-easy) nối vào mọi LAN OpenWRT của server đó; server fxconsole kết nối làm client tới tất cả để Guacamole tới được mọi VPS. Chi tiết: Hạ tầng máy chủ. Sau mỗi lần tạo hoặc xoá client phải restart Docker trong CT.
- Public HTTPS enters through openresty on aaPanel (Let's Encrypt certificate), then reaches
fxconsole-webon port8080. Laravel sits behind a proxy, so it needs an httpsAPP_URLandTRUSTED_PROXIESso it trusts theX-Forwarded-Protoheader. - Broker TLS ports (9600, 9602, 9605, 9607) are opened on the OVH Network Firewall and port-forwarded on IPFire directly to the Docker host, bypassing openresty.
- Browser WebSockets need no extra port: the browser speaks
wss://fxconsole.gamelau.uk/ws,/status-ws,/vps-wswith openresty's real certificate; nginx insidefxconsole-webproxies on tofxconsole-tick-broker:9601,fxconsole-account-status-broker:9603,fxconsole-vps-stream-broker:9606withproxy_ssl_verify off(the self-signed certificate is only crossed inside the Docker network), 3600 s read/send timeouts. - Docker network: the fxconsole stack creates
fxconsole_default(project namefxconsole). The four broker stacks declare itexternal, so fxconsole must be up first for the brokers to resolvefxconsole-redisandfxconsole-web. Guacamole and guacd also use a private networkguac_internal. - VPN: each VPS server has a WireGuard CT (wg-easy) attached to every OpenWRT LAN on that server; the fxconsole server connects to all of them as a client so Guacamole can reach every VPS. Details: Server infrastructure. After every client create or delete, restart Docker inside the CT.
flowchart LR
I(["Internet"])
subgraph AAP["aaPanel host"]
OR["openresty :443"]
MY[("MySQL :3217")]
end
FW["OVH Network Firewall + IPFire"]
subgraph DH["Docker host"]
W["fxconsole-web :8080"]
T["tick-broker :9600 :9601"]
A["account-status-broker :9602 :9603"]
V["vps-stream-broker :9605 :9606"]
E["ea-link-broker :9607"]
G["guacamole :8282"]
C["WireGuard client"]
end
subgraph PVE["OVH Server 02…N (Proxmox)"]
WG["WireGuard CT (wg-easy) :51820/udp"]
LAN["OpenWRT LANs: VPS + MT5"]
end
I -- "HTTPS 443" --> OR
OR -- "HTTP 8080" --> W
I -- "TLS 9600 9602 9605 9607" --> FW
FW --> T
FW --> A
FW --> V
FW --> E
W -- "/ws" --> T
W -- "/status-ws" --> A
W -- "/vps-ws" --> V
C == "WireGuard UDP 51820" ==> WG
WG --- LAN
G -. "VNC lan_ip:5900 via tunnel" .-> LAN
Đường vào từ Internet. Guacamole (:8282) chỉ nên được reverse proxy truy cập, không mở ra ngoài.
Entry points from the Internet. Guacamole (:8282) should only be reached by the reverse proxy, never exposed directly.
fxconsole-timescale publish 5432:5432 trên mọi interface của Docker host để máy trong LAN dùng DBeaver/psql. Không được port-forward 5432; nếu host có IP public phải chặn 5432 bằng firewall. Guacamole publish 8282:8080 (HTTP thuần) cũng chỉ dành cho reverse proxy.
fxconsole-timescale publishes 5432:5432 on all Docker host interfaces so LAN machines can use DBeaver/psql. Never port-forward 5432; if the host has a public IP, block 5432 at the firewall. Guacamole publishes 8282:8080 (plain HTTP), also meant only for the reverse proxy.
Luồng dữ liệuData flows
Tám luồng dưới đây bao phủ toàn bộ giao tiếp giữa các thành phần. Mỗi luồng ghi rõ ai khởi tạo, xác thực bằng gì, dữ liệu dừng ở đâu và đường dự phòng khi một mắt xích chết.
The eight flows below cover all communication between components. Each one states who initiates, how it authenticates, where the data lands, and the fallback path when a link in the chain dies.
(a) GoldenRobber: heartbeat HTTPS, hot-reload, leader election(a) GoldenRobber: HTTPS heartbeat, hot-reload, leader election
- EA gọi mạng trong
OnTimer, nhịp đo bằngGetTickCount64(), mỗiInpCloudSyncIntervalSecgiây (config ổn định:10; chuẩn yêu cầu ≤ 60 vì server coi account offline sau 120 s không heartbeat). Backtest không gọi mạng. - URL =
InpCloudMyApiDomain+/api/tmap/heartbeat, tứchttps://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap/heartbeat. HeaderAuthorization: Bearer <InpCloudBearerToken>, so bằnghash_equalsvớieasupported.secret_tokencủa product: sai token →401, slug không tồn tại →404 Unknown EA.WebRequesttrả-1nghĩa là lỗi mạng hoặc host chưa có trong Allow WebRequest. - Request mang
magic,account,composite_id(slug(company)::slug(server)::login),node_id,priority,timeout_sec,server,account_name,config_schema(EA tự khai báo các key chỉnh được) vàpayload(toàn bộ Info Panel). - Server lưu account vào
ea_account(unique(ea_id, composite_id, magic)), node vàotmap_nodes, rồi trảis_leader,current_leader_id,alive_count,alive_nodes,hot_keys(CSV key đang override) và giá trị override từea_config. Key không có tronghot_keysthì EA quay về giá trị input. Production b21 hot-reload 14 key. - Leader election: leader = node còn sống có
prioritynhỏ nhất (hoà thìnode_idtheo alphabet); còn sống = heartbeat trongtimeout_sec(InpCloudHeartbeatTimeoutSec= 90). Chỉ leader mở/đóng lệnh vàsave/clearTMap. Heartbeat lỗi liên tục quá 2 × timeout → EA tự nhận leader; chưa từng heartbeat thành công → mặc định leader. - Admin xem và chỉnh ở
/admin/easupported/goldenrobber: account Online khi heartbeat < 120 s, chọn nhiều account rồi Apply Hot config / Clear config. Không có EA Link thì EA nhận giá trị mới ở heartbeat kế tiếp.
- The EA does network calls in
OnTimer, timed withGetTickCount64(), everyInpCloudSyncIntervalSecseconds (stable config:10; the standard requires ≤ 60 because the server marks an account offline after 120 s without a heartbeat). Backtests make no network calls. - URL =
InpCloudMyApiDomain+/api/tmap/heartbeat, i.e.https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap/heartbeat. HeaderAuthorization: Bearer <InpCloudBearerToken>, compared withhash_equalsagainst the product'seasupported.secret_token: wrong token →401, unknown slug →404 Unknown EA.WebRequestreturning-1means a network error or the host is missing from Allow WebRequest. - The request carries
magic,account,composite_id(slug(company)::slug(server)::login),node_id,priority,timeout_sec,server,account_name,config_schema(the EA declares its own tunable keys) andpayload(the whole Info Panel). - The server stores the account in
ea_account(unique(ea_id, composite_id, magic)) and the node intmap_nodes, then returnsis_leader,current_leader_id,alive_count,alive_nodes,hot_keys(CSV of overridden keys) and the override values fromea_config. A key absent fromhot_keysreverts to its input value. Production b21 hot-reloads 14 keys. - Leader election: leader = the alive node with the lowest
priority(ties broken bynode_idalphabetically); alive = heartbeat withintimeout_sec(InpCloudHeartbeatTimeoutSec= 90). Only the leader opens/closes orders and runs TMapsave/clear. Heartbeats failing for more than 2 × timeout → the EA claims leadership; never had a successful heartbeat → leader by default. - Admins view and edit at
/admin/easupported/goldenrobber: an account is Online when its heartbeat is < 120 s old; select several accounts, then Apply Hot config / Clear config. Without EA Link, the EA picks up new values on its next heartbeat.
sequenceDiagram
participant EA as GoldenRobber (MT5)
participant OR as openresty :443
participant APP as fxconsole-web + app
participant DB as MySQL
Note over EA: OnTimer, mỗi InpCloudSyncIntervalSec giây
EA->>OR: POST /easupported/goldenrobber/api/tmap/heartbeat
OR->>APP: chuyển tới :8080
APP->>DB: so Bearer với easupported.secret_token
alt sai token hoặc slug
APP-->>EA: 401 Unauthorized hoặc 404 Unknown EA
else hợp lệ
APP->>DB: upsert ea_account, tmap_nodes, config_schema
APP->>DB: đọc override trong ea_config
APP-->>EA: is_leader, alive_nodes, hot_keys, giá trị override
Note over EA: áp vào biến global, key ngoài hot_keys về giá trị input
endsequenceDiagram
participant EA as GoldenRobber (MT5)
participant OR as openresty :443
participant APP as fxconsole-web + app
participant DB as MySQL
Note over EA: OnTimer, every InpCloudSyncIntervalSec seconds
EA->>OR: POST /easupported/goldenrobber/api/tmap/heartbeat
OR->>APP: forwarded to :8080
APP->>DB: compare Bearer with easupported.secret_token
alt wrong token or slug
APP-->>EA: 401 Unauthorized or 404 Unknown EA
else valid
APP->>DB: upsert ea_account, tmap_nodes, config_schema
APP->>DB: read overrides from ea_config
APP-->>EA: is_leader, alive_nodes, hot_keys, override values
Note over EA: applied to global mutables, keys outside hot_keys revert to input
endHeartbeat HTTPS là kênh bắt buộc và là nguồn chân lý của config. Chi tiết chuẩn: Chuẩn tích hợp EA. The HTTPS heartbeat is the mandatory channel and the source of truth for config. Full standard: EA integration standard.
(b) EA Link: tín hiệu config realtime(b) EA Link: realtime config signal
- Từ GoldenRobber 1.42 (
Include/EaLink.mqh, lớpCEaLink), mỗi chart giữ một kết nối TLS TCP tớiInpLinkHost:InpLinkPort(config ổn định:fxconsole.gamelau.uk:9607,InpLinkMode=1= HYBRID). Protocol NDJSON, tối đa 8 KiB mỗi dòng. Link chỉ bật khiInpCloudSyncEnable, có slug trongInpCloudMyApiDomainvà cóInpCloudBearerToken; không bật trong tester. - Vì sao chỉ là tín hiệu: MQL5
SocketTlsHandshake()không kiểm chứng chỉ server, broker dùng chứng chỉ tự ký. Socket vì thế không bao giờ mang giá trị config, token hay lệnh giao dịch; EA nhậncfgrồi tự kéo giá trị qua heartbeat HTTPS (WebRequest kiểm chứng chỉ thật). - Xác thực challenge-HMAC: broker gửi
challenge(nonce 32 hex) ngay sau TLS; EA trảhellotrong 5 s (HELLO_GRACE_MS) kèm chữ ký. Broker lấy link key của slug bằngPOST http://fxconsole-web/api/internal/ea-link/keyvới headerX-Broker-Secret; Laravel trảsha256(secret_token), không bao giờ trả token thô.200được cacheKEY_CACHE_SEC(60 s),404→unknown_ea(cache âm 10 s), lỗi khác →auth_unavailable(không cache). - Thành công →
hello_ok(hb_sec20), broker ghi presencelaravel-database-ea_link:presence:{slug}:{composite_id}:{magic}, field{node_id}#{inst}= unix giây,EXPIRE 90. EA gửihbmỗi 20 s; im lặng 60 s (HB_TIMEOUT_SEC) thì bị đóng. Web coi presence < 60 s là live và hiện badge Link. - Tín hiệu: admin Apply/Clear →
EaLinkNotifierPUBLISH ea:link:cfg(Redis facade thêm tiền tố thànhlaravel-database-ea:link:cfg) payload{"slug","targets":[{"composite_id","magic"}],"rev":<ms>}→ brokerPSUBSCRIBE *ea:link:cfg→ frame{"t":"cfg","rev"}tới mọi socket trong topicea/{slug}/{composite_id}/{magic}→ EA gom tín hiệu, kéo heartbeat tối đa 1 lần/giây → áp config → gửicfg_ack. Độ trễ ≈ ≤ 1 s. Broker logcfg rev=… slug=… targets=N delivered=M. - Kết nối lại cùng
{node_id}#{inst}thay socket cũ (bye superseded); khácinst(nhiều chart, leader/follower) thì cùng tồn tại và đều nhậncfg. EA backoff 2 → 60 s ±20% jitter;bad_sig/unknown_eachờ 5 phút. Broker chết thì panel báoLink: DOWNvà EA vẫn chạy như b21.1 (HTTP theo chu kỳ).
- From GoldenRobber 1.42 (
Include/EaLink.mqh, classCEaLink), each chart keeps one TLS TCP connection toInpLinkHost:InpLinkPort(stable config:fxconsole.gamelau.uk:9607,InpLinkMode=1= HYBRID). NDJSON protocol, 8 KiB max per line. Link is enabled only withInpCloudSyncEnable, a slug inInpCloudMyApiDomainand anInpCloudBearerToken; never in the tester. - Why only a signal: MQL5
SocketTlsHandshake()does not verify the server certificate, and the broker uses a self-signed one. The socket therefore never carries config values, tokens or trade commands; oncfgthe EA pulls the values over its HTTPS heartbeat (WebRequest verifies the real certificate). - Challenge-HMAC auth: the broker sends
challenge(32-hex nonce) right after TLS; the EA answershellowithin 5 s (HELLO_GRACE_MS) with a signature. The broker fetches the slug's link key withPOST http://fxconsole-web/api/internal/ea-link/keyand headerX-Broker-Secret; Laravel returnssha256(secret_token), never the raw token.200is cached forKEY_CACHE_SEC(60 s),404→unknown_ea(negative cache 10 s), anything else →auth_unavailable(not cached). - On success →
hello_ok(hb_sec20); the broker writes presencelaravel-database-ea_link:presence:{slug}:{composite_id}:{magic}, field{node_id}#{inst}= unix seconds,EXPIRE 90. The EA sendshbevery 20 s; 60 s of silence (HB_TIMEOUT_SEC) closes the socket. The web treats presence < 60 s as live and shows the Link badge. - Signal: admin Apply/Clear →
EaLinkNotifierPUBLISH ea:link:cfg(the Redis facade prefixes it tolaravel-database-ea:link:cfg) with payload{"slug","targets":[{"composite_id","magic"}],"rev":<ms>}→ brokerPSUBSCRIBE *ea:link:cfg→ frame{"t":"cfg","rev"}to every socket in topicea/{slug}/{composite_id}/{magic}→ the EA coalesces signals and pulls the heartbeat at most once per second → applies config → sendscfg_ack. Latency ≈ ≤ 1 s. Broker log:cfg rev=… slug=… targets=N delivered=M. - Reconnecting with the same
{node_id}#{inst}replaces the old socket (bye superseded); a differentinst(several charts, leader/follower) coexists and every one receivescfg. The EA backs off 2 → 60 s ±20% jitter;bad_sig/unknown_eawaits 5 minutes. If the broker is dead, the panel showsLink: DOWNand the EA keeps running like b21.1 (periodic HTTP).
key = hex(sha256(secret_token)) # 64-char hex STRING, used as-is as the HMAC key
msg = "ealink1|" + nonce + "|" + slug + "|" + composite_id + "|" + magic + "|" + node_id + "|" + inst
sig = hex(HMAC-SHA256(key, msg))
sequenceDiagram
participant EA as GoldenRobber 1.42
participant EB as ea-link-broker :9607
participant R as fxconsole-redis
participant APP as fxconsole-web + app
participant ADM as Admin
EA->>EB: TLS connect (không verify cert)
EB-->>EA: challenge nonce
EA->>EB: hello slug, composite_id, magic, node_id, inst, sig
EB->>APP: POST /api/internal/ea-link/key, X-Broker-Secret
APP-->>EB: key = sha256(secret_token), cache 60 s
EB-->>EA: hello_ok hb_sec=20
EB->>R: HSET ea_link:presence, EXPIRE 90
loop mỗi 20 s
EA->>EB: hb
EB-->>EA: hb_ok
end
ADM->>APP: Apply hoặc Clear trên /admin/easupported/goldenrobber
APP->>R: PUBLISH ea:link:cfg slug, targets, rev
R-->>EB: PSUBSCRIBE *ea:link:cfg
EB-->>EA: cfg rev
Note over EA: gom tín hiệu, tối đa 1 lần kéo mỗi giây
EA->>APP: POST /easupported/goldenrobber/api/tmap/heartbeat (HTTPS, cert thật)
APP-->>EA: hot_keys + giá trị
EA->>EB: cfg_ack rev, oksequenceDiagram
participant EA as GoldenRobber 1.42
participant EB as ea-link-broker :9607
participant R as fxconsole-redis
participant APP as fxconsole-web + app
participant ADM as Admin
EA->>EB: TLS connect (cert not verified)
EB-->>EA: challenge nonce
EA->>EB: hello slug, composite_id, magic, node_id, inst, sig
EB->>APP: POST /api/internal/ea-link/key, X-Broker-Secret
APP-->>EB: key = sha256(secret_token), cached 60 s
EB-->>EA: hello_ok hb_sec=20
EB->>R: HSET ea_link:presence, EXPIRE 90
loop every 20 s
EA->>EB: hb
EB-->>EA: hb_ok
end
ADM->>APP: Apply or Clear on /admin/easupported/goldenrobber
APP->>R: PUBLISH ea:link:cfg slug, targets, rev
R-->>EB: PSUBSCRIBE *ea:link:cfg
EB-->>EA: cfg rev
Note over EA: coalesce signals, at most one pull per second
EA->>APP: POST /easupported/goldenrobber/api/tmap/heartbeat (HTTPS, real cert)
APP-->>EA: hot_keys + values
EA->>EB: cfg_ack rev, okEA Link: kết nối, xác thực, rồi một vòng Apply → tín hiệu → kéo HTTPS → ack. Vận hành broker: EaLinkBroker. EA Link: connect, authenticate, then one Apply → signal → HTTPS pull → ack round. Broker operations: EaLinkBroker.
(c) AccountStatusUpdater → AccountStatusBroker → dashboard(c) AccountStatusUpdater → AccountStatusBroker → dashboard
- EA có ba chế độ
InpTransport: HTTP (mặc định), TCP, HYBRID. - HTTP:
POST InpApiUrl(/api/account-status) mỗiInpPostIntervalSec(5 s), header Bearer =InpApiBearer, so với settingea_api_bearer(envEA_API_BEARER); sai thì401 Unauthorized bearer token. LaravelAccountStatusStoreghi Redis (TTL 48 h), ghilot_history, chạyEquityAlertService; response có thể kèmpending_report. - TCP / HYBRID: TLS tới
InpBrokerHost:InpBrokerPort(mặc địnhfxconsole.gamelau.uk:9602), dòng đầuHELLO|statuspub|v2|<composite_id>|<secret>|<broker_name>, secret =InpBrokerSecret=BROKER_SECRETcủa broker (fxconsole hiện read-only dưới tênBROKER_STREAM_SECRET). HELLO kiểu cũ (trước v2) bị từ chối. Broker ghi đúng các key củaAccountStatusStorenhưng bỏ qualot_historyvàEquityAlertService, nên HYBRID vẫn gọi HTTP control plane mỗiInpHybridHttpIntervalSec(30 s, đo bằngGetTickCount64từ 1.14 để chạy cả khi đóng thị trường). - Stream theo nhu cầu: ngay sau HELLO broker gửi
stream_on(cadence_ms) nếu đang có trình duyệt subscribe, ngược lạistream_off(hb_interval_secs). Khi off, EA chỉ gửitcp:hb_min(status, balance, equity, tổng lot) mỗihb_interval_secs(EA kẹp 5–120 s, mặc định 15), bỏpositions[],experts[],today_history[]. Khi on, EA gửi payload đầy đủ mỗicadence_ms(mặc định 900; 0 = mỗi tick, giới hạn bởiInpMinPushIntervalMs100). Broker chỉ phátstream_on/stream_offở cạnh 0 ↔ N subscriber. Hai giá trị này đọc theo thứ tự bảngsettings→ Redisapp_config:*→ env. - Dashboard chế độ "Stream (socket)" mở
wss://…/status-ws→:9603, gửi{"action":"subscribe","secret_key"}(chỉ account của key đó) hoặc{"action":"subscribe_all","admin_token"}(=broker_admin_secret, chỉ admin nhận qua Inertia props). Broker gửi snapshot rồi chuyển tiếp mọi push, tự thêm account online sau. Chế độ polling đọc cùng các key Redis qua Laravel. - Báo cáo: user bấm Generate report ở
/account/{id}→ReportServicelưu yêu cầu vàPUBLISH report:request{composite_id, from_ts, to_ts}(kênh không tiền tố) → broker chuyển framereport_requesttới socket của EA → EA bậtg_forceHttpNow→ lần push HTTP kế nhậnpending_report→ gửi theo từng tuần, chunk sau đi ngay tick 1 s kế (báo cáo 3 tháng ≈ 13 chunk ≈ 13 s). EA không có socket thì vẫn thấypending_reportở heartbeat HTTP kế. Schedulerreport:generate-allyêu cầu báo cáo 3 tháng cho mọi account đang hoạt động hằng ngày.
- The EA has three
InpTransportmodes: HTTP (default), TCP, HYBRID. - HTTP:
POST InpApiUrl(/api/account-status) everyInpPostIntervalSec(5 s), Bearer header =InpApiBearer, compared with theea_api_bearersetting (envEA_API_BEARER); a mismatch returns401 Unauthorized bearer token. LaravelAccountStatusStorewrites Redis (48 h TTL), writeslot_history, runsEquityAlertService; the response may carrypending_report. - TCP / HYBRID: TLS to
InpBrokerHost:InpBrokerPort(defaultfxconsole.gamelau.uk:9602), first lineHELLO|statuspub|v2|<composite_id>|<secret>|<broker_name>, secret =InpBrokerSecret= the broker'sBROKER_SECRET(fxconsole shows it read-only asBROKER_STREAM_SECRET). Legacy (pre-v2) HELLOs are rejected. The broker writes the same keys asAccountStatusStorebut skipslot_historyandEquityAlertService, so HYBRID still calls the HTTP control plane everyInpHybridHttpIntervalSec(30 s, timed withGetTickCount64since 1.14 so it also runs while the market is closed). - On-demand stream: right after HELLO the broker sends
stream_on(cadence_ms) if a browser is subscribed, otherwisestream_off(hb_interval_secs). While off, the EA only sendstcp:hb_min(status, balance, equity, lot totals) everyhb_interval_secs(EA clamps to 5–120 s, default 15), skippingpositions[],experts[],today_history[]. While on, it sends the full payload everycadence_ms(default 900; 0 = every tick, floored byInpMinPushIntervalMs100). The broker only firesstream_on/stream_offon 0 ↔ N subscriber edges. Both values are read in the ordersettingstable → Redisapp_config:*→ env. - The dashboard in "Stream (socket)" mode opens
wss://…/status-ws→:9603and sends{"action":"subscribe","secret_key"}(only that key's accounts) or{"action":"subscribe_all","admin_token"}(=broker_admin_secret, which only admins get via Inertia props). The broker sends a snapshot, then forwards every push, auto-including accounts that come online later. Polling mode reads the same Redis keys through Laravel. - Reports: a user clicks Generate report on
/account/{id}→ReportServicestores the request andPUBLISH report:request{composite_id, from_ts, to_ts}(unprefixed channel) → the broker routes areport_requestframe to that EA's socket → the EA setsg_forceHttpNow→ the next HTTP push picks uppending_report→ weekly chunks, each next chunk on the following 1 s tick (a 3-month report ≈ 13 chunks ≈ 13 s). Without a socket the EA still findspending_reporton its next HTTP heartbeat. The scheduler'sreport:generate-allrequests a 3-month report for every active account daily.
flowchart LR
EA["AccountStatusUpdater 1.16"]
API["POST /api/account-status"]
STORE["AccountStatusStore + lot_history + EquityAlertService"]
AB["account-status-broker :9602"]
WS["WS :9603 qua /status-ws"]
R[("Redis account_status:*")]
MY[("MySQL")]
B(["Dashboard trình duyệt"])
RS["ReportService"]
EA -- "HTTP 5 s hoặc HYBRID 30 s" --> API
API --> STORE
STORE --> R
STORE --> MY
EA -- "TLS HELLO v2, hb_min hoặc stream" --> AB
AB --> R
AB -- "stream_on / stream_off" --> EA
B -- "subscribe / subscribe_all" --> WS
WS --- AB
AB -- "snapshot + push" --> B
RS -- "PUBLISH report:request" --> R
R -- "SUBSCRIBE" --> AB
AB -- "report_request" --> EAflowchart LR
EA["AccountStatusUpdater 1.16"]
API["POST /api/account-status"]
STORE["AccountStatusStore + lot_history + EquityAlertService"]
AB["account-status-broker :9602"]
WS["WS :9603 via /status-ws"]
R[("Redis account_status:*")]
MY[("MySQL")]
B(["Dashboard browser"])
RS["ReportService"]
EA -- "HTTP 5 s or HYBRID 30 s" --> API
API --> STORE
STORE --> R
STORE --> MY
EA -- "TLS HELLO v2, hb_min or stream" --> AB
AB --> R
AB -- "stream_on / stream_off" --> EA
B -- "subscribe / subscribe_all" --> WS
WS --- AB
AB -- "snapshot + push" --> B
RS -- "PUBLISH report:request" --> R
R -- "SUBSCRIBE" --> AB
AB -- "report_request" --> EAHTTP là control plane (báo cáo, cảnh báo, lot history); socket là data plane cho dashboard realtime. Chi tiết EA: AccountStatusUpdater. HTTP is the control plane (reports, alerts, lot history); the socket is the data plane for the realtime dashboard. EA details: AccountStatusUpdater.
(d) TickStreamerPub → TickBroker → Redis stream → Charts(d) TickStreamerPub → TickBroker → Redis streams → Charts
TickStreamerPub1.04 mở TLS tớiInpBrokerHost:InpBrokerPort(fxconsole.gamelau.uk:9600), dòng đầuHELLO|tickpub|<account_id>|<secret>|<broker_name>(secret =InpSharedSecret=TICK_BROKER_SECRET; tên broker =InpForexBrokerhoặcACCOUNT_COMPANY). EA pollSymbolInfoTickmỗiInpTickPollMs(100 ms), gửi một dòng JSON cho mỗi tick mới{"type":"tick","s","b","a","t"}, danh sáchsymbols, và NOOP mỗiInpHeartbeatSec(15 s).InpSymbolWhitelistrỗng = mọi symbol trong Market Watch.- Mỗi tick, broker:
XADD ticks:<broker>:<symbol> MAXLEN ~ 100000,SADD brokers,SADD broker:<broker>:symbols,HSET broker_latest:<broker>, rồi broadcast tới client WS đang subscribe cặp (broker, symbol). Các key này không có tiền tố Laravel; Laravel đọc bằng connection Redistickstream(prefix rỗng). - Lịch kinh tế: snapshot đầy đủ mỗi
InpCalendarIntervalMinutes(15 phút; 7 ngày đã qua, 14 ngày tới) và cửa sổ "gần bây giờ" ±3 h đẩy lại mỗi 30 s, gửi thành nhiều frame{"type":"calendar","seq","total","events"}, broker ghép lại và ghicalendar:<broker>+calendar:<broker>:rev. - Backfill OHLC khi kết nối: HTTPS
GET /api/sync-state+POST /api/sync-barstớiInpFxconsoleUrl, BearerInpFxconsoleBearer(=EA_API_BEARER; rỗng = bỏ qua), các TFM1,M5,M15,M30,H1,H4,D1,W1,MN→ bảngohlc_datatrong TimescaleDB. - Scheduler:
ohlc:aggregateđọc stream bằngXRANGE, gộp thành nến M1 trongohlc_data, rồi cắt tick đã xử lý;brokers:syncđồng bộbroker_profiles(MySQL) từ TimescaleDB + Redis;calendar:syncchép snapshot lịch vào bảngeconomic_events. - Trình duyệt ở
/charts/{slug}/{symbol}: lịch sử nến từOhlcApiController(TimescaleDB); giá sống quawss://…/ws→:9601, gửi{"action":"subscribe","broker","symbol"}, nhận tick cuối (từXREVRANGE) rồi các frame{"br","s","b","a","t"}.
TickStreamerPub1.04 opens TLS toInpBrokerHost:InpBrokerPort(fxconsole.gamelau.uk:9600), first lineHELLO|tickpub|<account_id>|<secret>|<broker_name>(secret =InpSharedSecret=TICK_BROKER_SECRET; broker name =InpForexBrokerorACCOUNT_COMPANY). The EA pollsSymbolInfoTickeveryInpTickPollMs(100 ms) and sends one JSON line per fresh tick{"type":"tick","s","b","a","t"}, asymbolslist, and a NOOP everyInpHeartbeatSec(15 s). An emptyInpSymbolWhitelist= every Market Watch symbol.- Per tick, the broker runs
XADD ticks:<broker>:<symbol> MAXLEN ~ 100000,SADD brokers,SADD broker:<broker>:symbols,HSET broker_latest:<broker>, then broadcasts to WS clients subscribed to that (broker, symbol). These keys have no Laravel prefix; Laravel reads them through thetickstreamRedis connection (empty prefix). - Economic calendar: a full snapshot every
InpCalendarIntervalMinutes(15 min; 7 past days, 14 future days) plus a ±3 h "near now" window re-pushed every 30 s, sent as several{"type":"calendar","seq","total","events"}frames that the broker reassembles intocalendar:<broker>+calendar:<broker>:rev. - OHLC backfill on connect: HTTPS
GET /api/sync-state+POST /api/sync-barstoInpFxconsoleUrl, BearerInpFxconsoleBearer(=EA_API_BEARER; empty = skipped), timeframesM1,M5,M15,M30,H1,H4,D1,W1,MN→ theohlc_datatable in TimescaleDB. - Scheduler:
ohlc:aggregatereads the streams withXRANGE, builds M1 bars inohlc_data, then trims processed ticks;brokers:syncsyncsbroker_profiles(MySQL) from TimescaleDB + Redis;calendar:synccopies calendar snapshots into theeconomic_eventstable. - The browser on
/charts/{slug}/{symbol}: bar history fromOhlcApiController(TimescaleDB); live prices overwss://…/ws→:9601, sending{"action":"subscribe","broker","symbol"}, receiving the last tick (fromXREVRANGE) then{"br","s","b","a","t"}frames.
flowchart LR
TSP["TickStreamerPub 1.04"]
TB["tick-broker :9600"]
WS["tick-broker WS :9601"]
R[("Redis ticks:broker:symbol, calendar:broker")]
SYNC["/api/sync-state + /api/sync-bars"]
AGG["scheduler: ohlc:aggregate, brokers:sync, calendar:sync"]
TS[("TimescaleDB ohlc_data")]
MY[("MySQL broker_profiles, economic_events")]
OH["OhlcApiController"]
B(["Browser /charts"])
TSP -- "TLS tick, symbols, calendar" --> TB
TB -- "XADD, SADD, HSET, SET" --> R
TSP -- "HTTPS backfill" --> SYNC
SYNC --> TS
R -- "XRANGE" --> AGG
AGG --> TS
AGG --> MY
TS --> OH
OH --> B
B -- "wss /ws subscribe" --> WS
WS --- TB
TB -- "live tick frames" --> BTick sống đi qua Redis stream; nến lịch sử nằm trong TimescaleDB. Live ticks go through Redis streams; historical bars live in TimescaleDB.
(e) vps-agent: heartbeat, đẩy lệnh, ACK(e) vps-agent: heartbeat, command push, ACK
- Trên VPS:
HeartbeatAgent.exe(1.3.2-phase9) là tiến trình sống lâu; nó giám sátCmdAgent.exevà nói chuyện qua IPC NDJSON trên127.0.0.1:9606(loopback của VPS, không phải cổng 9606 của broker). HeartbeatAgent nối127.0.0.1:9605;stunnelbọc TLS và kết nối tớivps_broker_host:vps_broker_port(fxconsole.gamelau.uk:9605), verify bằng CA lấy từGET /api/agent/broker-config(headerX-Vps-Secret).vps-agent.inichỉ chứaserver_urlvàsecret_key. - Xác thực: frame
hellomangsecret; broker travpses.secret_keytrong MySQL (cache 60 s) →hello_ok(kèmvps_id) hoặchello_err bad_secret. Broker ping mỗiPING_INTERVAL30 s, không pong trongPING_TIMEOUT10 s thì đóng. - Heartbeat: frame
hbmỗiheartbeat_interval. BrokerSETEX laravel-database-vps:agent:{id}:heartbeat15 s (HEARTBEAT_TTL), cập nhậtvpses.account_logins+account_count, và trảhb_okkèmintervalnếuvps:agent:{id}:intervalcó giá trị (Laravel ghi khi admin sửa interval → agent đổi nhịp và ghi lại ini, không restart). Stream theo nhu cầu giống AccountStatusBroker: có admin mở WS thì broker gửistream_on(agent gửi đủ CPU/RAM/disk/net +logins[]), không ai xem thìstream_off(frame "còn sống" tối giản, bỏ WMI); nhịp vẫn làheartbeat_interval. - Đẩy lệnh: admin bấm Deploy / Remove / Upgrade →
INSERT vps_commands→VpsCommand::bootedPUBLISH laravel-database-vps:cmd {vps_id, cmd_id, kind}→ broker gửi{"t":"cmd","cmd_id","kind"}→ HeartbeatAgent chuyểndo_cmd_pullqua IPC → CmdAgentGET /api/vps/cmd/{id}/payloadqua HTTPS (mật khẩu MT5 mã hoá, URL gói… chỉ đi đường này) → thực thi →ackqua IPC → frameeventtới broker. - ACK: broker
UPDATE vps_commandstrực tiếp rồiPOST http://fxconsole-web/api/internal/broker/command-ackvớiX-Broker-Secretđể Laravel chạy toàn bộ chuỗi trạng thái (chuyển trạng tháimt5_accounts, observer →PUBLISH vps:state, email trạng thái deploy, migration stage 2). Thiếu callback này thì deploy "kẹt ở pending". - UI sống:
/vpsvà/deploymentsmởwss://…/vps-ws→:9606, gửi{"action":"subscribe_all","admin_token"}, nhậnvps_hb,vps_cmd,vps_state. - Dự phòng: broker chết hoặc HELLO lỗi → HeartbeatAgent tự chuyển về
POST /api/vps/heartbeat(chuyển trong ~60 s); lệnh đến qua response heartbeat, ACK xếp hàng gửi ở heartbeat HTTPS kế. Vòng dedup 256 phần tử bỏcmd_idtrùng. CmdAgent chết → supervisor spawn lại trong ~5 s; ACK của lệnh đang chạy mất và reaper 5 phút phía server xử lý. - Khác: provisioning không chạm tay qua
POST /api/agent/init-config(MAC + bearerinit_token); tự nâng cấp agent bằng lệnhTYPE_UPGRADE_AGENT→upgrade.ps1tảiGET /api/agent/release/{id}/download, kiểm SHA-256 (X-Release-Hash), giải nén, reboot.
- On the VPS:
HeartbeatAgent.exe(1.3.2-phase9) is the long-lived process; it supervisesCmdAgent.exeand talks to it over NDJSON IPC on127.0.0.1:9606(the VPS loopback, not the broker's port 9606). HeartbeatAgent connects to127.0.0.1:9605;stunnelwraps TLS and connects tovps_broker_host:vps_broker_port(fxconsole.gamelau.uk:9605), verifying with the CA fetched fromGET /api/agent/broker-config(headerX-Vps-Secret).vps-agent.inionly holdsserver_urlandsecret_key. - Auth: the
helloframe carriessecret; the broker looks upvpses.secret_keyin MySQL (60 s cache) →hello_ok(withvps_id) orhello_err bad_secret. The broker pings everyPING_INTERVAL30 s and closes the socket if no pong arrives withinPING_TIMEOUT10 s. - Heartbeat: an
hbframe everyheartbeat_interval. The broker runsSETEX laravel-database-vps:agent:{id}:heartbeat15 s (HEARTBEAT_TTL), updatesvpses.account_logins+account_count, and returnshb_okwithintervalwhenvps:agent:{id}:intervalis set (Laravel writes it when an admin edits the interval → the agent changes cadence and rewrites its ini, no restart). On-demand streaming like AccountStatusBroker: while an admin has the WS open the broker sendsstream_on(the agent sends full CPU/RAM/disk/net +logins[]); with nobody watching,stream_off(a minimal "alive" frame, no WMI); the cadence staysheartbeat_interval. - Command push: an admin clicks Deploy / Remove / Upgrade →
INSERT vps_commands→VpsCommand::bootedPUBLISH laravel-database-vps:cmd {vps_id, cmd_id, kind}→ the broker sends{"t":"cmd","cmd_id","kind"}→ HeartbeatAgent forwardsdo_cmd_pullover IPC → CmdAgent callsGET /api/vps/cmd/{id}/payloadover HTTPS (encrypted MT5 passwords, package URLs… only travel this way) → executes →ackover IPC → aneventframe to the broker. - ACK: the broker runs
UPDATE vps_commandsdirectly, thenPOST http://fxconsole-web/api/internal/broker/command-ackwithX-Broker-Secretso Laravel runs the full state cascade (mt5_accountstransition, observers →PUBLISH vps:state, deploy-status emails, migration stage 2). Without this callback deploys get "stuck at pending". - Live UI:
/vpsand/deploymentsopenwss://…/vps-ws→:9606, send{"action":"subscribe_all","admin_token"}, and receivevps_hb,vps_cmd,vps_state. - Fallback: broker dead or HELLO failed → HeartbeatAgent silently falls back to
POST /api/vps/heartbeat(switchover within ~60 s); commands arrive in the heartbeat response and ACKs queue into the next HTTPS heartbeat. A 256-entry dedup ring drops a repeatedcmd_id. If CmdAgent dies, the supervisor respawns it within ~5 s; the in-flight command's ACK is lost and the server's 5-minute reaper handles it. - Other: zero-touch provisioning via
POST /api/agent/init-config(MAC +init_tokenbearer); agent self-upgrade through aTYPE_UPGRADE_AGENTcommand →upgrade.ps1downloadsGET /api/agent/release/{id}/download, checks SHA-256 (X-Release-Hash), extracts, reboots.
sequenceDiagram
participant ADM as Admin (/vps)
participant APP as fxconsole-web + app
participant R as fxconsole-redis
participant VB as vps-stream-broker :9605
participant HA as HeartbeatAgent + stunnel
participant CA as CmdAgent
HA->>VB: hello secret (TLS qua stunnel)
VB-->>HA: hello_ok vps_id
loop mỗi heartbeat_interval
HA->>VB: hb
VB->>R: SETEX vps:agent:id:heartbeat 15 s
VB-->>HA: hb_ok (+ interval)
end
ADM->>APP: Deploy / Remove / Upgrade
APP->>R: PUBLISH vps:cmd vps_id, cmd_id, kind
R-->>VB: SUBSCRIBE vps:cmd
VB-->>HA: cmd cmd_id, kind
HA->>CA: IPC do_cmd_pull (127.0.0.1:9606)
CA->>APP: GET /api/vps/cmd/id/payload (HTTPS, X-Vps-Secret)
APP-->>CA: payload đã mã hoá
Note over CA: tải gói, giải nén, ghi credential, chạy MT5
CA->>HA: IPC ack
HA->>VB: event cmd_id, status
VB->>APP: POST /api/internal/broker/command-ack (X-Broker-Secret)
APP->>R: PUBLISH vps:state
R-->>VB: SUBSCRIBE vps:state
VB-->>ADM: vps_state qua /vps-wssequenceDiagram
participant ADM as Admin (/vps)
participant APP as fxconsole-web + app
participant R as fxconsole-redis
participant VB as vps-stream-broker :9605
participant HA as HeartbeatAgent + stunnel
participant CA as CmdAgent
HA->>VB: hello secret (TLS via stunnel)
VB-->>HA: hello_ok vps_id
loop every heartbeat_interval
HA->>VB: hb
VB->>R: SETEX vps:agent:id:heartbeat 15 s
VB-->>HA: hb_ok (+ interval)
end
ADM->>APP: Deploy / Remove / Upgrade
APP->>R: PUBLISH vps:cmd vps_id, cmd_id, kind
R-->>VB: SUBSCRIBE vps:cmd
VB-->>HA: cmd cmd_id, kind
HA->>CA: IPC do_cmd_pull (127.0.0.1:9606)
CA->>APP: GET /api/vps/cmd/id/payload (HTTPS, X-Vps-Secret)
APP-->>CA: encrypted payload
Note over CA: download package, unzip, write credentials, launch MT5
CA->>HA: IPC ack
HA->>VB: event cmd_id, status
VB->>APP: POST /api/internal/broker/command-ack (X-Broker-Secret)
APP->>R: PUBLISH vps:state
R-->>VB: SUBSCRIBE vps:state
VB-->>ADM: vps_state over /vps-wsStream là đường chính, HTTPS là dự phòng. Cài đặt và vận hành agent: VPS agent. The stream is the primary path, HTTPS the fallback. Agent install and operations: VPS agent.
(f) TMap: lưu, nạp, khôi phục thảm hoạ(f) TMap: save, load, disaster recovery
- Vì sao cần: GoldenRobber dùng Ticket Map để biết position thuộc round nào (partial close trên account hedging đổi ticket và mất comment gốc). MT5 sập rồi mở ở máy khác thì map mất, logic Tia / SingleRoundMode sai.
- Khoá =
magic+account. Ba nơi lưu: file cục bộMQL5/Files/Common/GR_TicketMap_<magic>.csv(luôn ghi, kể cả backtest); fxconsolePOST {InpCloudMyApiDomain}/api/tmap/save→ MySQLtmap_entries(chỉ live, chỉ leader); Cloudflare Worker/tmap/save→ D1 (tuỳ chọn,InpCloudCfDomaintrống ở production). - Save: theo nhịp
InpCloudSyncIntervalSec(Init mới, DCA add, Tia close, dọn map) và ngay lập tức sauPositionClosePartialhoặc trongOnDeinit. Server làm REPLACE ALL (xoá theomagic+accountrồi insert lại). Từ fxconsole2026.09.04.1, server lưu dấu vân tay sha1 của payload trong Redis (TTL 1 h): payload trùng thì trả"skipped":truevà không đụng DB; Redis lỗi thì vẫn ghi (fail open);clearxoá dấu vân tay. Map về 0 →DELETE /api/tmap/clearở mọi backend. - Load khi OnInit, ưu tiên giảm dần: file cục bộ → Cloudflare → fxconsole →
TicketMapRebuild()dựng lại từ comment của position đang mở (mất dữ liệu partial close cũ nhưng vẫn quản lý được position hiện tại). Follower chỉ đọc cloud, không ghi. - Sự cố binlog: trước bản sửa, EA đẩy lại toàn bộ map mỗi 5–10 s dù không đổi; delete-all + insert-all tạo ~10 lượt ghi/giây (~873k/ngày) và ~3 GB binlog/ngày trên một DB chỉ 179 MB, làm đầy disk MySQL.
- Why: GoldenRobber uses the Ticket Map to know which round a position belongs to (a partial close on a hedging account changes the ticket and loses the original comment). If MT5 crashes and restarts on another machine, the map is gone and Tia / SingleRoundMode logic goes wrong.
- Key =
magic+account. Three stores: the local fileMQL5/Files/Common/GR_TicketMap_<magic>.csv(always written, backtests included); fxconsolePOST {InpCloudMyApiDomain}/api/tmap/save→ MySQLtmap_entries(live only, leader only); Cloudflare Worker/tmap/save→ D1 (optional,InpCloudCfDomainempty in production). - Save: on the
InpCloudSyncIntervalSeccadence (new Init, DCA add, Tia close, map cleanup) and immediately afterPositionClosePartialor inOnDeinit. The server does REPLACE ALL (delete bymagic+account, then insert). Since fxconsole2026.09.04.1the server stores a sha1 fingerprint of the payload in Redis (1 h TTL): an identical payload returns"skipped":truewithout touching the DB; a Redis error still writes (fail open);clearforgets the fingerprint. Map back to 0 →DELETE /api/tmap/clearon every backend. - Load on OnInit, in priority order: local file → Cloudflare → fxconsole →
TicketMapRebuild()from the comments of open positions (older partial-close data is lost but current positions stay managed). Followers read from the cloud but never write. - Binlog incident: before the fix, the EA re-pushed the whole map every 5–10 s even when unchanged; delete-all + insert-all produced ~10 writes/s (~873k/day) and ~3 GB of binlog/day on a 179 MB database, filling the MySQL disk.
flowchart LR
EA["GoldenRobber (leader)"]
F[("File cục bộ GR_TicketMap_magic.csv")]
API["fxconsole /easupported/goldenrobber/api/tmap/save"]
FP[("Redis dấu vân tay sha1, TTL 1 h")]
MY[("MySQL tmap_entries")]
CF["Cloudflare Worker /tmap/save"]
D1[("D1 tmap_entries")]
EA -- "luôn ghi" --> F
EA -- "HTTPS, chỉ live" --> API
API -- "so dấu vân tay" --> FP
API -- "chỉ khi map đổi: REPLACE ALL" --> MY
EA -. "tuỳ chọn" .-> CF
CF -.-> D1
subgraph LOAD["OnInit: thứ tự nạp"]
L1["(1) file cục bộ"] --> L2["(2) Cloudflare"] --> L3["(3) fxconsole"] --> L4["(4) TicketMapRebuild từ comment"]
endflowchart LR
EA["GoldenRobber (leader)"]
F[("Local file GR_TicketMap_magic.csv")]
API["fxconsole /easupported/goldenrobber/api/tmap/save"]
FP[("Redis sha1 fingerprint, TTL 1 h")]
MY[("MySQL tmap_entries")]
CF["Cloudflare Worker /tmap/save"]
D1[("D1 tmap_entries")]
EA -- "always" --> F
EA -- "HTTPS, live only" --> API
API -- "compare fingerprint" --> FP
API -- "only when changed: REPLACE ALL" --> MY
EA -. "optional" .-> CF
CF -.-> D1
subgraph LOAD["OnInit: load order"]
L1["(1) local file"] --> L2["(2) Cloudflare"] --> L3["(3) fxconsole"] --> L4["(4) TicketMapRebuild from comments"]
endKịch bản MT5 sập, Cloudflare sập, cả hai sập: Khôi phục TMap. Scenarios for MT5 down, Cloudflare down, both down: TMap disaster recovery.
(g) Phân tích thị trường qua claude-cli(g) Market analysis via claude-cli
- Kích hoạt: scheduler chạy
analysis:run --scope=globaltheo lịch vàanalysis:run --scope=weeklykhi đến hạn; admin cũng chạy tay từng cặp hoặc tất cả trên/admin/analysis. - Dữ liệu vào: nến từ TimescaleDB qua
OhlcSeries(gộp nến gốc với M1 cho đoạn cuối) →TechnicalLevels; lịch kinh tế từ Rediscalendar:<broker>(connectiontickstream, do TickStreamerPub đẩy); tin tức từNewsService(news:syncchép RSS vàonews_items) và Finnhub nếu có key. - Gọi mô hình:
ClaudeServicecó hai engine. Mặc địnhcli:POST http://claude-cli:8788/analyze {model, system, prompt}→ sidecar chạyclaude -p --output-format jsonbằng subscription OAuth, tối đaANALYZE_CONCURRENCY=1lượt cùng lúc, timeout composeANALYZE_TIMEOUT_MS=900000. Engineapi(gọihttps://api.anthropic.com/v1) chỉ dùng khi settinganalysis_engine=apivà cóclaude_api_key; hiện để nghỉ. - Kết quả:
Analysis::createvào MySQL, hiển thị ở/analysischo mọi user đã đăng nhập, kèm thông báo/email digest (AnalysisReadyNotification,AnalysisDigestMail).GET /healthcủa sidecar trả cờauthed; mất đăng nhập thì/admin/analysishiện banner đỏ và nút Re-login (/relogin/start,/relogin/complete). - Sidecar không publish cổng, chỉ nói trong mạng Docker. Dùng subscription từ backend là vùng xám ToS: giữ ít cặp, nhịp thưa.
- Trigger: the scheduler runs
analysis:run --scope=globalon its schedule andanalysis:run --scope=weeklywhen due; admins can also run one pair or all of them from/admin/analysis. - Inputs: bars from TimescaleDB via
OhlcSeries(merges native bars with M1 for the tail) →TechnicalLevels; the economic calendar from Rediscalendar:<broker>(tickstreamconnection, pushed by TickStreamerPub); news fromNewsService(news:syncmirrors RSS intonews_items) and Finnhub when a key is set. - Model call:
ClaudeServicehas two engines. Defaultcli:POST http://claude-cli:8788/analyze {model, system, prompt}→ the sidecar runsclaude -p --output-format jsonon the OAuth subscription, at mostANALYZE_CONCURRENCY=1call at a time, compose timeoutANALYZE_TIMEOUT_MS=900000. Theapiengine (callshttps://api.anthropic.com/v1) is only used when the settinganalysis_engine=apiand aclaude_api_keyis set; currently dormant. - Output:
Analysis::createinto MySQL, shown on/analysisto every logged-in user, plus notifications/email digest (AnalysisReadyNotification,AnalysisDigestMail). The sidecar'sGET /healthreturns anauthedflag; when the login is lost,/admin/analysisshows a red banner and a Re-login button (/relogin/start,/relogin/complete). - The sidecar publishes no port and only talks inside the Docker network. Driving a subscription from a backend is a ToS grey area: keep few pairs and a relaxed cadence.
flowchart LR
S["scheduler: analysis:run"]
ADM(["Admin /admin/analysis"])
TS[("TimescaleDB ohlc_data")]
RC[("Redis calendar:broker")]
NW["NewsService + Finnhub (tuỳ chọn)"]
CS["ClaudeService engine=cli"]
CC["claude-cli :8788 /analyze"]
OA["Subscription OAuth (volume claude_creds)"]
MY[("MySQL Analysis")]
U(["User /analysis"])
S --> CS
ADM -- "chạy tay" --> CS
TS -- "OhlcSeries, TechnicalLevels" --> CS
RC --> CS
NW --> CS
CS -- "POST model, system, prompt" --> CC
CC -- "claude -p --output-format json" --> OA
CC -- "text" --> CS
CS --> MY
MY --> Uflowchart LR
S["scheduler: analysis:run"]
ADM(["Admin /admin/analysis"])
TS[("TimescaleDB ohlc_data")]
RC[("Redis calendar:broker")]
NW["NewsService + Finnhub (optional)"]
CS["ClaudeService engine=cli"]
CC["claude-cli :8788 /analyze"]
OA["OAuth subscription (volume claude_creds)"]
MY[("MySQL Analysis")]
U(["User /analysis"])
S --> CS
ADM -- "manual run" --> CS
TS -- "OhlcSeries, TechnicalLevels" --> CS
RC --> CS
NW --> CS
CS -- "POST model, system, prompt" --> CC
CC -- "claude -p --output-format json" --> OA
CC -- "text" --> CS
CS --> MY
MY --> UĐăng nhập, sao lưu credential, khoá phiên bản: claude-cli. Login, credential backup, version pinning: claude-cli.
(h) Guacamole: remote desktop trong trình duyệt(h) Guacamole: in-browser remote desktop
- Admin bấm Connect ở
/vps/{id}→POST /admin/vps/{id}/remote-session. Server giải mã mật khẩu VNC của VPS, dựng mô tả kết nối JSON (protocol=vnc), ký HMAC-SHA256 rồi mã hoá AES-128-CBC bằngGUAC_JSON_SECRET_KEY(extension guacamole-auth-json), ghi auditvps_remote_sessions, trả URL<GUAC_PUBLIC_URL>/?data=<token>hết hạn sau 60 s. - Trình duyệt mở tab mới → reverse proxy (TLS ở biên) → host cổng
8282→fxconsole-guacamole(Tomcat,WEBAPP_CONTEXT=ROOT, không cần DB) →guacd:4822qua mạngguac_internal→ VNC tới VPS (TightVNC doprepare-vm.ps1cài). - Vì sao VNC chứ không RDP (tài liệu kế hoạch ban đầu ghi RDP): gắn RDP vào console session làm khoá desktop khi ngắt, phá thao tác GUI tự động của CmdAgent (tiêm URL WebRequest cho MT5 dùng
SendInput). VNC chỉ soi framebuffer, không tạo hay chiếm session. - Kéo thả file đi qua volume
guac_drive(/var/lib/guacamole/drive, ổ ảofxconsole). Schedulerfxconsole:close-stale-remote-sessionsđóng các phiên audit bị bỏ quên.
- An admin clicks Connect on
/vps/{id}→POST /admin/vps/{id}/remote-session. The server decrypts the VPS's VNC password, builds a JSON connection descriptor (protocol=vnc), signs it with HMAC-SHA256 and encrypts it with AES-128-CBC usingGUAC_JSON_SECRET_KEY(guacamole-auth-json extension), writes avps_remote_sessionsaudit row, and returns<GUAC_PUBLIC_URL>/?data=<token>, which expires after 60 s. - The browser opens a new tab → reverse proxy (TLS at the edge) → host port
8282→fxconsole-guacamole(Tomcat,WEBAPP_CONTEXT=ROOT, no DB) →guacd:4822over theguac_internalnetwork → VNC to the VPS (TightVNC installed byprepare-vm.ps1). - Why VNC, not RDP (the original plan document says RDP): attaching RDP to the console session locks the desktop on disconnect, which breaks CmdAgent's GUI automation (MT5 WebRequest URL injection uses
SendInput). VNC mirrors the framebuffer without creating or taking over a session. - File drag-and-drop goes through the
guac_drivevolume (/var/lib/guacamole/drive, virtual drivefxconsole). The scheduler'sfxconsole:close-stale-remote-sessionscloses abandoned audit sessions.
sequenceDiagram participant B as Trình duyệt admin participant APP as fxconsole-web + app participant DB as MySQL participant P as Reverse proxy (TLS) participant G as fxconsole-guacamole :8282 participant D as fxconsole-guacd :4822 participant V as VPS TightVNC B->>APP: POST /admin/vps/id/remote-session APP->>DB: giải mã mật khẩu VNC, ghi vps_remote_sessions APP-->>B: url GUAC_PUBLIC_URL/?data=token, hết hạn 60 s B->>P: GET /?data=token P->>G: HTTP G->>D: guac_internal D->>V: VNC
sequenceDiagram participant B as Admin browser participant APP as fxconsole-web + app participant DB as MySQL participant P as Reverse proxy (TLS) participant G as fxconsole-guacamole :8282 participant D as fxconsole-guacd :4822 participant V as VPS TightVNC B->>APP: POST /admin/vps/id/remote-session APP->>DB: decrypt VNC password, write vps_remote_sessions APP-->>B: url GUAC_PUBLIC_URL/?data=token, expires in 60 s B->>P: GET /?data=token P->>G: HTTP G->>D: guac_internal D->>V: VNC
Cấu hình proxy và bảo mật: Guacamole. Proxy setup and security: Guacamole.
Bảng cổngPorts
| CổngPort | Giao thứcProtocol | Service | Công khai?Public? | Ai kết nốiWho connects |
|---|---|---|---|---|
| 443 | HTTPS | openresty (aaPanel) | YES | Trình duyệt, WebRequest của mọi EA, HTTPS dự phòng của vps-agentBrowsers, every EA's WebRequest, vps-agent HTTPS fallback |
| 8080 → 80 | HTTP | fxconsole-web | NO | openresty; broker gọi http://fxconsole-web cổng 80 trong mạng Dockeropenresty; brokers call http://fxconsole-web on port 80 inside the Docker network |
| 9000 | FastCGI | fxconsole-app | NO | fxconsole-web |
| 6379 | Redis | fxconsole-redis | NO | app, scheduler, bốn brokerapp, scheduler, the four brokers |
| 5432 | PostgreSQL | fxconsole-timescale | MUST NOT | app, scheduler; máy LAN (publish mọi interface, phải chặn ở firewall)app, scheduler; LAN machines (published on all interfaces, must be firewalled) |
| 8788 | HTTP | fxconsole-claude-cli | NO | app, scheduler |
| 8282 → 8080 | HTTP | fxconsole-guacamole | PROXY ONLY | Reverse proxy của domain GuacamoleThe Guacamole domain's reverse proxy |
| 4822 | Guacamole | fxconsole-guacd | NO | fxconsole-guacamole (mạng guac_internal)fxconsole-guacamole (network guac_internal) |
| 9600 | TLS TCP | fxconsole-tick-broker | YES | TickStreamerPub |
| 9601 | WSS | fxconsole-tick-broker | NO | fxconsole-web qua /ws (compose vẫn publish lên host)fxconsole-web via /ws (compose still publishes it on the host) |
| 9602 | TLS TCP | fxconsole-account-status-broker | YES | AccountStatusUpdater (TCP / HYBRID)AccountStatusUpdater (TCP / HYBRID) |
| 9603 | WSS | fxconsole-account-status-broker | NO | fxconsole-web qua /status-ws (compose vẫn publish lên host)fxconsole-web via /status-ws (compose still publishes it on the host) |
| 9605 | TLS TCP | fxconsole-vps-stream-broker | YES | stunnel trên VPSstunnel on the VPSes |
| 9606 | WSS | fxconsole-vps-stream-broker | NO | fxconsole-web qua /vps-ws (chỉ expose)fxconsole-web via /vps-ws (expose only) |
| 9607 | TLS TCP | fxconsole-ea-link-broker | YES | GoldenRobber 1.42+ |
| 3217 | MySQL | MySQL trên host aaPanelMySQL on the aaPanel host | host từ xaremote host | app, scheduler, AccountStatus/VpsStream/EaLink broker (đi ra từ Docker host)app, scheduler, AccountStatus/VpsStream/EaLink brokers (outbound from the Docker host) |
| 51820/udp | WireGuard | wg-easy trong CT WireGuard của từng server VPS (port-forward trên một OpenWRT)wg-easy in each VPS server's WireGuard CT (port-forwarded on one OpenWRT) | VPN | Server fxconsole và client VPN của admin (cổng mặc định của wg-easy)The fxconsole server and admin VPN clients (wg-easy default port) |
| 127.0.0.1:9605 | TCP | stunnel trên VPS (accept)stunnel on the VPS (accept) | LOCAL | HeartbeatAgent |
| 127.0.0.1:9606 | TCP NDJSON | IPC của HeartbeatAgent trên VPSHeartbeatAgent IPC on the VPS | LOCAL | CmdAgent |
vnc_port | VNC | TightVNC trên VPSTightVNC on the VPS | theo VPSper VPS | fxconsole-guacd |
Key và kênh RedisRedis keys and channels
Laravel dùng tiền tố laravel-database- (REDIS_PREFIX của broker phải khớp). Nhóm dữ liệu tick dùng connection tickstream không tiền tố. Redis không persistence: mất hết khi container restart, mọi thứ dưới đây đều tự tái tạo.
Laravel uses the laravel-database- prefix (the brokers' REDIS_PREFIX must match). The tick data group uses the tickstream connection with no prefix. Redis has no persistence: everything is lost on container restart, and everything below regenerates itself.
| Key / kênhKey / channel | KiểuType | GhiWriter | ĐọcReader | TTL |
|---|---|---|---|---|
laravel-database-account_status:{account_id} | string JSON | AccountStatusStore, AccountStatusBroker | Dashboard, snapshot của brokerDashboard, broker snapshot | 48 h |
…account_positions:{id}, …account_experts:{id}, …account_today_history:{id} | string JSON | như trênsame | Trang accountAccount pages | 48 h |
…account_status_keys:{secret_key}, …account_status_all_secret_keys | set | như trênsame | Liệt kê account theo key / mọi keyAccount listing per key / all keys | — |
…app_config:broker_admin_secret | string | Laravel (mỗi lần admin tải trang / lưu settings)Laravel (every admin page load / settings save) | AccountStatusBroker, VpsStreamBroker | — |
…app_config:broker_internal_secret | string | Laravel | VpsStreamBroker, EaLinkBroker | — |
…app_config:hb_interval_secs, …app_config:stream_cadence_ms | string | Laravel | AccountStatusBroker | — |
…vps:agent:{id}:heartbeat | string JSON | VpsStreamBroker | Laravel VpsController (dự phòng cột DB)Laravel VpsController (falls back to DB columns) | 15 s |
…vps:agent:{id}:interval | string | Laravel Vps::updatedLaravel Vps::updated | VpsStreamBroker (gửi trong hb_ok)VpsStreamBroker (sent in hb_ok) | — |
…ea_link:presence:{slug}:{composite_id}:{magic} | hash {node_id}#{inst} → unix | EaLinkBroker | Laravel (badge Link khi < 60 s)Laravel (Link badge when < 60 s) | 90 s |
| Dấu vân tay TMap (sha1 payload)TMap fingerprint (payload sha1) | string | Controller TMapTMap controllers | Controller TMap (bỏ ghi trùng)TMap controllers (skip identical writes) | 1 h |
kênhchannel laravel-database-vps:cmd | pub/sub | VpsCommand created | VpsStreamBroker | — |
kênhchannel laravel-database-vps:state | pub/sub | Observer Vps, Mt5Account, VpsCommandVps, Mt5Account, VpsCommand observers | VpsStreamBroker → WS vps_stateVpsStreamBroker → WS vps_state | — |
kênhchannel laravel-database-ea:link:cfg | pub/sub | EaLinkNotifier | EaLinkBroker (PSUBSCRIBE *ea:link:cfg, khớp cả tên có và không tiền tố)EaLinkBroker (PSUBSCRIBE *ea:link:cfg, matches prefixed and bare names) | — |
kênhchannel report:request no prefix | pub/sub | ReportService | AccountStatusBroker | — |
ticks:{broker}:{symbol} no prefix | stream, MAXLEN ~ 100000 | TickBroker | ohlc:aggregate (rồi cắt), tick cuối cho WSohlc:aggregate (then trims), last tick for WS | — |
brokers, broker:{broker}:symbols, broker_latest:{broker} no prefix | set, set, hash | TickBroker | ohlc:aggregate, brokers:sync, Charts | — |
calendar:{broker}, calendar:{broker}:rev no prefix | string JSON | TickBroker | calendar:sync, analysis:run | — |
Kho dữ liệu và volumeData stores and volumes
MySQL trade_copier
Host aaPanel từ xa. Bảng chính: users, settings, easupported, ea_account, ea_config, tmap_entries, tmap_nodes, vpses, vps_commands, mt5_accounts, lot_history, report_deals, report_summaries, broker_profiles, economic_events, news_items, vps_remote_sessions, fx_rates, bảng phân tích (Analysis).
Remote aaPanel host. Main tables: users, settings, easupported, ea_account, ea_config, tmap_entries, tmap_nodes, vpses, vps_commands, mt5_accounts, lot_history, report_deals, report_summaries, broker_profiles, economic_events, news_items, vps_remote_sessions, fx_rates, the analysis table (Analysis).
TimescaleDB
Chỉ ohlc_data (truy vấn qua DB::connection('timescale')). Volume timescale_data. Tuning: shared_buffers=4GB, effective_cache_size=8GB; đổi shared_buffers phải down/up service.
Only ohlc_data (queried through DB::connection('timescale')). Volume timescale_data. Tuning: shared_buffers=4GB, effective_cache_size=8GB; changing shared_buffers needs a service down/up.
Redis
Không persistence, allkeys-lru. Đổi tốc độ ghi lấy độ bền: trạng thái account được EA tái tạo sau vài giây. Hệ quả: secret mirror trong Redis mất khi restart, nên từ commit cb272d5 broker đọc bảng settings trước.
No persistence, allkeys-lru. Trades durability for write speed: account state is regenerated by the EAs within seconds. Consequence: the secret mirrors vanish on restart, so since commit cb272d5 brokers read the settings table first.
Named volume của stack fxconsole: vendor, node_modules, public_build, timescale_data, guac_drive, claude_creds, claude_config. Cert của broker nằm trên bind mount monitor/<Broker>/certs.
fxconsole stack named volumes: vendor, node_modules, public_build, timescale_data, guac_drive, claude_creds, claude_config. Broker certs live on the bind mount monitor/<Broker>/certs.
| Giá trịValue | Broker đọcRead by | Thứ tự đọc (cache 30 s)Read order (30 s cache) |
|---|---|---|
broker_internal_secret | EaLinkBroker, VpsStreamBroker | settings → app_config:broker_internal_secret → BROKER_INTERNAL_SECRET |
broker_admin_secret | AccountStatusBroker, VpsStreamBroker | settings → app_config:broker_admin_secret → BROKER_ADMIN_SECRET |
hb_interval_secs, stream_cadence_ms | AccountStatusBroker | settings → app_config:* → HB_INTERVAL_SECS / STREAM_CADENCE_MS |
DB lỗi hoặc chậm quá 2 s thì rơi xuống Redis/env. DB_HOST rỗng = bỏ qua DB (AccountStatusBroker, EaLinkBroker); VpsStreamBroker luôn cần DB.
A DB error or a query slower than 2 s falls back to Redis/env. An empty DB_HOST skips the DB (AccountStatusBroker, EaLinkBroker); VpsStreamBroker always needs the DB.
Secret nằm ở đâuWhere secrets live
Bảng này chỉ nói secret được lưu ở đâu và phải khớp với gì. Giá trị thật chỉ nằm trong .env trên server, DB và terminal MT5.
This table only says where each secret is stored and what it must match. Real values live only in server .env files, the database and MT5 terminals.
| Secret | Lưu ởStored in | Dùng choUsed for | Phải khớp vớiMust match |
|---|---|---|---|
easupported.secret_token | MySQL, mỗi EA product (tạo / Regenerate trên /admin/easupported)MySQL, per EA product (created / Regenerate on /admin/easupported) | Bearer của /easupported/{slug}/api/tmap/*; sha256 của nó là link key EA LinkBearer for /easupported/{slug}/api/tmap/*; its sha256 is the EA Link key | InpCloudBearerToken trên mọi terminalon every terminal |
settings.broker_internal_secret | MySQL ("EALinkBroker Secret Key"), mirror Redis, env BROKER_INTERNAL_SECRET ở fxconsole và brokerMySQL ("EALinkBroker Secret Key"), Redis mirror, env BROKER_INTERNAL_SECRET in fxconsole and brokers | Header X-Broker-Secret của /api/internal/* (fail-closed khi chưa cấu hình)X-Broker-Secret header on /api/internal/* (fails closed when unset) | EaLinkBroker, VpsStreamBroker |
settings.broker_admin_secret | MySQL, mirror Redis, env BROKER_ADMIN_SECRETMySQL, Redis mirror, env BROKER_ADMIN_SECRET | subscribe_all WS firehose; chỉ admin nhận qua Inertia propssubscribe_all WS firehose; only admins receive it via Inertia props | AccountStatusBroker, VpsStreamBroker |
BROKER_SECRET | monitor/AccountStatusBroker/.env | HELLO statuspubstatuspub HELLO | InpBrokerSecret (AccountStatusUpdater), BROKER_STREAM_SECRET (fxconsole, hiện read-only)InpBrokerSecret (AccountStatusUpdater), BROKER_STREAM_SECRET (fxconsole, shown read-only) |
TICK_BROKER_SECRET | monitor/TickBroker/.env | HELLO tickpubtickpub HELLO | InpSharedSecret (TickStreamerPub) |
EA_API_BEARER / settings.ea_api_bearer | monitor/fxconsole/.env, DB ghi đèmonitor/fxconsole/.env, DB overrides | /api/account-status, /api/sync-state, /api/sync-bars | InpApiBearer, InpFxconsoleBearer |
TMAP_SECRET_KEY | monitor/fxconsole/.env, secret của Cloudflare Workermonitor/fxconsole/.env, Cloudflare Worker secret | URL TMap cũ /api/tmap/*, /tmap/* trên workerLegacy /api/tmap/*, worker /tmap/* | InpCloudBearerToken khi EA dùng URL cũInpCloudBearerToken when the EA uses the legacy URL |
vpses.secret_key | MySQL, mỗi VPSMySQL, per VPS | Header X-Vps-Secret, HELLO tới VpsStreamBroker, upgrade.ps1X-Vps-Secret header, VpsStreamBroker HELLO, upgrade.ps1 | secret_key trongin vps-agent.ini |
VPS_INIT_BOOTSTRAP_TOKEN / settings.vps_init_bootstrap_token | .env fxconsole, DB ghi đèfxconsole .env, DB overrides | POST /api/agent/init-config | init_token trongin vps-agent.ini.init-template |
GUAC_JSON_SECRET_KEY | .env fxconsole (setting guac_json_secret_key)fxconsole .env (setting guac_json_secret_key) | Ký + mã hoá token ?data=Signs + encrypts the ?data= token | JSON_SECRET_KEY củaof fxconsole-guacamole |
APP_KEY | monitor/fxconsole/.env | Mã hoá cột DB (vpses.vnc_password, mt5_accounts.password_encrypted…), cookie phiênEncrypts DB columns (vpses.vnc_password, mt5_accounts.password_encrypted…), session cookies | Không đổi, không bao giờNever changes |
DB_PASSWORD, TIMESCALE_PASSWORD, MAIL_PASSWORD | .env fxconsole (DB_* được copy sang .env broker)fxconsole .env (DB_* is copied into broker .env files) | MySQL, TimescaleDB, SMTP MailjetMySQL, TimescaleDB, Mailjet SMTP | — |
| OAuth ClaudeClaude OAuth | Volume claude_creds, claude_config (bản sao lưu cũng là secret)Volumes claude_creds, claude_config (backups are secrets too) | Sidecar claude-cliclaude-cli sidecar | — |
claude_api_key, key FinnhubFinnhub key | DB, mã hoá (quản lý trên trang admin)DB, encrypted (managed from admin pages) | Engine api (đang nghỉ), tin tức cho phân tíchapi engine (dormant), news for analysis | — |
user.mt5_secret_key | MySQL, mỗi userMySQL, per user | Được tiêm vào .set khi deploy góiInjected into the .set during package deploys | InpAccountSecretKey (AccountStatusUpdater) |
Bố cục repoRepo layout
| Đường dẫnPath | Thành phầnComponent |
|---|---|
GoldenRobber-1.42.* · archive/ | Gốc chỉ giữ bản production GoldenRobber-1.42.mq5 / .ex5 (b3). Mọi bản cũ nằm trong archive/: 1.41.4-b21.1 (hotfix), 1.41.4 (b28, dòng test), 1.41.5 / 1.41.3 / 1.41.2 / 1.41.1 / 1.41 / 1.39 và GoldenRobber.mq5. Production cũ b21 chỉ còn trong git (71dac5f).The root keeps only the production build GoldenRobber-1.42.mq5 / .ex5 (b3). Every older build sits in archive/: 1.41.4-b21.1 (hotfix), 1.41.4 (b28, test line), 1.41.5 / 1.41.3 / 1.41.2 / 1.41.1 / 1.41 / 1.39 and GoldenRobber.mq5. The previous production b21 lives only in git (71dac5f). |
Include/ | Include MQL5 dùng chung, gồm EaLink.mqhShared MQL5 includes, including EaLink.mqh |
docs/ | Tài liệu nguồn: BACKTEST_COMPARISON.md, STRATEGY_SUMMARY.md, CAPITAL_GUIDE.*, WHIPSAW_DEFENSE.md, CHAIN_PERSISTENT.md, DCA_TUNING_GUIDE.md, TMAP_DISASTER_RECOVERY_GUIDE.md, ghi chú 1.41.1 và bản StrategyExplained (html/pdf) — site README/ dựng từ đâySource documents: BACKTEST_COMPARISON.md, STRATEGY_SUMMARY.md, CAPITAL_GUIDE.*, WHIPSAW_DEFENSE.md, CHAIN_PERSISTENT.md, DCA_TUNING_GUIDE.md, TMAP_DISASTER_RECOVERY_GUIDE.md, the 1.41.1 notes and the StrategyExplained exports — the README/ site is built from these |
tools/ | Script tiện ích chỉnh .set: optimize_test_set.py, update_set_partial_log.pyHelper scripts for .set files: optimize_test_set.py, update_set_partial_log.py |
*.set, archive/configs/, DEPLOY/, README/configs/ | Gốc chỉ giữ config production v1.42.b7-pass58.set; config cũ nằm trong archive/configs/; DEPLOY/ chứa các config H4-origin cũ; bản chia sẻ được (token trống) nằm ở README/configs/EA config files; DEPLOY/ holds older H4-origin configs; shareable copies (token blank) are in README/configs/ |
RELEASES.md, EA-HOTRELOAD-STANDARD.md | Trạng thái build; chuẩn tích hợp EA ↔ fxconsoleBuild status; EA ↔ fxconsole integration standard |
deploy.sh | Script deploy serverServer deploy script |
monitor/fxconsole/ | Stack Laravel + compose, docker/nginx/, docker/nginx-guac/Laravel stack + compose, docker/nginx/, docker/nginx-guac/ |
monitor/TickBroker/, monitor/AccountStatusBroker/, monitor/VpsStreamBroker/, monitor/EaLinkBroker/ | Bốn broker Node.js (mỗi thư mục có compose, Dockerfile, gen-cert.js)The four Node.js brokers (each with compose, Dockerfile, gen-cert.js) |
monitor/ea-account-status-updater/ | AccountStatusUpdater EA + .set |
monitor/TickStreamer/pub/ | TickStreamerPub EA |
monitor/vps-agent/ | HeartbeatAgent.au3, CmdAgent.au3, lib/, prepare-vm.ps1, bootstrap-fresh-vm.ps1, upgrade.ps1, gói setup/upgrade agent và gói MT5 dạng zipHeartbeatAgent.au3, CmdAgent.au3, lib/, prepare-vm.ps1, bootstrap-fresh-vm.ps1, upgrade.ps1, agent setup/upgrade zips and MT5 package zips |
monitor/claude-cli/ | Sidecar phân tích (server.js, Dockerfile)Analysis sidecar (server.js, Dockerfile) |
monitor/README.html, monitor/GUACAMOLE_FEATURE_PLAN.md | Tài liệu cũ của monitor/TMap; kế hoạch GuacamoleOlder monitor/TMap docs; Guacamole plan |
monitor/TradeCopier/ | Không mô tả trên trang nàyNot covered on this page |
cloudflare-tmap-worker/ | Worker + D1 (tuỳ chọn)Worker + D1 (optional) |
backtest/, *.md chiến lượcstrategy | Dữ liệu backtest và tài liệu chiến lượcBacktest data and strategy docs |
README/ | Bộ tài liệu nàyThese docs |
Đọc tiếpRead next
- Hướng dẫn deploy: dựng, cập nhật, kiểm tra, rollback.
- Các broker realtime, fxconsole, VPS agent.
- Chuẩn tích hợp EA, Build & release, Release note 1.42 b2.
- Deployment guide: set up, update, verify, roll back.
- Realtime brokers, fxconsole, VPS agent.
- EA integration standard, Builds & releases, Release note 1.42 b2.