Services

fxconsole (web)(web)

Web console Laravel + React giám sát tài khoản MT5, quản lý VPS và deploy, cấu hình hot-reload cho EA, phân tích thị trường và báo cáo. Trang này giúp bạn biết console làm gì, chạy trên container nào, route nào gọi được, dữ liệu nằm ở đâu và broker đọc setting nào. The Laravel + React web console that monitors MT5 accounts, manages VPSs and deployments, pushes hot-reload config to EAs, and runs market analysis and reports. This page tells you what the console does, which containers it runs in, which routes exist, where data lives and which settings the brokers read.

  • Cập nhậtUpdated 17/09/2026
  • Phiên bảnVersion 2026.09.14.1
  • NguồnSource monitor/fxconsole/ · monitor/README.html · monitor/fxconsole/CLAUDE.md · routes/web.php · routes/api.php · app/Http/Controllers/Admin/SettingsController.php · EA-HOTRELOAD-STANDARD.md §15

Console làm gìWhat the console does

Mọi trang đều cần đăng nhập. Người dùng thường thấy tài khoản của chính mình (khớp theo users.mt5_secret_key); admin thấy toàn bộ và có thêm nhóm /admin/*.

Every page requires a login. Regular users see their own accounts (matched by users.mt5_secret_key); admins see everything and get the extra /admin/* group.

Khu vựcArea Route Ai dùngWho Chức năngWhat it does
Dashboard trạng thái tài khoảnAccount status dashboard /dashboard, /dashboard/accounts/{account_id} Mọi userAll users Balance, equity, lệnh mở, lịch sử hôm nay, ghi chú, watchlist, cảnh báo sụt equity. Dữ liệu đến từ EA AccountStatusUpdater (HTTP hoặc qua AccountStatusBroker). Có chế độ polling và chế độ "Stream (socket)" qua /status-ws.Balance, equity, open positions, today's history, notes, watchlist, equity-drop alerts. Data comes from the AccountStatusUpdater EA (HTTP or via AccountStatusBroker). Polling mode and "Stream (socket)" mode over /status-ws.
Báo cáo & hiệu suấtReports & performance /dashboard/accounts/{account_id}/report, /performance Mọi userAll users "Generate report" yêu cầu EA stream lịch sử deal theo từng tuần; /performance vẽ chuỗi hằng ngày từ báo cáo định kỳ (balance/profit cộng dồn các tài khoản của user, đến phiên đóng cửa gần nhất), kèm hai ô tổng cho khoảng đang chọn: Lợi nhuận và Đã rút (tổng các balance op âm, tức lệnh rút tiền; rê chuột để xem số lần rút). Admin xem user khác qua /admin/users/{id}/performance."Generate report" asks the EA to stream deal history week by week; /performance charts a daily series from the scheduled reports (balance/profit summed over the user's accounts, up to the last market close), with two totals for the selected range: Profit and Withdrawn (sum of negative balance operations, i.e. withdrawals; hover for the count). Admins view another user via /admin/users/{id}/performance.
Charts /charts, /charts/{slug}/{symbol} Mọi userAll users Giá realtime từ TickBroker (/ws) + nến OHLC trong TimescaleDB, có thể hiện lệnh đang mở của tài khoản mình trên chart.Realtime prices from TickBroker (/ws) + OHLC candles in TimescaleDB, optionally overlaying your own open positions.
Phân tích, lịch kinh tế, tinAnalysis, calendar, news /analysis, /analysis/{slug}/{symbol}, /calendar, /news Mọi user (chỉ đọc); admin cấu hình ở /admin/analysisAll users (read-only); admins configure at /admin/analysis Thẻ phân tích do lệnh lịch analysis:run tạo (mức kỹ thuật + đoạn nhận định do Claude viết qua sidecar claude-cli). Lịch kinh tế đồng bộ từ lịch MT5 (calendar:sync), tin nóng từ RSS công khai (news:sync). Trang chỉ đọc bảng DB, không gọi nguồn ngoài.Analysis cards are produced by the scheduled analysis:run command (technical levels + a narrative written by Claude through the claude-cli sidecar). The economic calendar is mirrored from the MT5 calendar (calendar:sync), headlines from public RSS (news:sync). Pages only read DB tables, never the upstream.
VPS & deployVPS & deployments /vps, /vps/{id}, /deployments, /deployments/new Mọi user xem fleet và tự deploy trong hạn mức max_deployed_accounts; thao tác sửa VPS chỉ adminAll users see the fleet and self-deploy within their max_deployed_accounts quota; VPS mutations are admin-only Deploy / gỡ / đổi package / migrate tài khoản MT5 giữa VPS, trạng thái live qua VpsStreamBroker (/vps-ws). Admin: thêm VPS, Proxmox clone template, nâng cấp agent, logs, rollback, gán tài khoản cho user khác.Deploy / remove / change package / migrate MT5 accounts between VPSs, live status via VpsStreamBroker (/vps-ws). Admin: add VPS, Proxmox template clone, agent upgrades, logs, rollback, reassigning accounts to another user.
Package & bản agentPackages & agent releases /admin/packages, /admin/agent-releases Admin Thư viện package deploy (zip EA + .set) cho user chọn khi deploy; bulk upgrade mọi tài khoản đang dùng 1 package; upload zip phiên bản vps-agent; dọn file zip mồ côi.Deployment package library (EA zip + .set) users pick from; bulk-upgrade every account on a package; upload vps-agent release zips; sweep orphaned zips.
EA products (easupported)EA products (easupported) /admin/easupported, /admin/easupported/{slug} Admin Đăng ký EA product (slug + secret token), xem tài khoản đang heartbeat, Apply / Clear config hot-reload. Xem bên dưới.Register EA products (slug + secret token), see accounts that heartbeat, Apply / Clear hot-reload config. See below.
EA của tôiMy EAs /easupported, /easupported/{slug}, /easupported/{slug}/account/{id} Mọi user (chỉ account được gán cho mình)All users (only accounts assigned to them) Cùng bảng trạng thái và Apply / Clear hot config như trang admin, giới hạn ở account MT5 được gán cho user. Xem bên dưới.The same status table and Apply / Clear hot config as the admin page, limited to the MT5 accounts assigned to the user. See below.
Hiển thị tiền: USD/VND, hệ số chiaMoney display: USD/VND, money divisor Nút $ / ₫ trên header (POST /currency), menu Hệ số chia (POST /deployments/bulk-divisor), hệ số theo server trên /admin/brokersHeader $ / ₫ button (POST /currency), Money divisor menu (POST /deployments/bulk-divisor), per-server divisor on /admin/brokers Mọi user; đặt hệ số theo server chỉ adminAll users; per-server divisors are admin-only Hiện mọi số tiền bằng VND theo tỷ giá Binance P2P; chia số tiền của account báo ×10 / ×100 (vd. account cent) theo hệ số /10 hoặc /100. Xem Hiển thị tiền.Shows every money figure in VND at the Binance P2P rate; divides the money of accounts that report ×10 / ×100 (e.g. cent accounts) by /10 or /100. See Money display.
Remote desktopRemote desktop POST /admin/vps/{id}/remote-session Admin Mở phiên VNC trong trình duyệt qua Guacamole bằng token ký 60 giây; có trang audit /admin/vps/{id}/remote-sessions.Opens a browser VNC session through Guacamole with a 60-second signed token; audit page at /admin/vps/{id}/remote-sessions.
Quản trị khácOther admin /admin/users, /admin/settings, /admin/brokers, /admin/theme, /admin/ip-report, /admin/proxmox/nodes Admin User (role, hạn mức, khoá, chuyển toàn bộ tài khoản), setting runtime, hồ sơ broker + logo, màu giao diện + ảnh nền login, báo cáo IP public theo tài khoản, node Proxmox.Users (role, quota, block, reassign all accounts), runtime settings, broker profiles + logos, theme accents + login background, public-IP report per account, Proxmox nodes.

StackStack

Backend

  • Laravel 12 (PHP ^8.2) + Inertia
  • MySQL (kết nối mặc định, database trade_copier)(default connection, database trade_copier)
  • Redis 7 (không persist)(no persistence)
  • TimescaleDB (PostgreSQL 16) chỉ cho ohlc_datafor ohlc_data only
  • Scheduler php artisan schedule:work (không cron hệ thống)Scheduler php artisan schedule:work (no system cron)

Frontend

  • React 19 + TypeScript + @inertiajs/react, Vite 7
  • Tailwind CSS v4 (không có tailwind.config.js, theme nằm trong resources/css/app.css)Tailwind CSS v4 (no tailwind.config.js; theme lives in resources/css/app.css)
  • shadcn/ui (new-york, OKLCH) — resources/js/components/ui/
  • recharts cho biểu đồ tài khoản, lightweight-charts cho chart giá/tickrecharts for account charts, lightweight-charts for price/tick charts
  • Font IBM Plex Sans, số dùng tabular-numsIBM Plex Sans, numbers use tabular-nums

Page nằm ở resources/js/Pages/**, layout chung resources/js/layouts/AppLayout.tsx, sidebar resources/js/components/AppSidebar.tsx, alias @/ → resources/js/. Dev: npm run dev; build: npm run build; typecheck: npx tsc --noEmit (không có script lint).

Pages live in resources/js/Pages/**, shared layout resources/js/layouts/AppLayout.tsx, sidebar resources/js/components/AppSidebar.tsx, alias @/ → resources/js/. Dev: npm run dev; build: npm run build; typecheck: npx tsc --noEmit (no lint script).

Quy tắc UIUI rules

Chỉ dùng token màu ngữ nghĩa (text-up, text-down, text-success, bg-primary…), không dùng màu palette thô; không tự dựng component mà shadcn đã có; màu xanh brand dùng hạn chế; phải chạy được cả sáng lẫn tối. Mọi bảng liệt kê dòng phải dùng toolbar chung (refresh, cột, tìm kiếm) và mọi cột dữ liệu sort được. Chi tiết: monitor/fxconsole/CLAUDE.md.

Use semantic colour tokens only (text-up, text-down, text-success, bg-primary…), never raw palette colours; never hand-roll a component shadcn already provides; keep brand blue scarce; light and dark must both work. Every row-listing table uses the shared toolbar (refresh, columns, search) and every data column is sortable. Details: monitor/fxconsole/CLAUDE.md.

ContainerContainers

Stack monitor/fxconsole/docker-compose.yml (project fxconsole, network fxconsole_default). Bốn broker là stack riêng, join vào network này — xem Các broker realtime.

Stack monitor/fxconsole/docker-compose.yml (project fxconsole, network fxconsole_default). The four brokers are separate stacks that join this network — see Realtime brokers.

ServiceContainerCổngPorts Vai tròRole
appfxconsole-app9000 (nội bộ)9000 (internal) PHP-FPM. Mỗi lần boot: cài composer/npm nếu manifest đổi, npm run build, tạo APP_KEY chỉ khi chưa có, php artisan migrate --force, view:clear. Mount read-only ../VpsStreamBroker/certs vào /broker-certs để phục vụ GET /api/agent/broker-config.PHP-FPM. On every boot: installs composer/npm deps if manifests changed, npm run build, generates APP_KEY only when missing, php artisan migrate --force, view:clear. Mounts ../VpsStreamBroker/certs read-only at /broker-certs to serve GET /api/agent/broker-config.
webfxconsole-web8080:80 nginx. Proxy WebSocket /ws → TickBroker 9601, /status-ws → AccountStatusBroker 9603, /vps-ws → VpsStreamBroker 9606 (proxy_ssl_verify off, timeout 3600s). Broker gọi Laravel qua http://fxconsole-web (cổng 80 nội bộ).nginx. WebSocket proxies /ws → TickBroker 9601, /status-ws → AccountStatusBroker 9603, /vps-ws → VpsStreamBroker 9606 (proxy_ssl_verify off, 3600s timeouts). Brokers call Laravel at http://fxconsole-web (internal port 80).
redisfxconsole-redis6379 (nội bộ)6379 (internal) Redis 7, --save "", --appendonly no, allkeys-lru: dữ liệu trạng thái được EA tạo lại liên tục nên ưu tiên tốc độ ghi.Redis 7 with --save "", --appendonly no, allkeys-lru: status data is regenerated by EAs constantly, so write speed wins over durability.
timescalefxconsole-timescale5432:5432 TimescaleDB pg16 cho ohlc_data. Cổng publish mọi interface — nếu host có IP public phải chặn 5432 ở firewall.TimescaleDB pg16 for ohlc_data. Published on all interfaces — block 5432 at the firewall if the host has a public IP.
schedulerfxconsole-scheduler— Chờ vendor/autoload.php, chạy schedule:clear-cache (xoá lock kẹt) rồi schedule:work.Waits for vendor/autoload.php, runs schedule:clear-cache (clears stuck locks), then schedule:work.
claude-clifxconsole-claude-cli8788 (nội bộ)8788 (internal) Sidecar Claude Code CLI dùng gói subscription (OAuth). Credential ở volume claude_creds / claude_config. Xem claude-cli.Claude Code CLI sidecar on the team subscription (OAuth). Credentials on the claude_creds / claude_config volumes. See claude-cli.
guacd, guacamolefxconsole-guacd, fxconsole-guacamole8282:8080 Remote desktop trong trình duyệt, network riêng guac_internal, auth JSON (không DB). Xem Guacamole.Browser remote desktop on its own guac_internal network, JSON auth (no DB). See Guacamole.
Không bao giờ down -vNever down -v

docker compose down -v xoá volume có tên: timescale_data (mất toàn bộ lịch sử OHLC) và claude_creds / claude_config (mất đăng nhập subscription). Chỉ dùng down thường.

docker compose down -v destroys the named volumes: timescale_data (all OHLC history) and claude_creds / claude_config (subscription login). Use plain down only.

Route chínhKey routes

API cho EA và agent (routes/api.php)EA and agent API (routes/api.php)

Stateless, không session. Tất cả có tiền tố /api.

Stateless, no session. All prefixed with /api.

Method · PathAi gọiCallerAuthMục đíchPurpose
POST /api/account-statusAccountStatusUpdaterAuthorization: Bearer = setting ea_api_bearer Upsert trạng thái tài khoản, nhận chunk báo cáo, trả pending_report khi có yêu cầu báo cáo.Upserts account status, ingests report chunks, returns pending_report when a report is requested.
POST /api/vps/heartbeatvps-agentX-Vps-Secret Heartbeat HTTPS (đường dự phòng khi broker stream chết).HTTPS heartbeat (fallback when the broker stream is down).
GET /api/vps/cmd/{id}/payloadCmdAgentX-Vps-Secret Kéo payload lệnh đầy đủ (mật khẩu MT5 mã hoá…) sau khi broker đẩy cmd_id.Pulls the full command payload (encrypted MT5 password…) after the broker pushes cmd_id.
GET /api/agent/broker-configprepare-vm.ps1X-Vps-Secret Trả broker_host, broker_port, broker_ca để cấu hình stunnel.Returns broker_host, broker_port, broker_ca for stunnel.
POST /api/agent/init-configVM clone từ templateTemplate-cloned VMAuthorization: Bearer = vps_init_bootstrap_token + MAC đã đăng ký+ pre-registered MAC Zero-touch provisioning; token trống → 503.Zero-touch provisioning; empty token → 503.
GET /api/agent/release/{id}/downloadupgrade.ps1X-Vps-Secret Tải zip agent mới; header X-Release-Hash trả SHA256.Downloads the new agent zip; X-Release-Hash header echoes the SHA256.
POST /api/tmap/save · GET /api/tmap/load · DELETE /api/tmap/clear · POST /api/tmap/heartbeatEA cũLegacy EAsAuthorization: Bearer = tmap_secret_key (fallback TMAP_SECRET_KEY)(fallback TMAP_SECRET_KEY) Ticket map + leader election dùng token chung (legacy). EA mới dùng đường per-product bên dưới.Ticket map + leader election with the shared token (legacy). New EAs use the per-product route below.
GET /api/sync-state · POST /api/sync-barsTickStreamerPubBearer Bù nến OHLC bị hụt khi EA offline.OHLC backfill for gaps built up while the EA was offline.

Endpoint EA theo productPer-product EA endpoints

Khai báo trong routes/web.php nhưng bỏ toàn bộ middleware session/cookie/CSRF/Inertia; auth bằng Bearer = secret_token của product (EA đặt vào InpCloudBearerToken). URL gốc là InpCloudMyApiDomain = https://<host>/easupported/{slug}.

Declared in routes/web.php but stripped of every session/cookie/CSRF/Inertia middleware; auth is Bearer = the product's secret_token (set on the EA as InpCloudBearerToken). The base URL is InpCloudMyApiDomain = https://<host>/easupported/{slug}.

Method · PathMục đíchPurpose
POST /easupported/{slug}/api/tmap/heartbeatGửi config_schema + payload + node; nhận leader, hot_keys và giá trị override.Sends config_schema + payload + node; receives leader state, hot_keys and override values.
POST /easupported/{slug}/api/tmap/saveLưu ticket map (bỏ qua ghi khi map không đổi, trả "skipped":true).Saves the ticket map (skips the write when unchanged, returns "skipped":true).
GET /easupported/{slug}/api/tmap/load?magic=&account=Nạp ticket map khi khởi động.Loads the ticket map at startup.
DELETE /easupported/{slug}/api/tmap/clear?magic=&account=Xoá ticket map.Clears the ticket map.

Endpoint nội bộ cho brokerInternal broker endpoints

Method · PathAi gọiCallerMục đíchPurpose
POST /api/internal/ea-link/keyEaLinkBroker Body {"slug"} → 200 {"ok":true,"ea_id","key"} với key = sha256(secret_token) dạng hex (token thô không rời Laravel); 404 {"ok":false,"reason":"unknown_ea"}.Body {"slug"} → 200 {"ok":true,"ea_id","key"} where key = hex sha256(secret_token) (the raw token never leaves Laravel); 404 {"ok":false,"reason":"unknown_ea"}.
POST /api/internal/broker/command-ackVpsStreamBroker Mỗi event kết thúc lệnh của agent → Laravel chạy cascade (mt5_accounts, observer, email deploy, migration bước 2).Each terminal agent event → Laravel runs the cascade (mt5_accounts, observers, deploy emails, migration stage 2).

Cả hai xác thực bằng header X-Broker-Secret = setting broker_internal_secret và fail-closed khi server chưa cấu hình secret.

Both authenticate with the X-Broker-Secret header = the broker_internal_secret setting and fail closed when the server has no secret configured.

Route web (session)Web routes (session)

  • Khách: /login, /register, /forgot-password, /reset-password (OTP qua email). POST /locale đổi ngôn ngữ cho cả khách lẫn user.
  • User: các trang ở bảng phía trên, cộng /settings (hồ sơ, thông báo, secret key MT5, đổi mật khẩu bằng OTP), /notifications. Bấm thông báo "deploy thành công" mở /dashboard/accounts/{login} (cả thông báo cũ đã lưu link /vps); thông báo deploy lỗi hoặc gỡ vẫn mở /vps (admin) hoặc /deployments.
  • Chỉ user đã đăng nhập: POST /currency (USD | VND), POST /deployments/bulk-divisor (account_ids[], mode = 1 | 10 | 100 | auto; user thường chỉ đổi account của mình, admin đổi mọi account), nhóm /easupported/* (EA của tôi).
  • JSON cho SPA Charts nằm trong web.php dưới tiền tố /api để dùng cookie session: /api/brokers, /api/brokers/{slug}/symbols, /api/brokers/{slug}/quotes, /api/charts/{slug}/{symbol}, /api/charts/{slug}/{symbol}/latest, /api/broker-profiles, /api/mt5-accounts, /api/mt5-accounts/{accountId}/positions.
  • Admin: toàn bộ nằm trong group /admin với middleware admin (403 cho user thường).
  • Guests: /login, /register, /forgot-password, /reset-password (email OTP). POST /locale switches language for guests and users.
  • Users: the pages in the table above, plus /settings (profile, notifications, MT5 secret key, OTP password change) and /notifications. Clicking a "deploy succeeded" notification opens /dashboard/accounts/{login} (stored notifications that still carry /vps too); failed or removed deploy notifications still open /vps (admins) or /deployments.
  • Logged-in users only: POST /currency (USD | VND), POST /deployments/bulk-divisor (account_ids[], mode = 1 | 10 | 100 | auto; regular users change only their own accounts, admins any account), the /easupported/* group (My EAs).
  • JSON for the Charts SPA lives in web.php under the /api prefix so it uses the session cookie: /api/brokers, /api/brokers/{slug}/symbols, /api/brokers/{slug}/quotes, /api/charts/{slug}/{symbol}, /api/charts/{slug}/{symbol}/latest, /api/broker-profiles, /api/mt5-accounts, /api/mt5-accounts/{accountId}/positions.
  • Admin: everything sits in the /admin group behind the admin middleware (403 for regular users).
Tài liệu cũStale docs

Mục "Route web" trong monitor/README.html chỉ còn liệt kê /login, /register, /dashboard và "làm mới theo interval (AJAX)" — đó là giao diện Blade đời đầu. Bảng trên đọc từ routes/web.php hiện tại.

The "Route web" section in monitor/README.html still lists only /login, /register, /dashboard and "interval AJAX refresh" — that was the original Blade UI. The tables above are read from the current routes/web.php.

EA products (easupported) và hot-reloadEA products (easupported) and hot-reload

Nguyên tắc: 1 EA product = 1 slug + 1 secret token. Server là nguồn chân lý của config override, input của EA là giá trị fallback. Giá trị config chỉ đi qua HTTPS; socket EA Link chỉ mang tín hiệu. Chuẩn đầy đủ cho EA: Chuẩn tích hợp EA.

Principle: 1 EA product = 1 slug + 1 secret token. The server is the source of truth for config overrides; the EA's input is the fallback. Config values travel over HTTPS only; the EA Link socket carries only a signal. Full EA-side standard: EA integration standard.

Đăng ký productRegistering a product

  1. Mở /admin/easupported → thêm EA: nhập tên, slug được gợi ý từ tên (server slugify lại và thêm hậu tố -2, -3… cho unique), secret token 8–128 ký tự.Open /admin/easupported → add EA: enter a name; the slug is suggested from it (the server slugifies again and appends -2, -3… to keep it unique); secret token 8–128 characters.
  2. Copy URL https://<host>/easupported/{slug} vào InpCloudMyApiDomain và token vào InpCloudBearerToken trên terminal (không commit .set chứa token).Copy the URL https://<host>/easupported/{slug} into InpCloudMyApiDomain and the token into InpCloudBearerToken on the terminal (never commit a .set with the token).
  3. Chạy EA: heartbeat đầu tiên tạo dòng trong ea_account và lưu config_schema vào product.Run the EA: the first heartbeat creates an ea_account row and stores the config_schema on the product.

Xoá product sẽ cascade xoá ea_account → ea_config. Nghi lộ token: đổi token trên trang rồi cập nhật .set của mọi terminal; EaLinkBroker nhận key mới cho kết nối mới trong vòng KEY_CACHE_SEC (60s).

Deleting a product cascades to ea_account → ea_config. If a token may have leaked: change it on the page and update every terminal's .set; EaLinkBroker picks up the new key for new connections within KEY_CACHE_SEC (60s).

config_schema và trang accountconfig_schema and the account pages

  • EA tự khai báo danh sách config chỉnh được trong mỗi heartbeat: mỗi phần tử có key (snake_case), label, type (bool · int · float · enum · string), default, options (bắt buộc với enum). Web render form từ schema, không hardcode.
  • Schema lưu theo product; nhiều build khác schema cùng slug thì web hiển thị schema của account heartbeat gần nhất.
  • /admin/easupported/{slug}: danh sách account (Online khi heartbeat < 120s; account có heartbeat cuối quá 5 ngày được ẩn khỏi bảng và khỏi số đếm, không bị xoá, hiện lại ở heartbeat kế tiếp), broker + server, heartbeat cuối, config đang override, badge Link (có presence EA Link) hoặc HTTP. Bảng dùng toolbar chung (tự làm mới, cột, sort mọi cột, tìm kiếm); trang payload cũng vậy.
  • /admin/easupported/{slug}/account/{id}: bảng payload 2 cột name / data do EA đẩy (thường là nguyên nội dung info panel).
  • The EA declares its editable configs in every heartbeat: each item has key (snake_case), label, type (bool · int · float · enum · string), default, options (required for enum). The web renders the form from the schema; nothing is hardcoded.
  • The schema is stored per product; when several builds with different schemas share a slug, the web shows the schema of the most recently heartbeating account.
  • /admin/easupported/{slug}: account list (Online when heartbeat < 120s; accounts whose last heartbeat is more than 5 days old are hidden from the table and the counts, not deleted, and come back on their next heartbeat), broker + server, last heartbeat, current overrides, a Link badge (EA Link presence) or HTTP. The table uses the shared toolbar (auto refresh, columns, every column sortable, search); so does the payload page.
  • /admin/easupported/{slug}/account/{id}: two-column name / data payload table pushed by the EA (usually the full info panel).

Apply / Clear configApply / Clear config

  • Apply (POST /admin/easupported/{slug}/config): chọn 1 hoặc nhiều account, nhập giá trị. Chỉ nhận key có trong schema (product chưa báo schema → lỗi "This EA has not reported its config schema yet."), chỉ account thuộc product đó. Ghi ea_config (param, value, updated_by).
  • Clear (DELETE /admin/easupported/{slug}/config): xoá override của các account, toàn bộ hoặc theo danh sách params. Apply và Clear đều ghi log (easupported.config.apply / easupported.config.clear, kèm scope admin hoặc user). Mỗi giá trị Apply phải là một giá trị đơn (không phải mảng), tối đa 128 ký tự.
  • Có thay đổi thì EaLinkNotifier publish Redis ea:link:cfg với {"slug","targets":[{"composite_id","magic"}],"rev":<ms>}. EA đang có Link kéo config ngay; EA khác nhận ở heartbeat kế tiếp.
  • Heartbeat response chỉ chứa key đang override, kèm hot_keys (CSV). Key vắng trong hot_keys → EA quay về giá trị input; "hot_keys":"" → revert toàn bộ.
  • Apply (POST /admin/easupported/{slug}/config): pick one or many accounts and enter values. Only schema keys are accepted (no schema yet → "This EA has not reported its config schema yet."), and only accounts of that product. Writes ea_config (param, value, updated_by).
  • Clear (DELETE /admin/easupported/{slug}/config): deletes the overrides of the selected accounts, all of them or only the listed params. Apply and Clear are both logged (easupported.config.apply / easupported.config.clear, with scope admin or user). Each Apply value must be a single value (not an array), at most 128 characters.
  • When something changed, EaLinkNotifier publishes Redis ea:link:cfg with {"slug","targets":[{"composite_id","magic"}],"rev":<ms>}. Linked EAs pull config immediately; others get it on their next heartbeat.
  • The heartbeat response carries only keys that are overridden, plus hot_keys (CSV). A key missing from hot_keys → the EA reverts to its input; "hot_keys":"" → revert everything.
sequenceDiagram
  participant A as Admin
  participant L as Laravel
  participant R as Redis
  participant B as EaLinkBroker 9607
  participant E as EA (MT5)
  A->>L: Apply / Clear trên /admin/easupported/{slug}
  L->>L: Ghi / xoá ea_config
  L->>R: PUBLISH ea:link:cfg {slug, targets, rev}
  R-->>B: PSUBSCRIBE *ea:link:cfg
  B->>E: t=cfg, rev (chỉ tín hiệu)
  E->>L: POST /easupported/{slug}/api/tmap/heartbeat (HTTPS)
  L-->>E: hot_keys + giá trị override
  E->>B: t=cfg_ack, rev, ok
sequenceDiagram
  participant A as Admin
  participant L as Laravel
  participant R as Redis
  participant B as EaLinkBroker 9607
  participant E as EA (MT5)
  A->>L: Apply / Clear on /admin/easupported/{slug}
  L->>L: Write / delete ea_config
  L->>R: PUBLISH ea:link:cfg {slug, targets, rev}
  R-->>B: PSUBSCRIBE *ea:link:cfg
  B->>E: t=cfg, rev (signal only)
  E->>L: POST /easupported/{slug}/api/tmap/heartbeat (HTTPS)
  L-->>E: hot_keys + override values
  E->>B: t=cfg_ack, rev, ok

Chi tiết protocol EA Link: EaLinkBroker.EA Link protocol details: EaLinkBroker.

Trang EA của tôi (user)My EAs page (users)

  • /easupported (mục "EA của tôi" trên sidebar): một thẻ cho mỗi EA product đang chạy trên ít nhất một account được gán cho user, kèm số account và số account online. Trang không gửi secret token hay URL của EA.
  • /easupported/{slug} và /easupported/{slug}/account/{id}: cùng bảng và trang payload như admin. Apply / Clear gọi POST / DELETE /easupported/{slug}/config; user phải xác nhận trước khi Apply. EA không có account nào của user → 404, nên user không thấy các EA product khác.
  • Mọi lần đọc và ghi đều lọc ở server (EaAccount::ownedBy). Chỉ chọn account không phải của mình → lỗi account_ids. Logic dùng chung với admin nằm ở App\Services\EaSupportedAccounts.

Account được gán = dòng mt5_accounts chưa gỡ có user_id của user (user tự deploy, admin deploy hộ, hoặc admin gán lại). Một dòng ea_account thuộc user khi khớp dòng đó theo một trong ba điều kiện:

  • /easupported ("My EAs" in the sidebar): one card per EA product running on at least one account assigned to the user, with the account and online counts. The page never sends secret tokens or EA URLs.
  • /easupported/{slug} and /easupported/{slug}/account/{id}: the same table and payload page as the admin view. Apply / Clear call POST / DELETE /easupported/{slug}/config; users confirm before Apply. An EA with none of the user's accounts → 404, so other EA products stay hidden.
  • Every read and write is filtered on the server (EaAccount::ownedBy). Selecting only accounts that aren't yours → an account_ids error. The logic shared with the admin pages lives in App\Services\EaSupportedAccounts.

Assigned account = an active mt5_accounts row with the user's user_id (deployed by the user, deployed by an admin on their behalf, or reassigned to them). An ea_account row belongs to the user when it matches that row by any of three conditions:

Điều kiệnCondition Khi nào có tác dụngWhen it applies
composite_id trùng nhauSame composite_id slug(company)::slug(server)::login. Giá trị thật được ghi vào mt5_accounts bởi heartbeat HTTP của AccountStatusUpdater và (từ 2026.09.14.1) heartbeat /easupported/{slug}/api/tmap/heartbeat (Mt5Account::syncCompositeId). Trước đó cột là placeholder broker::unknown-server::login.slug(company)::slug(server)::login. The real value is written to mt5_accounts by AccountStatusUpdater's HTTP heartbeat and (since 2026.09.14.1) by the /easupported/{slug}/api/tmap/heartbeat heartbeat (Mt5Account::syncCompositeId). Until then the column holds the broker::unknown-server::login placeholder.
login + serverlogin + server EA gửi tên server MT5 trong trường server.The EA sends the MT5 server name in server.
login + tên brokerlogin + broker name GoldenRobber gửi server = ACCOUNT_COMPANY (vd "Vantage Markets (Pty) Ltd"); so với broker_profiles.name của account được gán. Từ 2026.09.14.1.GoldenRobber sends server = ACCOUNT_COMPANY (e.g. "Vantage Markets (Pty) Ltd"); compared with the assigned account's broker_profiles.name. Since 2026.09.14.1.
Không gán nhầm userNo cross-user matches

Mỗi login chỉ có một dòng active trong mt5_accounts (UNIQUE(login, removed_at)), nên khớp theo login không thể trỏ sang user khác. Cùng số login ở broker hoặc server khác thì không khớp. Account không deploy qua fxconsole (không có dòng mt5_accounts) không hiện ở trang này.

Each login has a single active row in mt5_accounts (UNIQUE(login, removed_at)), so a login match cannot point at another user. The same login number at another broker or server does not match. Accounts not deployed through fxconsole (no mt5_accounts row) do not appear on this page.

Bảng dữ liệuTables

BảngTableVai tròRole
easupportedProduct: slug (unique), name, secret_token, description, document, config_schemaProduct: slug (unique), name, secret_token, description, document, config_schema
ea_accountAccount phát hiện từ heartbeat: ea_id, composite_id, login, name, magic, server, payload, last_heartbeat; unique (ea_id, composite_id, magic)Accounts discovered from heartbeats: ea_id, composite_id, login, name, magic, server, payload, last_heartbeat; unique (ea_id, composite_id, magic)
ea_configOverride theo account: ea_account_id, param, value, updated_byPer-account overrides: ea_account_id, param, value, updated_by
tmap_entries, tmap_nodesTicket map và node leader election (theo magic + account)Ticket map and leader-election nodes (by magic + account)

Hiển thị tiền: USD/VND và hệ số chiaMoney display: USD/VND and money divisor

Số tiền được lưu và stream đúng như EA gửi. Việc đổi đơn vị chỉ xảy ra khi hiển thị (resources/js/lib/currency.ts: useMoney(), toUsd()) và khi server cộng tổng. Lot, giá, phần trăm và id không bao giờ bị đổi.

Money figures are stored and streamed exactly as the EA reports them. Units change only on display (resources/js/lib/currency.ts: useMoney(), toUsd()) and when the server sums totals. Lots, prices, percentages and ids are never converted.

Đổi USD ↔ VNDUSD ↔ VND switch

  • Nút $ / ₫ cạnh nút ngôn ngữ, chỉ hiện khi đã đăng nhập. Lựa chọn lưu ở users.currency qua POST /currency. Menu hiện "1 USDT = … ₫ · Binance P2P · x phút trước" và cảnh báo khi giá cũ hơn 1 giờ.
  • VND = giá trị USD × tỷ giá, làm tròn tới đồng, hậu tố ₫; trục biểu đồ dùng dạng rút gọn. USD giữ dạng 2 chữ số thập phân như trước. Chưa có tỷ giá → web hiện USD và khoá mục VND.
  • Tỷ giá lấy từ endpoint công khai mà trang Binance P2P dùng (p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search, không cần key) với fiat=VND, asset=USDT, tradeType=SELL: median giá của 10 quảng cáo đầu, tức giá bán USDT (số đồng thực nhận).
  • Scheduler gọi fx:sync-usdt-vnd mỗi phút; lần gọi Binance thật chỉ chạy khi đã qua usdt_vnd_interval_minutes (mặc định 5, khoảng 1–60) kể từ lần thử trước. Cần ít nhất 3 quảng cáo hợp lệ và median trong khoảng 10.000–100.000, không thì giữ giá cũ. HTTP 403 / 418 / 429 → tạm dừng 15 phút.
  • Giá lưu ở bảng fx_rates (một dòng cho mỗi cặp; không để Redis vì Redis không persist). Shared prop fx.usdt_vnd đọc qua cache 60 giây.
  • Endpoint không chính thức: Binance có thể đổi hoặc chặn IP server. Khi đó web vẫn chạy với giá cũ và hiện thời điểm cập nhật cuối.
  • A $ / ₫ button next to the language button, shown only when logged in. The choice is stored in users.currency via POST /currency. The menu shows "1 USDT = … ₫ · Binance P2P · x min ago" and warns when the rate is older than an hour.
  • VND = USD value × rate, rounded to whole đồng, with a ₫ suffix; chart axes use a compact form. USD keeps its usual 2-decimal format. No rate yet → the web shows USD and disables the VND option.
  • The rate comes from the public endpoint the Binance P2P page itself uses (p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search, no key) with fiat=VND, asset=USDT, tradeType=SELL: the median price of the first 10 ads, i.e. the USDT sell price (the đồng actually received).
  • The scheduler runs fx:sync-usdt-vnd every minute; the real Binance call only happens once usdt_vnd_interval_minutes (default 5, range 1–60) has passed since the last attempt. It needs at least 3 usable ads and a median between 10,000 and 100,000, otherwise the previous rate is kept. HTTP 403 / 418 / 429 → back off for 15 minutes.
  • The rate lives in the fx_rates table (one row per pair; not Redis, which has no persistence). The fx.usdt_vnd shared prop reads it through a 60-second cache.
  • The endpoint is unofficial: Binance may change it or block the server IP. The web then keeps working with the last rate and shows when it was fetched.
cd ~/goldenrobber/GoldenRobber/monitor/fxconsole
docker compose exec app php artisan fx:sync-usdt-vnd           # respects the interval / back-off
docker compose exec app php artisan fx:sync-usdt-vnd --force   # fetch now

Hệ số chia /1 · /10 · /100Money divisor /1 · /10 · /100

Một số account báo số tiền lớn hơn đơn vị thật: account cent (USC) báo ×100 (250.000 USC = 2.500 USD), vài broker báo ×10. EA không cho biết điều này, nên mỗi account có một hệ số chia và fxconsole chia số tiền cho hệ số đó ở mọi nơi hiển thị hoặc cộng tổng. /1 = giữ nguyên (standard), /100 = cent.

Some accounts report money larger than the real unit: cent (USC) accounts report ×100 (250,000 USC = 2,500 USD), a few brokers report ×10. The EA doesn't say so, so every account has a money divisor and fxconsole divides its money by it wherever money is shown or summed. /1 = as reported (standard), /100 = cent.

mt5_accounts.money_divisor NghĩaMeaning Đặt ở đâuSet where
null (mặc định)(default) Tự động: hệ số của server account trong broker_profiles.server_divisors (so khớp không phân biệt hoa thường và khoảng trắng thừa); server không có hệ số → /1Auto: the divisor of the account's server in broker_profiles.server_divisors (case- and extra-space-insensitive); a server without one → /1 Chọn /1 · /10 · /100 cạnh từng server khi sửa broker ở /admin/brokers (admin)Pick /1 · /10 · /100 next to each server when editing a broker on /admin/brokers (admin)
1 · 10 · 100 Đặt tay cho account, thắng hệ số của serverSet by hand for the account, overrides the server's divisor Menu Hệ số chia → /1, /10 hoặc /100Money divisor menu → /1, /10 or /100
  • Menu Hệ số chia áp cho các dòng đang chọn trên /deployments (chỉ chọn được dòng running hoặc failed), /admin/users/{id}/accounts và /admin/packages/{id}/accounts (chỉ dòng running). Mục "Tự động theo server của broker" đưa cột về null. Endpoint POST /deployments/bulk-divisor; user thường chỉ đổi account của mình.
  • Ba bảng trên có cột Hệ số: /1, /10 hoặc /100, thêm chữ "tự động" khi đang theo server. Account đang deploy chưa chọn được: dùng hệ số của server.
  • Account mới luôn ở chế độ tự động. Form deploy báo trước khi server đang chọn có hệ số /10 hoặc /100.
  • Hệ số thật sự tính ở một chỗ: Mt5Account::divisorByLogin() / effectiveDivisor(). Account chỉ chạy EA, không có dòng mt5_accounts, theo tên server EA báo lên (Dashboard).
  • Chia ở: Dashboard, trang account (thống kê, lệnh mở, lịch sử, thắng/thua, biểu đồ balance, báo cáo), trang VPS, admin Users và UserAccounts, ô tổng navbar/sidebar, /performance (chia phía server trước khi cộng). Cột tiền sort theo giá trị đã chia; quy đổi VND tính trên số đã chia. Nhãn /10 hoặc /100 hiện cạnh login ở Dashboard, trang account và trang VPS (không hiện cho /1).
  • Mỗi user tự chọn xem số đã chia hay số gốc: công tắc Chia theo hệ số account trong menu $ / ₫ (lưu ở users.money_divide, mặc định bật, qua POST /currency với money_divide). Chỉ đổi cách user đó nhìn, không đổi hệ số của account. Tắt thì mọi nơi ở trên hiện số gốc EA báo về (kể cả tổng và /performance) và nhãn /100 chuyển màu xám.
  • Từ 2026.09.14.2 thay cho cờ Cent / Standard cũ: migration 2026_09_14_000001 đổi is_cent true → 100, false → 1, rỗng → null, và mỗi server trong cent_servers → /100, rồi xoá hai cột cũ.
  • The Money divisor menu applies to the selected rows on /deployments (only running or failed rows are selectable), /admin/users/{id}/accounts and /admin/packages/{id}/accounts (running rows only). "Auto — follow the broker's servers" resets the column to null. Endpoint POST /deployments/bulk-divisor; regular users change only their own accounts.
  • Those three tables have a Divisor column: /1, /10 or /100, with "auto" when it follows the server. Accounts still deploying can't be selected: they use the server's divisor.
  • New accounts always start on auto. The deploy form warns when the picked server has a /10 or /100 divisor.
  • The effective divisor is computed in one place: Mt5Account::divisorByLogin() / effectiveDivisor(). EA-only accounts with no mt5_accounts row follow the server name the EA reports (Dashboard).
  • Divided on: the Dashboard, the account page (stats, open positions, history, win/loss, balance chart, reports), the VPS page, admin Users and UserAccounts, the navbar/sidebar totals, /performance (divided server-side before summing). Money columns sort by the divided value; VND conversion uses the divided value. A /10 or /100 badge shows next to the login on the Dashboard, the account page and the VPS page (none for /1).
  • Each user picks divided or raw figures: the Apply account divisor switch in the $ / ₫ menu (stored in users.money_divide, on by default, via POST /currency with money_divide). It changes only that user's view, never the account's divisor. When off, every place above shows the raw figures the EA reports (totals and /performance included) and the /100 badge turns grey.
  • Replaces the old Cent / Standard flag since 2026.09.14.2: migration 2026_09_14_000001 maps is_cent true → 100, false → 1, empty → null, and every server in cent_servers → /100, then drops both old columns.

Bearer token, secret key và token TMapBearer token, secret key and TMap token

Có nhiều loại key khác nhau, đừng nhầm. Giá trị thật chỉ nằm trong .env, bảng settings hoặc trang admin — không bao giờ trong tài liệu.

There are several distinct keys; don't mix them up. Real values live only in .env, the settings table or admin pages — never in docs.

LoạiKind Phía EA / clientEA / client side Phía serverServer side Bảo vệProtects
Account Status BearerInpApiBearer → Authorization: Bearer <secret> Setting ea_api_bearer ("EA API Bearer Token"), fallback EA_API_BEARER; trống = endpoint tắtSetting ea_api_bearer ("EA API Bearer Token"), fallback EA_API_BEARER; empty = endpoint disabled POST /api/account-status
Account Secret KeyInpAccountSecretKey → field JSONJSON field secret_key Index Redis account_status_keys:{secret_key}; khớp với users.mt5_secret_key để biết tài khoản của aiRedis index account_status_keys:{secret_key}; matched with users.mt5_secret_key to decide ownership Phân quyền xem theo user; đồng thời là khoá subscribe WS per-userPer-user visibility; also the per-user WS subscribe key
Token EA productEA product tokenInpCloudBearerToken easupported.secret_token (mỗi product một token)(one per product) /easupported/{slug}/api/tmap/*; sha256 của nó là HMAC key EA Link; its sha256 is the EA Link HMAC key
TMap Bearer (legacy)TMap Bearer (legacy)InpCloudBearerToken (EA cũ)(older EAs) tmap_secret_key → TMAP_SECRET_KEY trong .env; cũng là Cloudflare secret TMAP_SECRET_KEYin .env; also the Cloudflare secret TMAP_SECRET_KEY /api/tmap/*, Cloudflare /tmap/save|load|clear
Secret TLS AccountStatusBrokerAccountStatusBroker TLS secretInpBrokerSecret BROKER_SECRET trong .env broker (nguồn chân lý); fxconsole hiển thị read-only qua BROKER_STREAM_SECRETin the broker .env (source of truth); fxconsole shows it read-only via BROKER_STREAM_SECRET HELLO|statuspub|v2|…
Token firehose adminAdmin firehose tokenTrình duyệt admin (Inertia prop)Admin browser (Inertia prop) broker_admin_secret subscribe_all trênon /status-ws, /vps-ws
Secret nội bộ brokerBroker internal secretEaLinkBroker, VpsStreamBroker broker_internal_secret /api/internal/* (X-Broker-Secret)
Thay đổi so với tài liệu cũChanged since the old docs

monitor/README.html ghi EA_API_BEARER và TMAP_SECRET_KEY chỉ nằm trong .env. Hiện tại ea_api_bearer sửa được trên /admin/settings (DB thắng .env). Ô "TradingMap Secret" đã bị gỡ khỏi Settings: mỗi EA product có token riêng ở /admin/easupported; /api/tmap/* cũ vẫn chấp nhận TMAP_SECRET_KEY cho client cũ.

monitor/README.html says EA_API_BEARER and TMAP_SECRET_KEY live only in .env. Today ea_api_bearer is editable on /admin/settings (DB wins over .env). The "TradingMap Secret" field was removed from Settings: each EA product has its own token on /admin/easupported; the legacy /api/tmap/* still accepts TMAP_SECRET_KEY for old clients.

fxconsole TMap API

Backup thứ hai của Ticket Mapping (sau Cloudflare D1) cho EA GoldenRobber. Quy trình khôi phục: Khôi phục TMap; worker Cloudflare: Cloudflare TMap worker.

The second Ticket Mapping backup (after Cloudflare D1) for the GoldenRobber EA. Recovery procedure: TMap disaster recovery; Cloudflare worker: Cloudflare TMap worker.

File liên quanFiles

  • database/migrations/2026_04_25_000001_create_tmap_entries_table.php — schema
  • app/Models/TmapEntry.php — model + fingerprintmodel + fingerprint
  • app/Http/Controllers/Api/TmapApiController.php — save/load/clear/heartbeat token chungsave/load/clear/heartbeat with the shared token
  • app/Http/Controllers/Api/EaSupportedApiController.php — cùng chức năng theo productsame features, per product
  • config/app.php (tmap_secret_key), .env.example (TMAP_SECRET_KEY)

Bảng tmap_entriesTable tmap_entries

CộtColumnKiểuTypeMô tảDescription
idbigint PKAuto-increment
magicvarchar(32)Magic number của EAEA magic number
accountvarchar(64)Số tài khoản MT5MT5 account number
ticketunsigned bigintPosition ticket
round_idintRound ID từ commentRound ID from the comment
add_numintB#N hoặc S#N (0 = Init)B#N or S#N (0 = Init)
is_dca_posbooleantrue = DCAPos, false = DCANeg
pos_typeint0 = Buy, 1 = Sell
commentvarchar(255)Comment gốcOriginal comment
created_at / updated_attimestamp—

Index: UNIQUE(magic, account, ticket) + INDEX(magic, account).

Indexes: UNIQUE(magic, account, ticket) + INDEX(magic, account).

Hành vi savesave behaviour

  • REPLACE ALL: trong 1 transaction, DELETE WHERE magic+account rồi INSERT toàn bộ entries — không append, không merge.
  • Từ 2026.09.04.1: trước khi ghi, server tính fingerprint sha1 của payload (không phụ thuộc thứ tự entry) và so với Redis key tmap_fp:{magic}:{account} (TTL 3600s). Trùng → trả {"ok":true,"count":N,"skipped":true}, không đụng DB. Lý do: EA đẩy lại toàn bộ map mỗi 5–10s kể cả khi không đổi, gây ~873k lần ghi/ngày và ~3 GB binlog/ngày trên database 179 MB, làm đầy đĩa.
  • Redis lỗi → fail open (vẫn ghi). TTL 1 giờ bảo đảm map được ghi lại ít nhất mỗi giờ. Mọi chỗ xoá dòng ngoài save() phải gọi forgetFingerprint.
  • REPLACE ALL: in one transaction, DELETE WHERE magic+account then INSERT every entry — no append, no merge.
  • Since 2026.09.04.1: before writing, the server computes an order-independent sha1 fingerprint of the payload and compares it with Redis key tmap_fp:{magic}:{account} (TTL 3600s). Match → returns {"ok":true,"count":N,"skipped":true} without touching the DB. Why: the EA re-pushes the full map every 5–10s even when unchanged, which caused ~873k writes/day and ~3 GB of binlog per day on a 179 MB database and filled the disk.
  • Redis error → fail open (write anyway). The 1-hour TTL guarantees a rewrite at least hourly. Any code that deletes rows outside save() must call forgetFingerprint.

Setup và kiểm traSetup and checks

# Legacy: đặt TMAP_SECRET_KEY=<secret> trong .env rồi
cd monitor/fxconsole
php artisan config:clear
php artisan migrate

# Đếm entry đã sync
php artisan tinker
>>> App\Models\TmapEntry::where('magic', '21478308')->count();

# Dọn entry không cập nhật > 30 ngày
>>> App\Models\TmapEntry::where('updated_at', '<', now()->subDays(30))->delete();
# Legacy: set TMAP_SECRET_KEY=<secret> in .env, then
cd monitor/fxconsole
php artisan config:clear
php artisan migrate

# Count synced entries
php artisan tinker
>>> App\Models\TmapEntry::where('magic', '21478308')->count();

# Delete entries not updated for > 30 days
>>> App\Models\TmapEntry::where('updated_at', '<', now()->subDays(30))->delete();
Dọn dữ liệu bằng tinkerCleaning with tinker

Lệnh xoá trên đi thẳng qua model, không qua save(), nên fingerprint Redis vẫn còn tối đa 1 giờ: nếu EA gửi lại đúng map cũ, lần ghi có thể bị bỏ qua đến khi TTL hết. Xoá thêm key tmap_fp:* tương ứng nếu cần ghi lại ngay.

The delete above goes straight through the model, not save(), so the Redis fingerprint survives for up to an hour: if the EA re-sends the same map, the write may be skipped until the TTL expires. Also delete the matching tmap_fp:* key if you need an immediate rewrite.

MySQL, Redis, TimescaleDBMySQL, Redis, TimescaleDB

MySQL

Nguồn chân lý: users, settings, vpses, vps_commands, mt5_accounts, broker_profiles, lot_history, report_deals, easupported / ea_account / ea_config, tmap_entries / tmap_nodes, fx_rates (tỷ giá USDT/VND), bảng phân tích/lịch/tin. Cột mới 2026.09.13–14: users.currency, mt5_accounts.money_divisor, broker_profiles.server_divisors. VpsStreamBroker đọc vpses.secret_key và ghi vpses.account_logins; ba broker đọc settings.

Source of truth: users, settings, vpses, vps_commands, mt5_accounts, broker_profiles, lot_history, report_deals, easupported / ea_account / ea_config, tmap_entries / tmap_nodes, fx_rates (USDT/VND rate), analysis/calendar/news tables. New columns 2026.09.13–14: users.currency, mt5_accounts.money_divisor, broker_profiles.server_divisors. VpsStreamBroker reads vpses.secret_key and writes vpses.account_logins; three brokers read settings.

Redis

Trạng thái nóng và pub/sub. Prefix Laravel laravel-database- (broker phải đặt REDIS_PREFIX trùng). Không persist: sau khi Redis restart, dữ liệu được EA/agent/broker ghi lại.

Hot state and pub/sub. Laravel prefix laravel-database- (brokers must set the same REDIS_PREFIX). No persistence: after a Redis restart EAs, agents and brokers repopulate it.

TimescaleDB

Chỉ ohlc_data (qua DB::connection('timescale')). ohlc:aggregate chạy mỗi phút. Biến TIMESCALE_HOST, TIMESCALE_PORT, TIMESCALE_DATABASE, TIMESCALE_USERNAME, TIMESCALE_PASSWORD.

Only ohlc_data (via DB::connection('timescale')). ohlc:aggregate runs every minute. Variables TIMESCALE_HOST, TIMESCALE_PORT, TIMESCALE_DATABASE, TIMESCALE_USERNAME, TIMESCALE_PASSWORD.

Key / channel Redis (không prefix)Redis key / channel (unprefixed)Ai ghiWriterNội dungContents
account_status:{id}, account_positions:{id}, account_experts:{id}, account_today_history:{id}Laravel, AccountStatusBrokerSnapshot tài khoản, TTL 48hAccount snapshot, TTL 48h
account_status_keys:{secret_key}, account_status_all_secret_keysLaravel, AccountStatusBrokerIndex tài khoản theo secret keyAccount index by secret key
vps:agent:{id}:heartbeat, vps:agent:{id}:intervalVpsStreamBroker, LaravelHeartbeat agent (TTL 15s), interval hot-reloadAgent heartbeat (TTL 15s), hot-reload interval
ea_link:presence:{slug}:{composite_id}:{magic}EaLinkBrokerHash presence EA Link (TTL 90s), web coi là live nếu < 60sEA Link presence hash (TTL 90s); the web treats < 60s as live
app_config:broker_admin_secret, app_config:broker_internal_secret, app_config:hb_interval_secs, app_config:stream_cadence_msLaravelMirror setting cho broker (tầng 2 sau DB)Setting mirrors for brokers (tier 2 after DB)
tmap_fp:{magic}:{account}LaravelFingerprint TMap, TTL 3600sTMap fingerprint, TTL 3600s
ticks:{broker}:{symbol}, broker_latest:{broker}, brokers, broker:{broker}:symbols, calendar:{broker}TickBrokerTick stream, giá mới nhất, lịch kinh tế (key không prefix)Tick stream, latest quotes, economic calendar (no prefix)
ChannelChannels vps:cmd, vps:state, ea:link:cfg, report:requestLaravelĐẩy lệnh VPS, trạng thái, tín hiệu config, yêu cầu báo cáo (report:request không prefix)VPS command push, state changes, config signal, report request (report:request is unprefixed)
Số liệu tức thờiEphemeral metrics

CPU/RAM/disk/net của VPS chỉ sống trong Redis/stream, không lưu DB: hiển thị giá trị "lần cuối" cũ sẽ đánh lừa người xem. Riêng số tài khoản trên VPS: màn hình hiển thị dùng Redis heartbeat rồi fallback cột DB; màn hình quyết định deploy đếm mt5_accounts.

VPS CPU/RAM/disk/net live only in Redis/the stream, never the DB: a stale "last known" value would mislead viewers. VPS account count: display surfaces use the Redis heartbeat with a DB-column fallback; deploy-decision surfaces count mt5_accounts.

Trang /admin/settingsThe /admin/settings page

Setting lưu ở bảng MySQL settings; app_setting(key) đọc DB trước, trống thì dùng giá trị .env. Ô trống khi lưu = xoá override, quay về .env. Ô readonly chỉ hiển thị vì nguồn chân lý nằm ngoài app. Trang cũng hiện app_version để xác nhận build đang chạy. Hạ tầng Laravel thuần (DB_*, REDIS_*, APP_KEY/ENV/DEBUG/URL) không sửa ở đây. Setting phân tích thị trường đã chuyển sang /admin/analysis; thông tin Proxmox chuyển sang từng node ở /admin/proxmox/nodes.

Settings are stored in the MySQL settings table; app_setting(key) reads the DB first and falls back to .env. Saving a blank field deletes the override and falls back to .env. Readonly fields are display-only because their source of truth lives outside the app. The page also shows app_version to confirm the live build. Pure Laravel infra (DB_*, REDIS_*, APP_KEY/ENV/DEBUG/URL) is not editable here. Market-analysis settings moved to /admin/analysis; Proxmox credentials moved to per-node rows on /admin/proxmox/nodes.

KeyLabelEnvÝ nghĩaMeaning
ea_api_bearerEA API Bearer TokenEA_API_BEARERBearer của AccountStatusUpdater. Trống = endpoint tắt.AccountStatusUpdater bearer. Empty = endpoint disabled.
favicon_url, html_title, page_heading, app_sloganFavicon URL, Browser tab title, Dashboard heading, Header sloganFAVICON_URL, APP_HTML_TITLE, APP_PAGE_HEADING, APP_SLOGANNhận diện giao diện.Branding.
app_public_urlPublic app URLAPP_PUBLIC_URLURL công khai để sinh link tuyệt đối trong email và url()/route(), thay cho sửa APP_URL + restart. Không có / cuối.Public URL for absolute links in emails and url()/route(), instead of editing APP_URL + restarting. No trailing /.
datetime_format, timezoneDatetime format, App timezoneAPP_DATETIME_FORMAT, APP_TIMEZONEPattern date() của PHP; timezone của app và Carbon.PHP date() pattern; app and Carbon timezone.
staleness_multiplierStaleness multiplierSTALENESS_MULTIPLIERNgưỡng "lost" = interval auto refresh × hệ số (1–20)."Lost" threshold = auto-refresh interval × this (1–20).
default_heartbeat_intervalDefault heartbeat interval (s)DEFAULT_HEARTBEAT_INTERVALGiây giữa 2 heartbeat agent, dùng cho VPS mới tạo (1–3600).Seconds between agent heartbeats, seeded into newly created VPS rows (1–3600).
mail_enabled, mail_mailer, mail_host, mail_port, mail_scheme, mail_username, mail_password, mail_from_address, mail_from_nameMailMAIL_*SMTP (Mailjet), áp lại mỗi request nên đổi credential không cần restart. mail_enabled tắt = mọi mail đi vào transport array (bỏ im lặng) — dùng khi bảo trì.SMTP (Mailjet), re-applied per request so credentials rotate without a restart. mail_enabled off = all mail goes to the array transport (silently discarded) — use during maintenance.
mail_type_password_otp, mail_type_equity_drop, mail_type_deploy_status, mail_type_report_ready, mail_type_analysis_digest, mail_type_account_blockedMail — per-typeMAIL_TYPE_*Mỗi loại mail một checkbox, kiểm tra bởi App\Support\MailType trước khi dựng mail nên loại bị tắt không vào queue. Trống = bật. Tắt một loại không ảnh hưởng chuông trong app; riêng mail_type_password_otp tắt là không ai đổi/quên mật khẩu được. mail_enabled tắt vẫn đè lên tất cả.One checkbox per mail kind, checked by App\Support\MailType before the Mailable is built, so a disabled kind never reaches the queue. Blank = on. Turning one off leaves the in-app bell untouched; mail_type_password_otp off means nobody can reset or change a password. mail_enabled off still overrides them all.
hb_interval_secsStream: HB cadence (s)HB_INTERVAL_SECSNhịp frame keep-alive tối giản của EA AccountStatusUpdater khi không ai xem (5–120s).Cadence of the AccountStatusUpdater EA's minimal keep-alive frame while nobody watches (5–120s).
stream_cadence_msStream: full-push cadence (ms)STREAM_CADENCE_MSNhịp push đầy đủ khi có trình duyệt subscribe (0–30000; 0 = mỗi tick, giới hạn bởi InpMinPushIntervalMs; mặc định 900).Full-push cadence while a browser is subscribed (0–30000; 0 = per tick, capped by InpMinPushIntervalMs; default 900).
broker_admin_secretBroker admin firehose tokenBROKER_ADMIN_SECRETCho admin mở stream "mọi tài khoản" trên AccountStatusBroker và VpsStreamBroker. Trống ở mọi nơi = tắt Stream, quay về polling.Lets admins open the "all accounts" stream on AccountStatusBroker and VpsStreamBroker. Empty everywhere = Stream mode off, polling only.
broker_internal_secretEALinkBroker Secret KeyBROKER_INTERNAL_SECRETX-Broker-Secret cho call broker → Laravel. Dùng chung cho EaLinkBroker (/api/internal/ea-link/key) và VpsStreamBroker (/api/internal/broker/command-ack): đổi là ảnh hưởng cả hai. Trống ở mọi nơi = call nội bộ fail closed.X-Broker-Secret for broker → Laravel calls. Shared by EaLinkBroker (/api/internal/ea-link/key) and VpsStreamBroker (/api/internal/broker/command-ack): rotating it affects both. Empty everywhere = internal calls fail closed.
broker_stream_secretBroker stream secret readonlyBROKER_STREAM_SECRETHiển thị để copy vào InpBrokerSecret; phải trùng BROKER_SECRET trong .env của AccountStatusBroker.Shown for copying into InpBrokerSecret; must equal BROKER_SECRET in the AccountStatusBroker .env.
guac_json_secret_keyGuacamole JSON Auth secret readonlyGUAC_JSON_SECRET_KEYKhoá ký token remote desktop; phải trùng JSON_SECRET_KEY của container guacamole.Signs remote-desktop tokens; must equal the guacamole container's JSON_SECRET_KEY.
guac_public_urlGuacamole public URLGUAC_PUBLIC_URLURL HTTPS của webapp Guacamole, không / cuối. Trống = tắt nút Connect.HTTPS URL of the Guacamole webapp, no trailing /. Empty = Connect button disabled.
vps_init_bootstrap_tokenVPS init bootstrap tokenVPS_INIT_BOOTSTRAP_TOKENBearer cho /api/agent/init-config, nướng sẵn vào vps-agent.ini.init-template. Trống = 503.Bearer for /api/agent/init-config, baked into vps-agent.ini.init-template. Empty = 503.
vps_broker_host, vps_broker_port, vps_broker_urlVpsStreamBroker hostname / port / connection stringVPS_BROKER_HOST, VPS_BROKER_PORT, VPS_BROKER_URLDùng khi cài agent để render stunnel.conf. Host phải trùng CN cert (CERT_HOST); port mặc định 9605; connection string trống = tự ghép tls://host:port.Used at agent install to render stunnel.conf. Host must match the cert CN (CERT_HOST); port defaults to 9605; blank connection string = derived tls://host:port.
report_schedule_frequency, report_close_time_dst, report_close_time_std, report_market_tz, report_lookback_monthsReports: …REPORT_*Lịch report:generate-all: everyday / weekend, giờ đóng cửa UTC theo mùa DST / chuẩn, timezone để xét DST (mặc định EET), số tháng lịch sử (mặc định 3).report:generate-all schedule: everyday / weekend, UTC close time for DST / standard season, timezone for the DST check (default EET), months of history (default 3).
news_retention_daysNews: keep headlines for (days)NEWS_RETENTION_DAYSSố ngày giữ tin (mặc định 2).Days to keep headlines (default 2).
usdt_vnd_interval_minutesUSDT/VND rate: update every (minutes)USDT_VND_INTERVAL_MINUTESNhóm "Tỷ giá". Số phút giữa hai lần fx:sync-usdt-vnd gọi Binance P2P (mặc định 5, 1–60). Ngắn quá dễ bị chặn IP. Xem Đổi USD ↔ VND."Exchange rate" group. Minutes between two Binance P2P calls by fx:sync-usdt-vnd (default 5, 1–60). Too short risks an IP block. See USD ↔ VND switch.

Setting mà broker đọcSettings the brokers read

Từ commit cb272d5 (fxconsole 2026.09.12.3), broker đọc theo thứ tự bảng MySQL settings → Redis app_config:* → .env của broker, cache 30s mỗi key (lỗi DB thử lại sau 5s). Nhờ đọc DB trước, deploy hoặc Redis restart không làm mất giá trị trước khi admin mở trang. Broker cần DB_* trong .env của nó (deploy.sh tự điền từ monitor/fxconsole/.env).

Since commit cb272d5 (fxconsole 2026.09.12.3) brokers read in the order MySQL settings table → Redis app_config:* → the broker's own .env, with a 30s per-key cache (DB failures retry after 5s). Because the DB is read first, a deploy or Redis restart no longer blanks values until an admin opens a page. Brokers need DB_* in their .env (deploy.sh fills them from monitor/fxconsole/.env).

SettingAccountStatusBrokerVpsStreamBrokerEaLinkBroker
broker_admin_secret✓✓—
broker_internal_secret—✓✓
hb_interval_secs✓——
stream_cadence_ms✓——

Laravel vẫn ghi mirror Redis mỗi khi lưu Settings (BrokerAdminSecret::syncToRedis(), BrokerInternalSecret::syncToRedis(), StreamConfig::syncToRedis()) và mỗi lần admin tải trang (HandleInertiaRequests). TickBroker không đọc setting nào: secret của nó chỉ là TICK_BROKER_SECRET trong .env.

Laravel still writes the Redis mirrors on every Settings save (BrokerAdminSecret::syncToRedis(), BrokerInternalSecret::syncToRedis(), StreamConfig::syncToRedis()) and on every admin page load (HandleInertiaRequests). TickBroker reads no settings: its only secret is TICK_BROKER_SECRET in .env.

Comment .env.example đã cũStale .env.example comments

Comment trong monitor/fxconsole/.env.example ghi BROKER_INTERNAL_SECRET "phải khớp chính xác" với .env của VpsStreamBroker và "rotate bằng cách restart cả hai container". Đó là hành vi trước cb272d5. Hiện tại chỉ cần đổi trên /admin/settings; broker nhận trong ~30s. Giá trị .env chỉ là tầng cuối cùng.

The comment in monitor/fxconsole/.env.example says BROKER_INTERNAL_SECRET "MUST exactly match" the VpsStreamBroker .env and to "rotate by bouncing both containers". That was the behaviour before cb272d5. Today you change it on /admin/settings and brokers pick it up within ~30s; the .env value is only the last tier.

Phân tích, báo cáo và schedulerAnalysis, reports and the scheduler

  • Phân tích thị trường: /admin/analysis quản lý engine, model, interval, key Finnhub, URL sidecar, danh sách cặp (broker, symbol) và prompt tuỳ chỉnh, nút chạy từng cặp / chạy tất cả, trạng thái sidecar và đăng nhập lại OAuth. analysis:run gọi http://claude-cli:8788 (POST /analyze). Nội dung nhận định sinh bằng tiếng Việt, không dịch. Gói subscription chạy nền 24/7 là vùng xám điều khoản: giữ số cặp ít và interval thưa.
  • Báo cáo: user bấm Generate → Laravel lưu yêu cầu và publish report:request → AccountStatusBroker đẩy report_request tới EA → EA stream deal theo từng tuần qua POST /api/account-status. Chunk đầu xoá deal cũ, chunk cuối tính tổng kết và xoá yêu cầu. Admin có nút "Run reports now" (POST /admin/reports/run-now).
  • Market analysis: /admin/analysis manages the engine, model, interval, Finnhub key, sidecar URL, the (broker, symbol) watchlist and custom prompts, per-pair / run-all buttons, sidecar health and OAuth re-login. analysis:run calls http://claude-cli:8788 (POST /analyze). Narratives are generated in Vietnamese and not translated. Running a subscription 24/7 from a backend is a terms-of-service grey area: keep pairs few and intervals relaxed.
  • Reports: a user clicks Generate → Laravel stores the request and publishes report:request → AccountStatusBroker pushes report_request to the EA → the EA streams deals week by week through POST /api/account-status. The first chunk truncates old deals, the last computes the summary and clears the request. Admins have "Run reports now" (POST /admin/reports/run-now).
Lệnh lịchScheduled commandNhịpCadenceViệcJob
ohlc:aggregatemỗi phútevery minuteGộp tick thành nến OHLCAggregates ticks into OHLC bars
brokers:sync5 phút5 minĐồng bộ danh sách brokerSyncs the broker list
calendar:sync5 phút5 minMirror lịch kinh tế từ Redis calendar:{broker}Mirrors the economic calendar from Redis calendar:{broker}
news:sync2 phút2 minTin RSS + dọn theo news_retention_daysRSS headlines + prune by news_retention_days
fx:sync-usdt-vndmỗi phút, gọi Binance khi đủ usdt_vnd_interval_minutesevery minute, calls Binance once usdt_vnd_interval_minutes has passedTỷ giá USDT/VND (median 10 quảng cáo SELL Binance P2P) vào fx_ratesUSDT/VND rate (median of 10 Binance P2P SELL ads) into fx_rates
report:generate-allmỗi phút, chỉ chạy khi tới giờ theo setting Reportsevery minute, runs only when due per the Reports settingsYêu cầu báo cáo cho mọi tài khoảnRequests reports for every account
analysis:run --scope=global / --scope=weeklymỗi phút, khi tới hạnevery minute, when dueSinh thẻ phân tíchBuilds analysis cards
fxconsole:scan-lost-vpses, fxconsole:purge-removed-accounts5 phút5 minĐánh dấu VPS mất kết nối, dọn tài khoản đã gỡFlags lost VPSs, purges removed accounts
fxconsole:detect-mt5-drift10 phút10 minPhát hiện MT5 lệch trạng tháiDetects MT5 state drift
fxconsole:purge-completed-commands, fxconsole:close-stale-remote-sessionsmỗi giờhourlyDọn lệnh VPS xong, đóng phiên remote treoPurges finished VPS commands, closes stale remote sessions
proxmox:finish-provisionsmỗi phútevery minuteHoàn tất VM clone từ ProxmoxFinishes Proxmox-cloned VMs

Phiên remote GuacamoleGuacamole remote sessions

  1. Admin bấm Connect trên /vps/{id} → POST /admin/vps/{id}/remote-session.Admin clicks Connect on /vps/{id} → POST /admin/vps/{id}/remote-session.
  2. 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 (32 hex), ghi dòng audit.The server decrypts the VPS's VNC password, builds a JSON connection descriptor (protocol=vnc), signs it with HMAC-SHA256 and encrypts with AES-128-CBC using GUAC_JSON_SECRET_KEY (32 hex), and writes an audit row.
  3. Trả URL <GUAC_PUBLIC_URL>/?data=<base64> hết hạn sau 60s; trình duyệt mở tab mới, Guacamole giải mã và gọi guacd.Returns <GUAC_PUBLIC_URL>/?data=<base64> valid for 60s; the browser opens a new tab, Guacamole decodes it and dials guacd.

Dùng VNC thay RDP vì RDP khoá desktop console khi ngắt, làm hỏng thao tác GUI tự động của CmdAgent (inject URL WebRequest vào MT5 bằng SendInput). Thiếu guac_json_secret_key hoặc guac_public_url → 503. Chi tiết: Guacamole.

VNC is used instead of RDP because RDP locks the console desktop on disconnect, breaking CmdAgent's GUI automation (injecting the MT5 WebRequest URL via SendInput). Missing guac_json_secret_key or guac_public_url → 503. Details: Guacamole.

User và quyềnUsers and roles

  • Hai role: admin và user (User::isAdmin()). Middleware admin trả 403 cho mọi route /admin/* với user thường.
  • max_deployed_accounts: hạn mức tự deploy. blocked_at: user bị khoá không đăng nhập được và bị đá khỏi session.
  • mt5_accounts.user_id = account được gán cho user: quyết định /deployments, /easupported (EA của tôi), ô tổng trên navbar và /performance. Dashboard vẫn khớp theo mt5_secret_key.
  • mt5_secret_key: khoá gắn tài khoản MT5 với user (trùng InpAccountSecretKey trên EA). Đổi / sinh lại ở /settings.
  • Admin: đổi role, hạn mức, khoá, chuyển toàn bộ tài khoản sang user khác (/admin/users/{id}/reassign-all) hoặc từng tài khoản (/deployments/{id}/reassign). Lưu ý: chuyển chủ không đổi secret key đang cài trên EA.
  • Two roles: admin and user (User::isAdmin()). The admin middleware returns 403 on every /admin/* route for regular users.
  • max_deployed_accounts: self-deploy quota. blocked_at: a blocked user cannot log in and is kicked out of their session.
  • mt5_accounts.user_id = the accounts assigned to the user: drives /deployments, /easupported (My EAs), the navbar totals and /performance. The Dashboard still matches by mt5_secret_key.
  • mt5_secret_key: ties MT5 accounts to the user (equals InpAccountSecretKey on the EA). Change / regenerate at /settings.
  • Admins: change role, quota, block, move all accounts to another user (/admin/users/{id}/reassign-all) or a single account (/deployments/{id}/reassign). Note: reassigning ownership does not change the secret key already set on the EA.

Song ngữ (i18n)Bilingual UI (i18n)

  • Từ điển resources/js/lib/i18n/vi.ts và en.ts, kiểu ở types.ts. Tiếng Việt là nguồn/mặc định: t(key) fallback về VI rồi về chính key.
  • Trong page: const t = useT(); rồi t('ns.key'). Mảng label dựng bằng hàm build(t).
  • Locale là shared Inertia prop (user.locale, hoặc session, mặc định vi); đổi qua POST /locale.
  • Thêm ngôn ngữ: tạo <lang>.ts (copy key của vi.ts), thêm vào MESSAGES, thêm lựa chọn trong LanguageSwitcher.tsx và validation phía server (LocaleController + HandleInertiaRequests). Không cần sửa page.
  • Nội dung phân tích giữ tiếng Việt.
  • Dictionaries resources/js/lib/i18n/vi.ts and en.ts, types in types.ts. Vietnamese is the source/default: t(key) falls back to VI, then to the raw key.
  • In a page: const t = useT(); then t('ns.key'). Label arrays are built with a build(t) function.
  • The locale is a shared Inertia prop (user.locale, else session, default vi); switched via POST /locale.
  • Adding a language: create <lang>.ts (copy the keys of vi.ts), add it to MESSAGES, add an option in LanguageSwitcher.tsx and the server-side validation (LocaleController + HandleInertiaRequests). No page files change.
  • Analysis content stays in Vietnamese.

Quy tắc versionVersioning rule

Bump app_version mỗi lần sửa codeBump app_version on every code change

Mọi thay đổi fxconsole (backend hay frontend) phải bump app_version trong config/app.php. Định dạng CalVer YYYY.MM.DD.N: ngày hôm nay + số thứ tự trong ngày, sang ngày mới thì N về 1. Giá trị hiện trên /admin/settings để xác nhận build đã lên sau git pull + restart. Hiện tại: 2026.09.14.1 (có thể override bằng APP_VERSION). EA có quy tắc riêng: AccountStatusUpdater.mq5 dùng #property version, GoldenRobber.mq5 dùng GR_BUILD.

Every fxconsole change (backend or frontend) bumps app_version in config/app.php. Format is CalVer YYYY.MM.DD.N: today's date + same-day counter, reset N to 1 on a new day. The value is shown on /admin/settings so you can confirm the build is live after git pull + restart. Current: 2026.09.14.1 (overridable with APP_VERSION). EAs follow their own rule: AccountStatusUpdater.mq5 uses #property version, GoldenRobber.mq5 uses GR_BUILD.

VersionThay đổiChange
2026.09.04.1Bỏ ghi lại TMap khi map không đổi (fingerprint Redis)Skip TMap rewrite when the map is unchanged (Redis fingerprint)
2026.09.12.1EA Link hot-reload realtime (GoldenRobber 1.42 b1, EaLinkBroker)EA Link realtime hot-reload (GoldenRobber 1.42 b1, EaLinkBroker)
2026.09.12.2Label broker_internal_secret đổi thành "EALinkBroker Secret Key"broker_internal_secret label renamed to "EALinkBroker Secret Key"
2026.09.12.3Broker đọc secret/setting thẳng từ bảng settings (MySQL)Brokers read secrets/settings straight from the settings table (MySQL)
2026.09.13.1Thông báo "deploy thành công" mở /dashboard/accounts/{login}"Deploy succeeded" notifications open /dashboard/accounts/{login}
2026.09.13.2Trang EA của tôi (/easupported) cho user; bảng account/payload dùng toolbar chungMy EAs page (/easupported) for users; account/payload tables use the shared toolbar
2026.09.13.3Nút đổi USD ↔ VND theo tỷ giá Binance P2P (fx:sync-usdt-vnd, bảng fx_rates)USD ↔ VND switch at the Binance P2P rate (fx:sync-usdt-vnd, fx_rates table)
2026.09.13.4/easupported khớp account được gán (thêm login + server)/easupported matches assigned accounts (adds login + server)
2026.09.13.5Account cent (USC) hiển thị ÷100 = USD (cờ theo từng account)Cent (USC) accounts shown ÷100 = USD (per-account flag)
2026.09.13.6Cent / Standard hàng loạt + tự động theo server Cent của brokerBulk Cent / Standard + automatic by broker Cent servers
2026.09.14.1/easupported khớp login + tên broker (account GoldenRobber); heartbeat EA đồng bộ composite_id/easupported matches login + broker name (GoldenRobber accounts); the EA heartbeat syncs composite_id
2026.09.14.2Hệ số chia /1 · /10 · /100 thay cho Cent / Standard (theo account và theo server)Money divisor /1 · /10 · /100 replaces Cent / Standard (per account and per server)
2026.09.14.3Modal Nhà môi giới cuộn được, nút Lưu / Hủy luôn hiệnBroker modal scrolls, Save / Cancel always visible
2026.09.16.1Công tắc chia theo hệ số / xem số gốc cho từng user trong menu tiền tệPer-user divided / raw figures switch in the currency menu
2026.09.17.1Mô tả gói triển khai là ô nhiều dòng (release note), giữ xuống dòng khi hiển thịDeployment package description is a multi-line box (release note), line breaks kept when shown
2026.09.22.1Chart "Biến động số dư theo ngày" chọn được kiểu đường / cộtThe daily balance chart can switch between line and bars
2026.09.28.2/easupported và /admin/easupported: ẩn account có heartbeat cuối quá 5 ngày/easupported and /admin/easupported: hide accounts whose last heartbeat is more than 5 days old
2026.09.28.3/vps: header nhóm Proxmox node hiện thêm tổng tài khoản MT5 (vd. (5 VPS · 26 / 38 tài khoản))/vps: Proxmox node group headers also show the MT5 account total (e.g. (5 VPS · 26 / 38 accounts))
2026.09.28.4Cột Node (VPS) ở Dashboard, trang account và báo cáo IP tìm VPS theo login, hiện tên VPS hiện tại (đổi tên VPS không còn bị giữ tên cũ)The Node (VPS) cell on the Dashboard, account page and IP report finds the VPS by login and shows its current name (a renamed VPS no longer shows its old name)
2026.09.29.1Lưu VPS đã liên kết Proxmox trên /vps cũng đổi tên VM trên PVE cho khớp (PVE lỗi thì vẫn lưu, banner báo lỗi); tên VPS Proxmox chỉ gồm chữ, số, -, .. Không đổi tên máy WindowsSaving a Proxmox-linked VPS on /vps also renames its PVE VM to match (on a PVE error the save stands and the banner reports it); Proxmox VPS names allow letters, digits, - and . only. The Windows machine name is not changed
2026.09.29.2Dialog Edit VPS có ô "Đổi tên máy Windows theo tên VPS và khởi động lại VM ngay" (mặc định tắt): xếp lệnh rename_host cho agent 1.3.4-phase11+, mọi MT5 trên VPS ngừng ~1–2 phút rồi tự mở lạiVPS Edit dialog gets "Rename the Windows machine to the VPS name and restart the VM now" (off by default): queues rename_host for agent 1.3.4-phase11+; every MT5 on the VPS stops ~1–2 minutes, then relaunches

Reverse proxy, migration và DockerReverse proxy, migrations and Docker

Reverse proxy & nhiều domainReverse proxy & multiple domains

  • Proxy phía trước phải chuyển tiếp X-Forwarded-Proto và X-Forwarded-Host; đặt TRUSTED_PROXIES=* để Laravel tin header này (và Request::ip() là IP VPS thật cho báo cáo IP).
  • Redirect hoặc form sai scheme: kiểm tra APP_URL (hoặc setting app_public_url), header forwarded, rồi chạy php artisan config:clear sau khi đổi .env.
  • EA WebRequest cần HTTPS với cert hợp lệ. Cert self-signed lỗi với GetLastError = 4014 (CERT_VERIFICATION_FAILED). Host phải có trong Tools → Options → Expert Advisors → Allow WebRequest for listed URL.
  • The front proxy must forward X-Forwarded-Proto and X-Forwarded-Host; set TRUSTED_PROXIES=* so Laravel trusts them (and Request::ip() is the real VPS IP for the IP report).
  • Wrong-scheme redirects or forms: check APP_URL (or the app_public_url setting), the forwarded headers, then run php artisan config:clear after editing .env.
  • EA WebRequest requires HTTPS with a valid certificate. Self-signed certs fail with GetLastError = 4014 (CERT_VERIFICATION_FAILED). The host must be in Tools → Options → Expert Advisors → Allow WebRequest for listed URL.

Chạy và cập nhậtRun and update

# Dev bằng Docker, mở http://localhost:8080
cd monitor/fxconsole
docker compose up --build

# Sau khi pull code mới (container app tự migrate khi boot; chạy tay nếu cần)
php artisan migrate
php artisan config:clear   # khi thêm config mới

# Production: toàn bộ fxconsole + 4 broker
bash deploy.sh
# Dev with Docker, open http://localhost:8080
cd monitor/fxconsole
docker compose up --build

# After pulling new code (the app container migrates on boot; run by hand if needed)
php artisan migrate
php artisan config:clear   # when new config was added

# Production: fxconsole + the 4 brokers
bash deploy.sh

deploy.sh (gốc repo) chạy git restore ., checkout + pull main, down / up -d --build fxconsole, rồi từng broker (AccountStatusBroker, TickBroker, VpsStreamBroker, EaLinkBroker) sau khi tự tạo .env từ .env.example và điền DB_*. Lần đầu (hoặc khi refresh token hết hạn) phải đăng nhập claude-cli bằng tay: docker compose exec -it claude-cli claude login. Hướng dẫn đầy đủ: Hướng dẫn deploy.

deploy.sh (repo root) runs git restore ., checks out + pulls main, down / up -d --build for fxconsole, then each broker (AccountStatusBroker, TickBroker, VpsStreamBroker, EaLinkBroker) after creating its .env from .env.example and filling DB_*. On first run (or when the refresh token expires) log in to claude-cli by hand: docker compose exec -it claude-cli claude login. Full guide: Deployment guide.

APP_KEYAPP_KEY

Không bao giờ chạy php artisan key:generate --force trên instance đang có dữ liệu: đổi key làm mọi cột mã hoá (vpses.vnc_password, mt5_accounts.password_encrypted, cookie session…) không giải mã được nữa. Container chỉ tạo key khi .env chưa có.

Never run php artisan key:generate --force on an instance with data: a new key makes every encrypted column (vpses.vnc_password, mt5_accounts.password_encrypted, session cookies…) undecryptable. The container only generates a key when .env has none.