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.

  • Cập nhậtUpdated 29/09/2026
  • NguồnSource monitor/fxconsole/docker-compose.yml
  • monitor/fxconsole/docker/nginx/default.conf
  • monitor/*Broker/broker.js
  • EA-HOTRELOAD-STANDARD.md
  • monitor/vps-agent/README.html

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át AccountStatusUpdater, EA phát tick TickStreamerPub, 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 Docker fxconsole_default.
  • MySQL từ xa trên host aaPanel (database trade_copier): nguồn chân lý cho user, account, config EA, TMap, bảng settings.
  • 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 EA AccountStatusUpdater, the tick publisher TickStreamerPub, and vps-agent (HeartbeatAgent + CmdAgent + stunnel) managing the machine.
  • Docker host (OVH Dedicated Server 01) running the fxconsole stack (Laravel + React, nginx, Redis, TimescaleDB, scheduler, claude-cli, Guacamole) and four realtime Node.js brokers (Tick, AccountStatus, VpsStream, EaLink), all on the Docker network fxconsole_default.
  • Remote MySQL on the aaPanel host (database trade_copier): source of truth for users, accounts, EA config, TMap and the settings table.
  • 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" --> VNC

Toà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" --> VNC

The 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 01fxconsole 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 … NChứ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.
Sơ đồ hạ tầng: server fxconsole là WireGuard client của CT WireGuard trên từng server chứa VPS; mỗi LAN OpenWRT có IP WAN riêng Tunnel WireGuard UDP 51820 · VNC đi bên trong VNC tới lan_ip:5900 Nối mạng · NAT OVH Dedicated Server 01 fxconsole · Docker host fxconsole + 4 broker Guacamole + guacd WireGuard client 1 tunnel mỗi server VPS Mỗi LAN ra Internet, tới broker MT5, bằng IP WAN của OpenWRT riêng. EA và vps-agent gọi fxconsole qua Internet công khai, không qua VPN. OVH Dedicated Server 02 Proxmox · VPS chạy tài khoản MT5 IP WAN 1 · IP chính OpenWRT A NAT · fwd 51820 → CT LAN 192.168.101.0/24 VPS Windows · MT5 VPS Windows · MT5 VPS Windows · MT5 eth0 IP WAN 2 · IP phụ OpenWRT B NAT ra IP WAN 2 LAN 192.168.102.0/24 VPS Windows · MT5 VPS Windows · MT5 VPS Windows · MT5 eth1 IP WAN 3 · IP phụ OpenWRT C NAT ra IP WAN 3 LAN 192.168.103.0/24 VPS Windows · MT5 VPS Windows · MT5 VPS Windows · MT5 eth2 CT WireGuard (wg-easy) một NIC trên mỗi LAN · MASQUERADE lưu lượng client ra LAN đích VNC :5900 OVH Dedicated Server 03 … N cùng cấu trúc với Server 02 IP WAN × k OpenWRT × k LAN × k VPS · MT5 CT WireGuard

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.

Infrastructure diagram: the fxconsole server is a WireGuard client of the WireGuard CT on every VPS server; each OpenWRT LAN has its own WAN IP WireGuard tunnel UDP 51820 · VNC travels inside VNC to lan_ip:5900 Network link · NAT OVH Dedicated Server 01 fxconsole · Docker host fxconsole + 4 brokers Guacamole + guacd WireGuard client 1 tunnel per VPS server Each LAN reaches the Internet and its MT5 broker via its own WAN IP. EAs and vps-agent call fxconsole over the public Internet, not the VPN. OVH Dedicated Server 02 Proxmox · VPSes running MT5 accounts WAN IP 1 · main IP OpenWRT A NAT · fwd 51820 → CT LAN 192.168.101.0/24 Windows VPS · MT5 Windows VPS · MT5 Windows VPS · MT5 eth0 WAN IP 2 · additional OpenWRT B NAT via WAN IP 2 LAN 192.168.102.0/24 Windows VPS · MT5 Windows VPS · MT5 Windows VPS · MT5 eth1 WAN IP 3 · additional OpenWRT C NAT via WAN IP 3 LAN 192.168.103.0/24 Windows VPS · MT5 Windows VPS · MT5 Windows VPS · MT5 eth2 WireGuard CT (wg-easy) one NIC on every LAN · MASQUERADEs client traffic into the target LAN VNC :5900 OVH Dedicated Server 03 … N same layout as Server 02 WAN IP × k OpenWRT × k LAN × k VPS · MT5 WireGuard CT

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 51820 vào CT. Trên node fxbot (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 CT 192.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:5900 mà 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 .211 trong 192.168.101/102/103, chỉ NIC đầu có gateway), client 10.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 51820 to the CT. On node fxbot (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 at 192.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/0 by 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:5900 that 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 fxbot example: CT 107, 3 NICs vmbr101/102/103 (IP .211 in 192.168.101/102/103, only the first NIC has a gateway), clients 10.8.2.0/24. After every client create or delete, restart Docker inside the CT.
Khi thêm server hoặc LAN mớiWhen adding a server or a LAN
  • 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-webDocker host, nginx, cổng host 8080 → 80Docker host, nginx, host port 8080 → 80Phụ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-appDocker 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-schedulerDocker hostphp 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-redisDocker host, 6379 nội bộDocker host, 6379 internalRedis 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-timescaleDocker host, 5432 publish mọi interfaceDocker host, 5432 published on all interfacesTimescaleDB (pg16), chỉ cho bảng ohlc_data. Volume timescale_data.TimescaleDB (pg16), only for the ohlc_data table. Volume timescale_data.
fxconsole-claude-cliDocker host, 8788 nội bộDocker host, 8788 internalSidecar 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-guacdDocker host, 8282 → 8080; guacd 4822 trong mạng guac_internalDocker host, 8282 → 8080; guacd 4822 on network guac_internalRemote 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-brokerDocker host, 9600 TLS, 9601 WSSDocker host, 9600 TLS, 9601 WSSNhậ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-brokerDocker host, 9602 TLS, 9603 WSSDocker host, 9602 TLS, 9603 WSSNhậ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-brokerDocker 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-brokerDocker host, 9607 TLSDocker host, 9607 TLSKê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 EATerminal 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 EATerminal 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 EATerminal 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-agentMỗ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-workerCloudflare 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_copierHost 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-web cổng 8080. Laravel đứng sau proxy nên cần APP_URL dạng https và TRUSTED_PROXIES để tin header X-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-ws với chứng chỉ thật của openresty; nginx trong fxconsole-web proxy tiếp tới fxconsole-tick-broker:9601, fxconsole-account-status-broker:9603, fxconsole-vps-stream-broker:9606 với proxy_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 name fxconsole). 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 được fxconsole-redis và fxconsole-web. Guacamole và guacd dùng thêm mạng riêng guac_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-web on port 8080. Laravel sits behind a proxy, so it needs an https APP_URL and TRUSTED_PROXIES so it trusts the X-Forwarded-Proto header.
  • 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-ws with openresty's real certificate; nginx inside fxconsole-web proxies on to fxconsole-tick-broker:9601, fxconsole-account-status-broker:9603, fxconsole-vps-stream-broker:9606 with proxy_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 name fxconsole). The four broker stacks declare it external, so fxconsole must be up first for the brokers to resolve fxconsole-redis and fxconsole-web. Guacamole and guacd also use a private network guac_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.

Cổng không được lộ ra ngoàiPorts that must not be exposed

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ằng GetTickCount64(), mỗi InpCloudSyncIntervalSec giâ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ức https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap/heartbeat. Header Authorization: Bearer <InpCloudBearerToken>, so bằng hash_equals với easupported.secret_token của product: sai token → 401, slug không tồn tại → 404 Unknown EA. WebRequest trả -1 nghĩ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ào tmap_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ó trong hot_keys thì EA quay về giá trị input. Production b21 hot-reload 14 key.
  • Leader election: leader = node còn sống có priority nhỏ nhất (hoà thì node_id theo alphabet); còn sống = heartbeat trong timeout_sec (InpCloudHeartbeatTimeoutSec = 90). Chỉ leader mở/đóng lệnh và save/clear TMap. 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 with GetTickCount64(), every InpCloudSyncIntervalSec seconds (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. Header Authorization: Bearer <InpCloudBearerToken>, compared with hash_equals against the product's easupported.secret_token: wrong token → 401, unknown slug → 404 Unknown EA. WebRequest returning -1 means 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) and payload (the whole Info Panel).
  • The server stores the account in ea_account (unique (ea_id, composite_id, magic)) and the node in tmap_nodes, then returns is_leader, current_leader_id, alive_count, alive_nodes, hot_keys (CSV of overridden keys) and the override values from ea_config. A key absent from hot_keys reverts to its input value. Production b21 hot-reloads 14 keys.
  • Leader election: leader = the alive node with the lowest priority (ties broken by node_id alphabetically); alive = heartbeat within timeout_sec (InpCloudHeartbeatTimeoutSec = 90). Only the leader opens/closes orders and runs TMap save/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
  end
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, 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
  end

Heartbeat 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.

  • Từ GoldenRobber 1.42 (Include/EaLink.mqh, lớp CEaLink), mỗi chart giữ một kết nối TLS TCP tới InpLinkHost: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 khi InpCloudSyncEnable, có slug trong InpCloudMyApiDomain và 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ận cfg rồ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ả hello trong 5 s (HELLO_GRACE_MS) kèm chữ ký. Broker lấy link key của slug bằng POST http://fxconsole-web/api/internal/ea-link/key với header X-Broker-Secret; Laravel trả sha256(secret_token), không bao giờ trả token thô. 200 được cache KEY_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_sec 20), broker ghi presence laravel-database-ea_link:presence:{slug}:{composite_id}:{magic}, field {node_id}#{inst} = unix giây, EXPIRE 90. EA gửi hb mỗ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 → EaLinkNotifier PUBLISH ea:link:cfg (Redis facade thêm tiền tố thành laravel-database-ea:link:cfg) payload {"slug","targets":[{"composite_id","magic"}],"rev":<ms>} → broker PSUBSCRIBE *ea:link:cfg → frame {"t":"cfg","rev"} tới mọi socket trong topic ea/{slug}/{composite_id}/{magic} → EA gom tín hiệu, kéo heartbeat tối đa 1 lần/giây → áp config → gửi cfg_ack. Độ trễ ≈ ≤ 1 s. Broker log cfg rev=… slug=… targets=N delivered=M.
  • Kết nối lại cùng {node_id}#{inst} thay socket cũ (bye superseded); khác inst (nhiều chart, leader/follower) thì cùng tồn tại và đều nhận cfg. EA backoff 2 → 60 s ±20% jitter; bad_sig/unknown_ea chờ 5 phút. Broker chết thì panel báo Link: DOWN và EA vẫn chạy như b21.1 (HTTP theo chu kỳ).
  • From GoldenRobber 1.42 (Include/EaLink.mqh, class CEaLink), each chart keeps one TLS TCP connection to InpLinkHost:InpLinkPort (stable config: fxconsole.gamelau.uk:9607, InpLinkMode=1 = HYBRID). NDJSON protocol, 8 KiB max per line. Link is enabled only with InpCloudSyncEnable, a slug in InpCloudMyApiDomain and an InpCloudBearerToken; 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; on cfg the 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 answers hello within 5 s (HELLO_GRACE_MS) with a signature. The broker fetches the slug's link key with POST http://fxconsole-web/api/internal/ea-link/key and header X-Broker-Secret; Laravel returns sha256(secret_token), never the raw token. 200 is cached for KEY_CACHE_SEC (60 s), 404 → unknown_ea (negative cache 10 s), anything else → auth_unavailable (not cached).
  • On success → hello_ok (hb_sec 20); the broker writes presence laravel-database-ea_link:presence:{slug}:{composite_id}:{magic}, field {node_id}#{inst} = unix seconds, EXPIRE 90. The EA sends hb every 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 → EaLinkNotifier PUBLISH ea:link:cfg (the Redis facade prefixes it to laravel-database-ea:link:cfg) with payload {"slug","targets":[{"composite_id","magic"}],"rev":<ms>} → broker PSUBSCRIBE *ea:link:cfg → frame {"t":"cfg","rev"} to every socket in topic ea/{slug}/{composite_id}/{magic} → the EA coalesces signals and pulls the heartbeat at most once per second → applies config → sends cfg_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 different inst (several charts, leader/follower) coexists and every one receives cfg. The EA backs off 2 → 60 s ±20% jitter; bad_sig/unknown_ea waits 5 minutes. If the broker is dead, the panel shows Link: DOWN and 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, ok
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 (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, ok

EA 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ỗi InpPostIntervalSec (5 s), header Bearer = InpApiBearer, so với setting ea_api_bearer (env EA_API_BEARER); sai thì 401 Unauthorized bearer token. Laravel AccountStatusStore ghi Redis (TTL 48 h), ghi lot_history, chạy EquityAlertService; response có thể kèm pending_report.
  • TCP / HYBRID: TLS tới InpBrokerHost:InpBrokerPort (mặc định fxconsole.gamelau.uk:9602), dòng đầu HELLO|statuspub|v2|<composite_id>|<secret>|<broker_name>, secret = InpBrokerSecret = BROKER_SECRET của broker (fxconsole hiện read-only dưới tên BROKER_STREAM_SECRET). HELLO kiểu cũ (trước v2) bị từ chối. Broker ghi đúng các key của AccountStatusStore nhưng bỏ qua lot_history và EquityAlertService, nên HYBRID vẫn gọi HTTP control plane mỗi InpHybridHttpIntervalSec (30 s, đo bằng GetTickCount64 từ 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ại stream_off (hb_interval_secs). Khi off, EA chỉ gửi tcp:hb_min (status, balance, equity, tổng lot) mỗi hb_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ỗi cadence_ms (mặc định 900; 0 = mỗi tick, giới hạn bởi InpMinPushIntervalMs 100). Broker chỉ phát stream_on/stream_off ở cạnh 0 ↔ N subscriber. Hai giá trị này đọc theo thứ tự bảng settings → Redis app_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} → ReportService lư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 frame report_request tới socket của EA → EA bật g_forceHttpNow → lần push HTTP kế nhận pending_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ấy pending_report ở heartbeat HTTP kế. Scheduler report:generate-all yê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 InpTransport modes: HTTP (default), TCP, HYBRID.
  • HTTP: POST InpApiUrl (/api/account-status) every InpPostIntervalSec (5 s), Bearer header = InpApiBearer, compared with the ea_api_bearer setting (env EA_API_BEARER); a mismatch returns 401 Unauthorized bearer token. Laravel AccountStatusStore writes Redis (48 h TTL), writes lot_history, runs EquityAlertService; the response may carry pending_report.
  • TCP / HYBRID: TLS to InpBrokerHost:InpBrokerPort (default fxconsole.gamelau.uk:9602), first line HELLO|statuspub|v2|<composite_id>|<secret>|<broker_name>, secret = InpBrokerSecret = the broker's BROKER_SECRET (fxconsole shows it read-only as BROKER_STREAM_SECRET). Legacy (pre-v2) HELLOs are rejected. The broker writes the same keys as AccountStatusStore but skips lot_history and EquityAlertService, so HYBRID still calls the HTTP control plane every InpHybridHttpIntervalSec (30 s, timed with GetTickCount64 since 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, otherwise stream_off (hb_interval_secs). While off, the EA only sends tcp:hb_min (status, balance, equity, lot totals) every hb_interval_secs (EA clamps to 5–120 s, default 15), skipping positions[], experts[], today_history[]. While on, it sends the full payload every cadence_ms (default 900; 0 = every tick, floored by InpMinPushIntervalMs 100). The broker only fires stream_on/stream_off on 0 ↔ N subscriber edges. Both values are read in the order settings table → Redis app_config:* → env.
  • The dashboard in "Stream (socket)" mode opens wss://…/status-ws → :9603 and 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} → ReportService stores the request and PUBLISH report:request {composite_id, from_ts, to_ts} (unprefixed channel) → the broker routes a report_request frame to that EA's socket → the EA sets g_forceHttpNow → the next HTTP push picks up pending_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 finds pending_report on its next HTTP heartbeat. The scheduler's report:generate-all requests 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" --> EA
flowchart 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" --> EA

HTTP 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

  • TickStreamerPub 1.04 mở TLS tới InpBrokerHost:InpBrokerPort (fxconsole.gamelau.uk:9600), dòng đầu HELLO|tickpub|<account_id>|<secret>|<broker_name> (secret = InpSharedSecret = TICK_BROKER_SECRET; tên broker = InpForexBroker hoặc ACCOUNT_COMPANY). EA poll SymbolInfoTick mỗi InpTickPollMs (100 ms), gửi một dòng JSON cho mỗi tick mới {"type":"tick","s","b","a","t"}, danh sách symbols, và NOOP mỗi InpHeartbeatSec (15 s). InpSymbolWhitelist rỗ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 Redis tickstream (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à ghi calendar:<broker> + calendar:<broker>:rev.
  • Backfill OHLC khi kết nối: HTTPS GET /api/sync-state + POST /api/sync-bars tới InpFxconsoleUrl, Bearer InpFxconsoleBearer (= EA_API_BEARER; rỗng = bỏ qua), các TF M1,M5,M15,M30,H1,H4,D1,W1,MN → bảng ohlc_data trong TimescaleDB.
  • Scheduler: ohlc:aggregate đọc stream bằng XRANGE, gộp thành nến M1 trong ohlc_data, rồi cắt tick đã xử lý; brokers:sync đồng bộ broker_profiles (MySQL) từ TimescaleDB + Redis; calendar:sync chép snapshot lịch vào bảng economic_events.
  • Trình duyệt ở /charts/{slug}/{symbol}: lịch sử nến từ OhlcApiController (TimescaleDB); giá sống qua wss://…/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"}.
  • TickStreamerPub 1.04 opens TLS to InpBrokerHost:InpBrokerPort (fxconsole.gamelau.uk:9600), first line HELLO|tickpub|<account_id>|<secret>|<broker_name> (secret = InpSharedSecret = TICK_BROKER_SECRET; broker name = InpForexBroker or ACCOUNT_COMPANY). The EA polls SymbolInfoTick every InpTickPollMs (100 ms) and sends one JSON line per fresh tick {"type":"tick","s","b","a","t"}, a symbols list, and a NOOP every InpHeartbeatSec (15 s). An empty InpSymbolWhitelist = 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 the tickstream Redis 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 into calendar:<broker> + calendar:<broker>:rev.
  • OHLC backfill on connect: HTTPS GET /api/sync-state + POST /api/sync-bars to InpFxconsoleUrl, Bearer InpFxconsoleBearer (= EA_API_BEARER; empty = skipped), timeframes M1,M5,M15,M30,H1,H4,D1,W1,MN → the ohlc_data table in TimescaleDB.
  • Scheduler: ohlc:aggregate reads the streams with XRANGE, builds M1 bars in ohlc_data, then trims processed ticks; brokers:sync syncs broker_profiles (MySQL) from TimescaleDB + Redis; calendar:sync copies calendar snapshots into the economic_events table.
  • The browser on /charts/{slug}/{symbol}: bar history from OhlcApiController (TimescaleDB); live prices over wss://…/ws → :9601, sending {"action":"subscribe","broker","symbol"}, receiving the last tick (from XREVRANGE) 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" --> B

Tick 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át CmdAgent.exe và nói chuyện qua IPC NDJSON trên 127.0.0.1:9606 (loopback của VPS, không phải cổng 9606 của broker). HeartbeatAgent nối 127.0.0.1:9605; stunnel bọc TLS và kết nối tới vps_broker_host:vps_broker_port (fxconsole.gamelau.uk:9605), verify bằng CA lấy từ GET /api/agent/broker-config (header X-Vps-Secret). vps-agent.ini chỉ chứa server_url và secret_key.
  • Xác thực: frame hello mang secret; broker tra vpses.secret_key trong MySQL (cache 60 s) → hello_ok (kèm vps_id) hoặc hello_err bad_secret. Broker ping mỗi PING_INTERVAL 30 s, không pong trong PING_TIMEOUT 10 s thì đóng.
  • Heartbeat: frame hb mỗi heartbeat_interval. Broker SETEX laravel-database-vps:agent:{id}:heartbeat 15 s (HEARTBEAT_TTL), cập nhật vpses.account_logins + account_count, và trả hb_ok kèm interval nếu vps:agent:{id}:interval có 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ửi stream_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::booted PUBLISH laravel-database-vps:cmd {vps_id, cmd_id, kind} → broker gửi {"t":"cmd","cmd_id","kind"} → HeartbeatAgent chuyển do_cmd_pull qua IPC → CmdAgent GET /api/vps/cmd/{id}/payload qua HTTPS (mật khẩu MT5 mã hoá, URL gói… chỉ đi đường này) → thực thi → ack qua IPC → frame event tới broker.
  • ACK: broker UPDATE vps_commands trực tiếp rồi POST http://fxconsole-web/api/internal/broker/command-ack với X-Broker-Secret để Laravel chạy toàn bộ chuỗi trạng thái (chuyển trạng thái mt5_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: /vps và /deployments mở wss://…/vps-ws → :9606, gửi {"action":"subscribe_all","admin_token"}, nhận vps_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_id trù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 + bearer init_token); tự nâng cấp agent bằng lệnh TYPE_UPGRADE_AGENT → upgrade.ps1 tải GET /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 supervises CmdAgent.exe and talks to it over NDJSON IPC on 127.0.0.1:9606 (the VPS loopback, not the broker's port 9606). HeartbeatAgent connects to 127.0.0.1:9605; stunnel wraps TLS and connects to vps_broker_host:vps_broker_port (fxconsole.gamelau.uk:9605), verifying with the CA fetched from GET /api/agent/broker-config (header X-Vps-Secret). vps-agent.ini only holds server_url and secret_key.
  • Auth: the hello frame carries secret; the broker looks up vpses.secret_key in MySQL (60 s cache) → hello_ok (with vps_id) or hello_err bad_secret. The broker pings every PING_INTERVAL 30 s and closes the socket if no pong arrives within PING_TIMEOUT 10 s.
  • Heartbeat: an hb frame every heartbeat_interval. The broker runs SETEX laravel-database-vps:agent:{id}:heartbeat 15 s (HEARTBEAT_TTL), updates vpses.account_logins + account_count, and returns hb_ok with interval when vps:agent:{id}:interval is 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 sends stream_on (the agent sends full CPU/RAM/disk/net + logins[]); with nobody watching, stream_off (a minimal "alive" frame, no WMI); the cadence stays heartbeat_interval.
  • Command push: an admin clicks Deploy / Remove / Upgrade → INSERT vps_commands → VpsCommand::booted PUBLISH laravel-database-vps:cmd {vps_id, cmd_id, kind} → the broker sends {"t":"cmd","cmd_id","kind"} → HeartbeatAgent forwards do_cmd_pull over IPC → CmdAgent calls GET /api/vps/cmd/{id}/payload over HTTPS (encrypted MT5 passwords, package URLs… only travel this way) → executes → ack over IPC → an event frame to the broker.
  • ACK: the broker runs UPDATE vps_commands directly, then POST http://fxconsole-web/api/internal/broker/command-ack with X-Broker-Secret so Laravel runs the full state cascade (mt5_accounts transition, observers → PUBLISH vps:state, deploy-status emails, migration stage 2). Without this callback deploys get "stuck at pending".
  • Live UI: /vps and /deployments open wss://…/vps-ws → :9606, send {"action":"subscribe_all","admin_token"}, and receive vps_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 repeated cmd_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_token bearer); agent self-upgrade through a TYPE_UPGRADE_AGENT command → upgrade.ps1 downloads GET /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-ws
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 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-ws

Stream 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); fxconsole POST {InpCloudMyApiDomain}/api/tmap/save → MySQL tmap_entries (chỉ live, chỉ leader); Cloudflare Worker /tmap/save → D1 (tuỳ chọn, InpCloudCfDomain trống ở production).
  • Save: theo nhịp InpCloudSyncIntervalSec (Init mới, DCA add, Tia close, dọn map) và ngay lập tức sau PositionClosePartial hoặc trong OnDeinit. Server làm REPLACE ALL (xoá theo magic+account rồi insert lại). Từ fxconsole 2026.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":true và không đụng DB; Redis lỗi thì vẫn ghi (fail open); clear xoá 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 file MQL5/Files/Common/GR_TicketMap_<magic>.csv (always written, backtests included); fxconsole POST {InpCloudMyApiDomain}/api/tmap/save → MySQL tmap_entries (live only, leader only); Cloudflare Worker /tmap/save → D1 (optional, InpCloudCfDomain empty in production).
  • Save: on the InpCloudSyncIntervalSec cadence (new Init, DCA add, Tia close, map cleanup) and immediately after PositionClosePartial or in OnDeinit. The server does REPLACE ALL (delete by magic+account, then insert). Since fxconsole 2026.09.04.1 the server stores a sha1 fingerprint of the payload in Redis (1 h TTL): an identical payload returns "skipped":true without touching the DB; a Redis error still writes (fail open); clear forgets the fingerprint. Map back to 0 → DELETE /api/tmap/clear on 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"]
  end
flowchart 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"]
  end

Kị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=global theo lịch và analysis:run --scope=weekly khi đế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ừ Redis calendar:<broker> (connection tickstream, do TickStreamerPub đẩy); tin tức từ NewsService (news:sync chép RSS vào news_items) và Finnhub nếu có key.
  • Gọi mô hình: ClaudeService có hai engine. Mặc định cli: POST http://claude-cli:8788/analyze {model, system, prompt} → sidecar chạy claude -p --output-format json bằng subscription OAuth, tối đa ANALYZE_CONCURRENCY=1 lượt cùng lúc, timeout compose ANALYZE_TIMEOUT_MS=900000. Engine api (gọi https://api.anthropic.com/v1) chỉ dùng khi setting analysis_engine=api và có claude_api_key; hiện để nghỉ.
  • Kết quả: Analysis::create vào MySQL, hiển thị ở /analysis cho mọi user đã đăng nhập, kèm thông báo/email digest (AnalysisReadyNotification, AnalysisDigestMail). GET /health của sidecar trả cờ authed; mất đăng nhập thì /admin/analysis hiệ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=global on its schedule and analysis:run --scope=weekly when 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 Redis calendar:<broker> (tickstream connection, pushed by TickStreamerPub); news from NewsService (news:sync mirrors RSS into news_items) and Finnhub when a key is set.
  • Model call: ClaudeService has two engines. Default cli: POST http://claude-cli:8788/analyze {model, system, prompt} → the sidecar runs claude -p --output-format json on the OAuth subscription, at most ANALYZE_CONCURRENCY=1 call at a time, compose timeout ANALYZE_TIMEOUT_MS=900000. The api engine (calls https://api.anthropic.com/v1) is only used when the setting analysis_engine=api and a claude_api_key is set; currently dormant.
  • Output: Analysis::create into MySQL, shown on /analysis to every logged-in user, plus notifications/email digest (AnalysisReadyNotification, AnalysisDigestMail). The sidecar's GET /health returns an authed flag; when the login is lost, /admin/analysis shows 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 --> U
flowchart 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ằng GUAC_JSON_SECRET_KEY (extension guacamole-auth-json), ghi audit vps_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:4822 qua mạng guac_internal → VNC tới VPS (TightVNC do prepare-vm.ps1 cà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, ổ ảo fxconsole). Scheduler fxconsole: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 using GUAC_JSON_SECRET_KEY (guacamole-auth-json extension), writes a vps_remote_sessions audit 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:4822 over the guac_internal network → VNC to the VPS (TightVNC installed by prepare-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_drive volume (/var/lib/guacamole/drive, virtual drive fxconsole). The scheduler's fxconsole:close-stale-remote-sessions closes 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
443HTTPSopenresty (aaPanel)YESTrì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 → 80HTTPfxconsole-webNOopenresty; 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
9000FastCGIfxconsole-appNOfxconsole-web
6379Redisfxconsole-redisNOapp, scheduler, bốn brokerapp, scheduler, the four brokers
5432PostgreSQLfxconsole-timescaleMUST NOTapp, scheduler; máy LAN (publish mọi interface, phải chặn ở firewall)app, scheduler; LAN machines (published on all interfaces, must be firewalled)
8788HTTPfxconsole-claude-cliNOapp, scheduler
8282 → 8080HTTPfxconsole-guacamolePROXY ONLYReverse proxy của domain GuacamoleThe Guacamole domain's reverse proxy
4822Guacamolefxconsole-guacdNOfxconsole-guacamole (mạng guac_internal)fxconsole-guacamole (network guac_internal)
9600TLS TCPfxconsole-tick-brokerYESTickStreamerPub
9601WSSfxconsole-tick-brokerNOfxconsole-web qua /ws (compose vẫn publish lên host)fxconsole-web via /ws (compose still publishes it on the host)
9602TLS TCPfxconsole-account-status-brokerYESAccountStatusUpdater (TCP / HYBRID)AccountStatusUpdater (TCP / HYBRID)
9603WSSfxconsole-account-status-brokerNOfxconsole-web qua /status-ws (compose vẫn publish lên host)fxconsole-web via /status-ws (compose still publishes it on the host)
9605TLS TCPfxconsole-vps-stream-brokerYESstunnel trên VPSstunnel on the VPSes
9606WSSfxconsole-vps-stream-brokerNOfxconsole-web qua /vps-ws (chỉ expose)fxconsole-web via /vps-ws (expose only)
9607TLS TCPfxconsole-ea-link-brokerYESGoldenRobber 1.42+
3217MySQLMySQL trên host aaPanelMySQL on the aaPanel hosthost từ xaremote hostapp, scheduler, AccountStatus/VpsStream/EaLink broker (đi ra từ Docker host)app, scheduler, AccountStatus/VpsStream/EaLink brokers (outbound from the Docker host)
51820/udpWireGuardwg-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)VPNServer 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:9605TCPstunnel trên VPS (accept)stunnel on the VPS (accept)LOCALHeartbeatAgent
127.0.0.1:9606TCP NDJSONIPC của HeartbeatAgent trên VPSHeartbeatAgent IPC on the VPSLOCALCmdAgent
vnc_portVNCTightVNC trên VPSTightVNC on the VPStheo VPSper VPSfxconsole-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 JSONAccountStatusStore, AccountStatusBrokerDashboard, snapshot của brokerDashboard, broker snapshot48 h
…account_positions:{id}, …account_experts:{id}, …account_today_history:{id}string JSONnhư trênsameTrang accountAccount pages48 h
…account_status_keys:{secret_key}, …account_status_all_secret_keyssetnhư trênsameLiệt kê account theo key / mọi keyAccount listing per key / all keys—
…app_config:broker_admin_secretstringLaravel (mỗi lần admin tải trang / lưu settings)Laravel (every admin page load / settings save)AccountStatusBroker, VpsStreamBroker—
…app_config:broker_internal_secretstringLaravelVpsStreamBroker, EaLinkBroker—
…app_config:hb_interval_secs, …app_config:stream_cadence_msstringLaravelAccountStatusBroker—
…vps:agent:{id}:heartbeatstring JSONVpsStreamBrokerLaravel VpsController (dự phòng cột DB)Laravel VpsController (falls back to DB columns)15 s
…vps:agent:{id}:intervalstringLaravel Vps::updatedLaravel Vps::updatedVpsStreamBroker (gửi trong hb_ok)VpsStreamBroker (sent in hb_ok)—
…ea_link:presence:{slug}:{composite_id}:{magic}hash {node_id}#{inst} → unixEaLinkBrokerLaravel (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)stringController TMapTMap controllersController TMap (bỏ ghi trùng)TMap controllers (skip identical writes)1 h
kênhchannel laravel-database-vps:cmdpub/subVpsCommand createdVpsStreamBroker—
kênhchannel laravel-database-vps:statepub/subObserver Vps, Mt5Account, VpsCommandVps, Mt5Account, VpsCommand observersVpsStreamBroker → WS vps_stateVpsStreamBroker → WS vps_state—
kênhchannel laravel-database-ea:link:cfgpub/subEaLinkNotifierEaLinkBroker (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 prefixpub/subReportServiceAccountStatusBroker—
ticks:{broker}:{symbol} no prefixstream, MAXLEN ~ 100000TickBrokerohlc: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 prefixset, set, hashTickBrokerohlc:aggregate, brokers:sync, Charts—
calendar:{broker}, calendar:{broker}:rev no prefixstring JSONTickBrokercalendar: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_secretEaLinkBroker, VpsStreamBrokersettings → app_config:broker_internal_secret → BROKER_INTERNAL_SECRET
broker_admin_secretAccountStatusBroker, VpsStreamBrokersettings → app_config:broker_admin_secret → BROKER_ADMIN_SECRET
hb_interval_secs, stream_cadence_msAccountStatusBrokersettings → 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

Chỉ tên, không giá trịNames only, no values

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_tokenMySQL, 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 keyInpCloudBearerToken trên mọi terminalon every terminal
settings.broker_internal_secretMySQL ("EALinkBroker Secret Key"), mirror Redis, env BROKER_INTERNAL_SECRET ở fxconsole và brokerMySQL ("EALinkBroker Secret Key"), Redis mirror, env BROKER_INTERNAL_SECRET in fxconsole and brokersHeader 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_secretMySQL, mirror Redis, env BROKER_ADMIN_SECRETMySQL, Redis mirror, env BROKER_ADMIN_SECRETsubscribe_all WS firehose; chỉ admin nhận qua Inertia propssubscribe_all WS firehose; only admins receive it via Inertia propsAccountStatusBroker, VpsStreamBroker
BROKER_SECRETmonitor/AccountStatusBroker/.envHELLO statuspubstatuspub HELLOInpBrokerSecret (AccountStatusUpdater), BROKER_STREAM_SECRET (fxconsole, hiện read-only)InpBrokerSecret (AccountStatusUpdater), BROKER_STREAM_SECRET (fxconsole, shown read-only)
TICK_BROKER_SECRETmonitor/TickBroker/.envHELLO tickpubtickpub HELLOInpSharedSecret (TickStreamerPub)
EA_API_BEARER / settings.ea_api_bearermonitor/fxconsole/.env, DB ghi đèmonitor/fxconsole/.env, DB overrides/api/account-status, /api/sync-state, /api/sync-barsInpApiBearer, InpFxconsoleBearer
TMAP_SECRET_KEYmonitor/fxconsole/.env, secret của Cloudflare Workermonitor/fxconsole/.env, Cloudflare Worker secretURL 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_keyMySQL, mỗi VPSMySQL, per VPSHeader X-Vps-Secret, HELLO tới VpsStreamBroker, upgrade.ps1X-Vps-Secret header, VpsStreamBroker HELLO, upgrade.ps1secret_key trongin vps-agent.ini
VPS_INIT_BOOTSTRAP_TOKEN / settings.vps_init_bootstrap_token.env fxconsole, DB ghi đèfxconsole .env, DB overridesPOST /api/agent/init-configinit_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= tokenJSON_SECRET_KEY củaof fxconsole-guacamole
APP_KEYmonitor/fxconsole/.envMã hoá cột DB (vpses.vnc_password, mt5_accounts.password_encrypted…), cookie phiênEncrypts DB columns (vpses.vnc_password, mt5_accounts.password_encrypted…), session cookiesKhô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 OAuthVolume 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 keyDB, 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_keyMySQL, mỗi userMySQL, per userĐược tiêm vào .set khi deploy góiInjected into the .set during package deploysInpAccountSecretKey (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.mdTrạng thái build; chuẩn tích hợp EA ↔ fxconsoleBuild status; EA ↔ fxconsole integration standard
deploy.shScript 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.mdTà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ượcstrategyDữ 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