EA GoldenRobberGoldenRobber EA

Khôi phục TMap khi MT5 sậpTMap disaster recovery

Cách GoldenRobber lưu Ticket Map ra file và lên cloud, để một MT5 khác (máy khác, cùng magic và account) nhận lại đúng round của từng position sau khi máy chính sập. How GoldenRobber stores its Ticket Map locally and in the cloud, so that another MT5 (different machine, same magic and account) can recover the round of every position after the main machine crashes.

  • Cập nhậtUpdated 18/09/2026
  • NguồnSource docs/TMAP_DISASTER_RECOVERY_GUIDE.md
  • monitor/README.html
  • cloudflare-tmap-worker/
  • GoldenRobber-1.42.mq5
  • monitor/fxconsole/app/Http/Controllers/Api/EaSupportedApiController.php
Trạng thái hiện tại (13/09/2026)Current state (13/09/2026)
  • Production chỉ dùng local file + fxconsole MySQL. InpCloudCfDomain để trống trong config ổn định, nên Cloudflare Worker + D1 không được dùng.
  • EA gọi endpoint theo từng EA: /easupported/{slug}/api/tmap/{save,load,clear,heartbeat}, xác thực bằng bearer riêng của product (easupported.secret_token). InpCloudMyApiDomain = https://fxconsole.gamelau.uk/easupported/goldenrobber.
  • Từ fxconsole 2026.09.04.1, save bỏ qua ghi DB khi map không đổi (fingerprint trong Redis).
  • Các phần đánh dấu LEGACY (secret toàn cục TMAP_SECRET_KEY, endpoint /api/tmap/*, input InpCloudSecretKey / InpCloudCfUrl / InpCloudMyApiUrl) chỉ giữ để tham khảo.
  • Production uses only the local file + fxconsole MySQL. InpCloudCfDomain is empty in the stable config, so the Cloudflare Worker + D1 is not used.
  • The EA calls per-EA endpoints: /easupported/{slug}/api/tmap/{save,load,clear,heartbeat}, authenticated with the product's own bearer (easupported.secret_token). InpCloudMyApiDomain = https://fxconsole.gamelau.uk/easupported/goldenrobber.
  • Since fxconsole 2026.09.04.1, save skips the DB write when the map is unchanged (fingerprint in Redis).
  • Parts marked LEGACY (global secret TMAP_SECRET_KEY, /api/tmap/* endpoints, inputs InpCloudSecretKey / InpCloudCfUrl / InpCloudMyApiUrl) are kept for reference only.

Vấn đề và giải phápProblem and solution

Vấn đề: EA GoldenRobber dùng Ticket Mapping để track partial close (position đổi ticket sau khi đóng partial trên tài khoản hedging, làm mất comment gốc). Nếu MT5 sập và mở instance khác ở máy khác, mapping bị mất → EA không biết position thuộc round nào → logic Tia/SingleRoundMode sai.

Giải pháp: lưu TMap qua 3 phương thức song song:

Problem: the GoldenRobber EA uses Ticket Mapping to track partial closes (on a hedging account a position gets a new ticket after a partial close and loses its original comment). If MT5 crashes and another instance is opened on a different machine, the mapping is lost → the EA no longer knows which round a position belongs to → Tia/SingleRoundMode logic goes wrong.

Solution: store the TMap through 3 parallel methods:

# Phương thứcMethod Khi nào hoạt độngWhen it runs Ưu điểmAdvantage ProductionProduction
1 Local file GR_TicketMap_<magic>.csv trong thư mục dữ liệu chung của terminal (FILE_COMMON)Local file GR_TicketMap_<magic>.csv in the terminals' common data folder (FILE_COMMON) Luôn (cả backtest)Always (backtests too) Nhanh nhất; lưu trữ chínhFastest; primary storage đang dùngin use
2 Cloudflare Worker + D1 Chỉ live, khi InpCloudCfDomain có giá trịLive only, when InpCloudCfDomain is set HA toàn cầu, free tier 100k req/ngàyGlobal HA, free tier 100k req/day không dùngnot used
3 fxconsole REST API → MySQL Chỉ liveLive only Dự phòng nếu Cloudflare sập; tự hostBackup if Cloudflare is down; self-hosted đang dùngin use

Định danh mỗi instanceIdentifying each instance

Key duy nhất trên cloud = magic + account. Mở MT5 ở máy khác với cùng config (cùng magic, cùng account) thì EA tự load TMap từ cloud. File local chỉ đặt tên theo magic, vì nó nằm trên máy của chính terminal đó.

The unique key in the cloud = magic + account. Opening MT5 on another machine with the same config (same magic, same account) makes the EA load the TMap from the cloud automatically. The local file is named by magic only, since it lives on that terminal's own machine.

Kiến trúcArchitecture

flowchart LR
  A["MT5 instance A (chính)<br/>GoldenRobber"]
  F[("Local file<br/>GR_TicketMap_magic.csv")]
  CF[("Cloudflare Worker → D1<br/>/tmap/save")]
  MY[("fxconsole → MySQL<br/>/easupported/goldenrobber/api/tmap/save")]
  A -- "luôn" --> F
  A -. "live, chỉ khi InpCloudCfDomain có giá trị" .-> CF
  A -- "live, chỉ leader" --> MY

Lưu: file local mỗi lần gọi; cloud theo chu kỳ InpCloudSyncIntervalSec và chỉ khi node là leader. Nét đứt = không dùng trong production.

flowchart TB
  S(["OnInit trên máy B"]) --> L1{"Local file có dữ liệu?"}
  L1 -- "có" --> RB
  L1 -- "trống, Sync bật, live" --> C1{"CF domain có giá trị và load OK?"}
  C1 -- "có" --> SF
  C1 -- "không" --> M1{"MyApi load OK?"}
  M1 -- "có" --> SF["TicketMapSaveFile(): cache local"]
  M1 -- "không" --> RB
  SF --> RB["TicketMapRebuild(): bổ sung position đang mở chưa có trong map"]
  RB --> HB["Heartbeat đầu tiên: leader hay follower"]

Nạp khi khởi động, đúng theo code 1.42. TicketMapRebuild() luôn chạy, không chỉ khi mọi nguồn đều lỗi.

flowchart LR
  A["MT5 instance A (main)<br/>GoldenRobber"]
  F[("Local file<br/>GR_TicketMap_magic.csv")]
  CF[("Cloudflare Worker → D1<br/>/tmap/save")]
  MY[("fxconsole → MySQL<br/>/easupported/goldenrobber/api/tmap/save")]
  A -- "always" --> F
  A -. "live, only when InpCloudCfDomain is set" .-> CF
  A -- "live, leader only" --> MY

Save: the local file on every call; the cloud on the InpCloudSyncIntervalSec cycle and only when the node is leader. Dashed = not used in production.

flowchart TB
  S(["OnInit on machine B"]) --> L1{"Local file has data?"}
  L1 -- "yes" --> RB
  L1 -- "empty, Sync on, live" --> C1{"CF domain set and load OK?"}
  C1 -- "yes" --> SF
  C1 -- "no" --> M1{"MyApi load OK?"}
  M1 -- "yes" --> SF["TicketMapSaveFile(): local cache"]
  M1 -- "no" --> RB
  SF --> RB["TicketMapRebuild(): add open positions missing from the map"]
  RB --> HB["First heartbeat: leader or follower"]

Loading at startup, as implemented in 1.42. TicketMapRebuild() always runs, not only when every source failed.

Sơ đồ gốc trong guideOriginal diagram from the guide

1. Local file        → Primary, hoạt động cả backtest
2. Cloudflare D1     → Cloud backup #1
3. MySQL REST API    → Cloud backup #2 (phòng CF sập)

MT5 Instance A (chính)
   ↓ save song song
   ├── Local file (luôn)
   ├── Cloudflare Worker → D1 (live only)
   └── fxconsole API → MySQL (live only)

MT5 Instance A SẬP

MT5 Instance B (máy khác, cùng magic+account)
   ↓ load priority
   1. Local file (TRỐNG vì máy mới)
   2. Cloudflare D1 → có data ✓ → dùng
   3. Nếu CF sập → MySQL → có data ✓ → dùng
   4. Fallback: rebuild từ comment positions
1. Local file        → Primary, works in backtests too
2. Cloudflare D1     → Cloud backup #1
3. MySQL REST API    → Cloud backup #2 (in case CF is down)

MT5 Instance A (main)
   ↓ saves in parallel
   ├── Local file (always)
   ├── Cloudflare Worker → D1 (live only)
   └── fxconsole API → MySQL (live only)

MT5 Instance A CRASHES

MT5 Instance B (other machine, same magic+account)
   ↓ load priority
   1. Local file (EMPTY because the machine is new)
   2. Cloudflare D1 → has data ✓ → use it
   3. If CF is down → MySQL → has data ✓ → use it
   4. Fallback: rebuild from position comments

fxconsole TMap API (MySQL)fxconsole TMap API (MySQL)

Endpoint hiện tại (theo từng EA)Current endpoints (per EA)

Base URL = InpCloudMyApiDomain + /api/tmap, ví dụ https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap. Header bắt buộc: Authorization: Bearer <secret>, trong đó secret là token của product trên /admin/easupported (EA đặt vào InpCloudBearerToken).

Base URL = InpCloudMyApiDomain + /api/tmap, for example https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap. Required header: Authorization: Bearer <secret>, where the secret is the product's token from /admin/easupported (set on the EA as InpCloudBearerToken).

Method Endpoint Hành viBehaviour Trả vềReturns
POST/easupported/{slug}/api/tmap/save REPLACE ALL cho magic+account: xoá hết rồi insert mới — hoặc bỏ qua nếu fingerprint trùngREPLACE ALL for magic+account: delete everything, then insert — or skip if the fingerprint matches {"ok":true,"count":N} · {"ok":true,"count":N,"skipped":true}
GET/easupported/{slug}/api/tmap/load?magic=&account= Đọc mapReads the map {"ok":true,"magic","account","entries":[...]}
DELETE/easupported/{slug}/api/tmap/clear?magic=&account= Xoá map và xoá fingerprintDeletes the map and its fingerprint {"ok":true,"deleted":N}
POST/easupported/{slug}/api/tmap/heartbeat Leader election + schema/telemetry + config hot-reloadLeader election + schema/telemetry + hot-reload config xem Chuẩn tích hợp §5.3see Integration standard §5.3
  • Mã lỗi: 404 Unknown EA khi slug không tồn tại · 401 Unauthorized khi token trống hoặc không khớp (so bằng hash_equals) · 422 khi thiếu magic/account hoặc sai dữ liệu.
  • Route không dùng session, cookie, CSRF hay Inertia (routes/web.php, prefix easupported/{slug}/api/tmap); xác thực bearer nằm trong controller.
  • Validate save: magic string ≤32, account string ≤64, entries array; mỗi entry có ticket, round_id, add_num (integer), is_dca_pos (boolean), pos_type (0 hoặc 1), comment (tuỳ chọn, ≤255).
  • Errors: 404 Unknown EA when the slug does not exist · 401 Unauthorized when the token is empty or does not match (compared with hash_equals) · 422 when magic/account is missing or data is invalid.
  • The routes run without session, cookies, CSRF or Inertia (routes/web.php, prefix easupported/{slug}/api/tmap); bearer authentication happens in the controller.
  • save validation: magic string ≤32, account string ≤64, entries array; each entry has ticket, round_id, add_num (integer), is_dca_pos (boolean), pos_type (0 or 1), comment (optional, ≤255).

Bỏ ghi lại khi map không đổi (fxconsole 2026.09.04.1)Skipping rewrites of an unchanged map (fxconsole 2026.09.04.1)

EA đẩy toàn bộ ticket map mỗi InpCloudSyncIntervalSec (5–10s) kể cả khi không có gì đổi, và save trước đây xoá hết + insert hết mỗi lần: ghi 2N dòng mỗi lượt, đo được khoảng 10 ghi/giây, ~873k ghi/ngày, sinh ra khoảng 3 GB binlog mỗi ngày trên một database 179 MB và cuối cùng làm đầy ổ đĩa.

  • Server tính fingerprint sha1(magic|account|json(entries đã chuẩn hoá và sort)) — không phụ thuộc thứ tự entry, vì EA không bảo đảm thứ tự giữa các lần đẩy.
  • Fingerprint lưu trong Redis key tmap_fp:{magic}:{account}, TTL 3600s: dù fingerprint có sống lâu hơn dữ liệu, map vẫn được ghi lại ít nhất mỗi giờ một lần.
  • Trùng fingerprint → trả "skipped":true, không ghi DB. Ghi thành công → lưu fingerprint mới. clear → xoá fingerprint.
  • Redis không truy cập được → fail open: vẫn ghi DB, không bao giờ bỏ qua ghi dựa trên phỏng đoán.
  • Áp dụng cho cả endpoint theo từng EA (EaSupportedApiController) lẫn endpoint legacy (TmapApiController).

The EA pushes its full ticket map every InpCloudSyncIntervalSec (5–10s) even when nothing changed, and save used to delete everything + insert everything each time: 2N rows written per sync, measured at about 10 writes/second, ~873k writes/day, producing roughly 3 GB of binlog per day on a 179 MB database and eventually filling the disk.

  • The server computes the fingerprint sha1(magic|account|json(normalised, sorted entries)) — independent of entry order, because the EA does not guarantee a stable order between pushes.
  • The fingerprint is stored in the Redis key tmap_fp:{magic}:{account} with a TTL of 3600s: even if a fingerprint outlives its rows, the map is still rewritten at least once an hour.
  • Fingerprint matches → returns "skipped":true with no DB write. Successful write → the new fingerprint is stored. clear → the fingerprint is deleted.
  • Redis unreachable → fail open: the DB write still happens; a write is never skipped on a guess.
  • Applies to both the per-EA endpoints (EaSupportedApiController) and the legacy endpoints (TmapApiController).

Request và responseRequests and responses

POST …/api/tmap/save

{
  "magic": "21478308",
  "account": "12345",
  "entries": [
    {
      "ticket": 100,
      "round_id": 5,
      "add_num": 2,
      "is_dca_pos": true,
      "pos_type": 0,
      "comment": "DCAPos R5 B#2 |add_minpips"
    }
  ]
}

GET …/api/tmap/load?magic=21478308&account=12345

{
  "ok": true,
  "magic": "21478308",
  "account": "12345",
  "entries": [...]
}

DELETE …/api/tmap/clear?magic=21478308&account=12345

{ "ok": true, "deleted": 5 }

Test bằng curlTesting with curl

Endpoint theo từng EA (hiện tại). Thay <secret> bằng token của product; không dán token vào tài liệu hay lịch sử shell dùng chung.

Per-EA endpoints (current). Replace <secret> with the product's token; never paste the token into docs or a shared shell history.

curl -X POST https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap/save \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <secret>" \
  -d '{"magic":"123","account":"test","entries":[]}'

curl "https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap/load?magic=123&account=test" \
  -H "Authorization: Bearer <secret>"

LEGACY Endpoint toàn cục trong guide gốc (your-server.com là placeholder):

LEGACY Global endpoints from the original guide (your-server.com is a placeholder):

curl -X POST https://your-server.com/api/tmap/save \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <secret>" \
  -d '{"magic":"123","account":"test","entries":[]}'

curl "https://your-server.com/api/tmap/load?magic=123&account=test" \
  -H "Authorization: Bearer <secret>"

Schema bảng tmap_entriestmap_entries table schema

Column Type Ghi chúNote
idbigintPK auto-increment
magicvarchar(32)EA magic number
accountvarchar(64)MT5 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_posbooltrue=DCAPos, false=DCANeg
pos_typeint0=Buy, 1=Sell
commentvarchar(255)Comment gốcOriginal comment
created_attimestamp
updated_attimestamp

Index: UNIQUE(magic, account, ticket) + INDEX(magic, account). Node leader election nằm ở bảng tmap_nodes (magic, account, node_id, priority, last_heartbeat).

Indexes: UNIQUE(magic, account, ticket) + INDEX(magic, account). Leader-election nodes live in the tmap_nodes table (magic, account, node_id, priority, last_heartbeat).

File liên quanRelated files

Mọi đường dẫn tính từ monitor/fxconsole/.

All paths are relative to monitor/fxconsole/.

File Chức năngPurpose
database/migrations/2026_04_25_000001_create_tmap_entries_table.phpSchema bảng tmap_entriestmap_entries table schema
app/Models/TmapEntry.phpEloquent model + fingerprint (payloadFingerprint, fingerprintMatches, rememberFingerprint, forgetFingerprint)Eloquent model + fingerprint (payloadFingerprint, fingerprintMatches, rememberFingerprint, forgetFingerprint)
app/Http/Controllers/Api/EaSupportedApiController.phpsave/load/clear/heartbeat theo từng EA, xác thực bằng easupported.secret_tokenPer-EA save/load/clear/heartbeat, authenticated with easupported.secret_token
routes/web.phpRoute easupported/{slug}/api/tmap/*easupported/{slug}/api/tmap/* routes
database/migrations/2026_08_29_000007_add_secret_token_to_easupported.phpThêm cột secret_token; backfill bằng TMAP_SECRET_KEY toàn cục cũ (hoặc chuỗi ngẫu nhiên 40 ký tự nếu không có) để EA cũ vẫn xác thực đượcAdds the secret_token column; backfills it with the old global TMAP_SECRET_KEY (or a random 40-character string if none) so existing EAs keep authenticating
app/Http/Controllers/Api/TmapApiController.php LEGACYsave/load/clear/heartbeat toàn cục /api/tmap/*Global /api/tmap/* save/load/clear/heartbeat
routes/api.php LEGACYRoute /api/tmap/*/api/tmap/* routes
config/app.php LEGACYConfig tmap_secret_keytmap_secret_key config
.env.example LEGACYTMAP_SECRET_KEY=<secret>

SetupSetup

  1. Sau khi pull code mới: php artisan migrate (hoặc bash deploy.sh trên server). Nếu có config mới: php artisan config:clear.
  2. Tạo hoặc mở EA product trên /admin/easupported, copy URL https://fxconsole.gamelau.uk/easupported/{slug} và secret token vào InpCloudMyApiDomain / InpCloudBearerToken. Trang /admin/settings không còn trường TradingMap Secret.
  1. After pulling new code: php artisan migrate (or bash deploy.sh on the server). If config was added: php artisan config:clear.
  2. Create or open the EA product on /admin/easupported and copy the URL https://fxconsole.gamelau.uk/easupported/{slug} and the secret token into InpCloudMyApiDomain / InpCloudBearerToken. The /admin/settings page no longer has a TradingMap Secret field.
cd monitor/fxconsole
php artisan migrate
php artisan config:clear

LEGACY Setup cũ cho /api/tmap/*: thêm TMAP_SECRET_KEY vào .env của fxconsole rồi migrate. Endpoint legacy vẫn nhận giá trị này nếu còn client cũ dựa vào nó.

LEGACY Old setup for /api/tmap/*: add TMAP_SECRET_KEY to fxconsole's .env, then migrate. The legacy endpoints still honour this value if an old client relies on it.

cd monitor/fxconsole
echo "TMAP_SECRET_KEY=<secret>" >> .env
php artisan config:clear
php artisan migrate

Cloudflare Worker + D1 (tuỳ chọn)Cloudflare Worker + D1 (optional)

Không dùng trong productionNot used in production
  • Worker vẫn xác thực bằng một secret toàn cục TMAP_SECRET_KEY. EA gửi cùng InpCloudBearerToken tới cả hai backend, nên nếu bật CF thì secret của Worker phải bằng đúng token của product goldenrobber.
  • Khi InpCloudCfDomain có giá trị, EA gọi heartbeat tới Cloudflare trước và dừng ở đó nếu thành công. Worker chỉ trả thông tin leader, không có hot_keys hay config, không nhận config_schema/payload — nên trong lúc CF khoẻ, hot-reload và EA Link sẽ không kéo được config từ fxconsole. Để trống InpCloudCfDomain.
  • The Worker still authenticates with one global secret, TMAP_SECRET_KEY. The EA sends the same InpCloudBearerToken to both backends, so if CF is enabled the Worker's secret must equal the goldenrobber product token exactly.
  • When InpCloudCfDomain is set, the EA sends its heartbeat to Cloudflare first and stops there on success. The Worker returns only leader information — no hot_keys, no config, and it does not accept config_schema/payload — so while CF is healthy, hot-reload and EA Link cannot pull config from fxconsole. Keep InpCloudCfDomain empty.

Files

Thư mục cloudflare-tmap-worker/:

Folder cloudflare-tmap-worker/:

File Chức năngPurpose
src/index.jsCode Worker: /tmap/save, /tmap/load, /tmap/clear, /tmap/heartbeat + Bearer authWorker code: /tmap/save, /tmap/load, /tmap/clear, /tmap/heartbeat + Bearer auth
schema.sqlSchema D1: bảng tmap_entries với UNIQUE(magic, account, ticket) và tmap_nodesD1 schema: tmap_entries with UNIQUE(magic, account, ticket), and tmap_nodes
wrangler.tomlConfig Wrangler (D1 binding)Wrangler config (D1 binding)
package.jsonnpm scriptsnpm scripts
README.mdHướng dẫn setup chi tiếtDetailed setup guide

Trang dịch vụ: Cloudflare TMap worker.

Service page: Cloudflare TMap worker.

Setup từng bướcStep-by-step setup

  1. Cài Wrangler CLI. Trình duyệt sẽ mở để đăng nhập tài khoản Cloudflare.
    cd cloudflare-tmap-worker
    npm install
    npm install -g wrangler
    wrangler login
  2. Tạo D1 database.
    npm run db:create

    Output mẫu:

    ✅ Successfully created DB 'gr-tmap'
    [[d1_databases]]
    binding = "DB"
    database_name = "gr-tmap"
    database_id = "abc123-def456-7890-..."

    Copy database_id và dán vào wrangler.toml (thay REPLACE_WITH_YOUR_D1_DATABASE_ID):

    [[d1_databases]]
    binding = "DB"
    database_name = "gr-tmap"
    database_id = "abc123-def456-7890-..."  # ← paste vào đây
  3. Apply schema (tạo bảng).
    npm run db:migrate
  4. Đặt secret. Nhập secret khi được hỏi. Secret này phải GIỐNG token EA gửi trong InpCloudBearerToken. LEGACY Guide gốc yêu cầu giống TMAP_SECRET_KEY trong .env của fxconsole và InpCloudSecretKey trong input EA.
    npm run secret:set
  5. Deploy. URL in ra là giá trị cho InpCloudCfDomain (guide gốc gọi là InpCloudCfUrl); EA tự nối thêm /tmap/{save,load,clear,heartbeat}.
    npm run deploy
    ✨ Deployed gr-tmap triggers (1 sec)
      https://gr-tmap.your-account.workers.dev
  1. Install the Wrangler CLI. A browser opens to log in to the Cloudflare account.
    cd cloudflare-tmap-worker
    npm install
    npm install -g wrangler
    wrangler login
  2. Create the D1 database.
    npm run db:create

    Sample output:

    ✅ Successfully created DB 'gr-tmap'
    [[d1_databases]]
    binding = "DB"
    database_name = "gr-tmap"
    database_id = "abc123-def456-7890-..."

    Copy the database_id and paste it into wrangler.toml (replacing REPLACE_WITH_YOUR_D1_DATABASE_ID):

    [[d1_databases]]
    binding = "DB"
    database_name = "gr-tmap"
    database_id = "abc123-def456-7890-..."  # ← paste here
  3. Apply the schema (create tables).
    npm run db:migrate
  4. Set the secret. Enter it when prompted. It MUST equal the token the EA sends in InpCloudBearerToken. LEGACY The original guide required it to match TMAP_SECRET_KEY in fxconsole's .env and the EA input InpCloudSecretKey.
    npm run secret:set
  5. Deploy. The printed URL is the value for InpCloudCfDomain (called InpCloudCfUrl in the original guide); the EA appends /tmap/{save,load,clear,heartbeat} itself.
    npm run deploy
    ✨ Deployed gr-tmap triggers (1 sec)
      https://gr-tmap.your-account.workers.dev

Test Worker đã deployTesting the deployed Worker

curl -X POST https://gr-tmap.your-account.workers.dev/tmap/save \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <secret>" \
  -d '{"magic":"123","account":"test","entries":[]}'

Giới hạn free tierFree tier limits

  • D1: 5GB storage, 5M reads/ngày, 100k writes/ngày.
  • Workers: 100k requests/ngày.

Guide gốc ước tính: EA save mỗi 30 giây + sự kiện partial close ≈ 3000 requests/ngày → đủ free. Lưu ý: với chu kỳ 10 giây như pass58, mỗi chu kỳ có 1 heartbeat + 1 save, tức khoảng 17,280 requests/ngày cho mỗi instance (ước tính số học) — vẫn dưới 100k nhưng nhiều hơn con số trong guide.

  • D1: 5GB storage, 5M reads/day, 100k writes/day.
  • Workers: 100k requests/day.

The original guide's estimate: the EA saves every 30 seconds + partial-close events ≈ 3000 requests/day → within the free tier. Note: with a 10-second cycle as in pass58, each cycle sends 1 heartbeat + 1 save, i.e. about 17,280 requests/day per instance (arithmetic estimate) — still below 100k, but well above the guide's figure.

EA GoldenRobber Cloud SyncGoldenRobber EA Cloud Sync

Input (1.42)Inputs (1.42)

Input Mặc địnhDefault pass58 1.42 b3 Mô tảDescription
InpCloudSyncEnablefalsetrueBật/tắt đồng bộ cloud (chỉ live, không chạy trong backtest)Enables cloud sync (live only, never in backtests)
InpCloudBearerToken""<secret>Bearer token: token của product trên /admin/easupported; gửi tới cả hai backendBearer token: the product's token from /admin/easupported; sent to both backends
InpCloudCfDomain""trốngemptyDomain Cloudflare Worker (vd https://gr-tmap.your-account.workers.dev); EA tự nối /tmap/{save,load,clear,heartbeat}Cloudflare Worker domain (e.g. https://gr-tmap.your-account.workers.dev); the EA appends /tmap/{save,load,clear,heartbeat}
InpCloudMyApiDomain""https://fxconsole.gamelau.uk/easupported/goldenrobberURL product trên fxconsole; EA tự nối /api/tmap/{save,load,clear,heartbeat}The product's fxconsole URL; the EA appends /api/tmap/{save,load,clear,heartbeat}
InpCloudSyncIntervalSec3010Chu kỳ heartbeat + save cloud (giây)Heartbeat + cloud save cycle (seconds)
InpCloudWebTimeoutMs50005000WebRequest timeout (ms)WebRequest timeout (ms)
InpCloudNodePriority100100Priority node (1 = cao nhất)Node priority (1 = highest)
InpCloudNodeId""trốngemptyTrống = auto từ đường dẫn terminalEmpty = auto from the terminal path
InpCloudHeartbeatTimeoutSec9090Coi node chết nếu không heartbeat trong N giâyA node is considered dead after N seconds without a heartbeat
Comment input trong code đã cũThe input comment in the code is outdated

Comment của InpCloudMyApiDomain trong GoldenRobber-1.42.mq5 vẫn ghi “DOMAIN ONLY (vd https://fxconsole.gamelau.uk)”. Giá trị đó chỉ trỏ tới endpoint legacy /api/tmap/*. Với remote config theo từng EA và EA Link, giá trị PHẢI kèm /easupported/goldenrobber.

The comment on InpCloudMyApiDomain in GoldenRobber-1.42.mq5 still says “DOMAIN ONLY (e.g. https://fxconsole.gamelau.uk)”. That value only reaches the legacy /api/tmap/* endpoints. For per-EA remote config and EA Link the value MUST include /easupported/goldenrobber.

LEGACY Nhóm input thiết kế ban đầu trong guide (trước v1.38 đổi sang *Domain và trước khi có token theo product):

LEGACY The input group as originally designed in the guide (before v1.38 renamed them to *Domain, and before per-product tokens):

=== Cloud Sync (TMap) ===
InpCloudSyncEnable     = false
InpCloudSecretKey      = <secret>
InpCloudCfUrl          = https://gr-tmap.your-account.workers.dev
InpCloudMyApiUrl       = https://your-server.com/api/tmap
InpCloudSyncIntervalSec = 30

Input multi-node và leader election: xem Chuẩn tích hợp §10. Toàn bộ input: Tham số input.

Multi-node inputs and leader election: see Integration standard §10. Every input: Input parameters.

WebRequest whitelist (BẮT BUỘC)WebRequest whitelist (REQUIRED)

Mở MT5 → Tools → Options → Expert Advisors → Allow WebRequest for listed URL và thêm origin của mọi backend đang dùng (chỉ cần origin, không cần path). Production chỉ cần https://fxconsole.gamelau.uk; guide gốc thêm cả hai:

Open MT5 → Tools → Options → Expert Advisors → Allow WebRequest for listed URL and add the origin of every backend in use (origin only, no path). Production needs only https://fxconsole.gamelau.uk; the original guide adds both:

https://gr-tmap.your-account.workers.dev
https://your-server.com

WebRequest() KHÔNG hoạt động trong Strategy Tester. EA tự bỏ qua cloud sync khi MQLInfoInteger(MQL_TESTER) == true để backtest không bị chậm. WebRequest cũng yêu cầu HTTPS với cert hợp lệ; cert self-signed sẽ lỗi GetLastError = 4014.

WebRequest() does NOT work in the Strategy Tester. The EA skips cloud sync when MQLInfoInteger(MQL_TESTER) == true so backtests are not slowed down. WebRequest also requires HTTPS with a valid certificate; a self-signed certificate fails with GetLastError = 4014.

Dòng “Cloud” trên panelThe “Cloud” panel line

Khi InpCloudSyncEnable = true, panel hiện mã HTTP của lần save gần nhất tới mỗi backend: OK khi 2xx, - khi chưa gọi (hoặc domain trống), còn lại là mã lỗi. Màu lime khi mọi backend đã cấu hình đều OK, đỏ cam khi có backend lỗi.

When InpCloudSyncEnable = true, the panel shows the HTTP code of the latest save to each backend: OK for 2xx, - when never called (or the domain is empty), otherwise the error code. Lime when every configured backend is OK, orange-red when any backend fails.

Dòng 1: production (CF trống). Dòng 2: cả hai backend OK. Dòng 3: Cloudflare lỗi 500. Dòng 4: WebRequest tới MyApi lỗi (-1).

Line 1: production (CF empty). Line 2: both backends OK. Line 3: Cloudflare returned 500. Line 4: the WebRequest to MyApi failed (-1).

Khi nào EA lưuWhen the EA saves

  • TicketMapSave(): luôn ghi file local; gọi CloudSyncPulse(). Được gọi sau init mới, add DCA, Tia close, và mỗi 30 giây ở bước dọn map trong OnTick.
  • CloudSyncPulse(): throttle theo InpCloudSyncIntervalSec, được gọi từ cả OnTick lẫn OnTimer (heartbeat chạy đúng chu kỳ dù không có tick). Mỗi lượt: heartbeat trước (leader + nhận config), rồi chỉ leader save lên cloud.
  • TicketMapSaveImmediate(): bỏ qua throttle — sau PositionClosePartial (cập nhật ticket mới) và trong OnDeinit. Follower vẫn ghi file local nhưng không sync cloud.
  • Xoá cloud: khi map vừa về 0 (clean state), TicketMapClearCloud() gửi DELETE tới mọi backend có domain.
  • Save gửi toàn bộ map (replace all), không append → không cần merge conflict. EA gửi mỗi chu kỳ dù map không đổi; server bỏ qua ghi DB (sai lệch D5).
  • TicketMapSave(): always writes the local file; calls CloudSyncPulse(). Called after a new init, an added DCA, a Tia close, and every 30 seconds in the map clean-up step in OnTick.
  • CloudSyncPulse(): throttled by InpCloudSyncIntervalSec, called from both OnTick and OnTimer (so the heartbeat keeps its cycle without ticks). Each pass: heartbeat first (leader + receive config), then only the leader saves to the cloud.
  • TicketMapSaveImmediate(): bypasses the throttle — after PositionClosePartial (to record the new ticket) and in OnDeinit. A follower still writes the local file but does not sync to the cloud.
  • Clearing the cloud: when the map has just dropped to 0 (clean state), TicketMapClearCloud() sends DELETE to every backend with a domain.
  • Save sends the whole map (replace all), never appends → no merge conflicts. The EA sends every cycle even when the map is unchanged; the server skips the DB write (deviation D5).
TicketMapSave():                 // file mỗi lần; cloud qua CloudSyncPulse (throttle InpCloudSyncIntervalSec)
  ├─ TicketMapSaveFile()          (LUÔN — kể cả backtest)
  └─ CloudSyncPulse()             (Live + Enable, OnTick + OnTimer)
     ├─ TicketMapHeartbeat()      (CF trước nếu có domain, rồi MyApi)
     └─ if leader:
        ├─ TicketMapSyncCloudflare()
        └─ TicketMapSyncMyApi()

TicketMapSaveImmediate():        // sự kiện quan trọng (partial close, OnDeinit)
  ├─ TicketMapSaveFile()
  └─ if (Live + Enable):
     ├─ TicketMapHeartbeat()
     └─ if leader: TicketMapSyncCloudflare() + TicketMapSyncMyApi()

Comments in the block above: TicketMapSave — file every time; cloud via CloudSyncPulse (throttled by InpCloudSyncIntervalSec); TicketMapSaveFile — ALWAYS, backtests included; TicketMapHeartbeat — CF first if it has a domain, then MyApi; TicketMapSaveImmediate — critical events (partial close, OnDeinit).

Khi khởi động (OnInit)At startup (OnInit)

1. TicketMapLoad()              ← local file (nhanh nhất)
2. TicketMapLoadCloudflare()    ← nếu local rỗng, sync bật, live, và có InpCloudCfDomain
3. TicketMapLoadMyApi()         ← nếu CF không có dữ liệu / lỗi
   (2 hoặc 3 thành công → TicketMapSaveFile() để cache local)
4. TicketMapRebuild()           ← LUÔN chạy: bổ sung position đang mở chưa có trong map
5. TicketMapHeartbeat()         ← heartbeat đầu tiên: leader / follower trước khi trade

Reading the block above: 1 — local file (fastest); 2 — only if local is empty, sync is on, live, and InpCloudCfDomain is set; 3 — if CF has no data or fails; when 2 or 3 succeeds, TicketMapSaveFile() caches it locally; 4 — ALWAYS runs, adding open positions missing from the map; 5 — the first heartbeat decides leader / follower before any trading.

LEGACY Phác thảo ban đầu trong guide (code thật khác ở hai điểm: TicketMapRebuild() luôn chạy bổ sung, và cloud chỉ do leader save):

LEGACY The original sketch from the guide (the real code differs in two ways: TicketMapRebuild() always runs as a supplement, and only the leader saves to the cloud):

void TicketMapSave() {
   TicketMapSaveFile();              // Always: local file
   if (!MQLInfoInteger(MQL_TESTER)) {
      TicketMapSyncCloudflare();    // Live only: Cloudflare D1
      TicketMapSyncMyApi();         // Live only: MySQL
   }
}

void OnInit() {
   // 1. Local file (nhanh nhất)
   if (TicketMapLoadFile()) return;

   // 2. Cloudflare D1
   if (!MQLInfoInteger(MQL_TESTER)) {
      if (TicketMapLoadCloudflare()) {
         TicketMapSaveFile();  // cache local
         return;
      }
   }

   // 3. MySQL fallback
   if (!MQLInfoInteger(MQL_TESTER)) {
      if (TicketMapLoadMyApi()) {
         TicketMapSaveFile();
         return;
      }
   }

   // 4. Rebuild từ comment positions (last resort)
   TicketMapRebuild();
}

Khi nPos == 0 (clean state): gọi TicketMapClear() cho cả 3 nguồn để reset.

When nPos == 0 (clean state): call TicketMapClear() on all 3 sources to reset. (Sketch comments: “nhanh nhất” = fastest; “Rebuild từ comment positions” = rebuild from position comments.)

Kịch bản phục hồiRecovery scenarios

Production hiện tại — máy chính sập, CF trốngCurrent production — main machine crashes, CF empty

Máy A: GoldenRobber, magic=21478308, account=12345, InpCloudCfDomain trống
       Mỗi InpCloudSyncIntervalSec (pass58 = 10s): heartbeat → nếu là leader: save lên fxconsole
       (server bỏ qua ghi DB khi map không đổi)
       Panel: Cloud: CF=- | MyApi=OK

Máy A SẬP

Máy B: gắn EA cùng .set (cùng magic, account, InpCloudMyApiDomain, token)
       OnInit:
         ├─ Local file: TRỐNG (máy mới)
         ├─ Cloudflare: bỏ qua (domain trống)
         ├─ MyApi: load OK ✓ → TicketMapSaveFile() cache local
         ├─ TicketMapRebuild() bổ sung từ comment
         └─ Heartbeat đầu tiên
       Leader: A ngừng heartbeat; khi heartbeat cuối của A quá InpCloudHeartbeatTimeoutSec (90s)
       thì B là node sống duy nhất → B thành leader và bắt đầu trade / save
Machine A: GoldenRobber, magic=21478308, account=12345, InpCloudCfDomain empty
           Every InpCloudSyncIntervalSec (pass58 = 10s): heartbeat → if leader: save to fxconsole
           (the server skips the DB write when the map is unchanged)
           Panel: Cloud: CF=- | MyApi=OK

Machine A CRASHES

Machine B: attach the EA with the same .set (same magic, account, InpCloudMyApiDomain, token)
           OnInit:
             ├─ Local file: EMPTY (new machine)
             ├─ Cloudflare: skipped (empty domain)
             ├─ MyApi: load OK ✓ → TicketMapSaveFile() caches locally
             ├─ TicketMapRebuild() fills in from comments
             └─ First heartbeat
           Leader: A stops heartbeating; once A's last heartbeat is older than
           InpCloudHeartbeatTimeoutSec (90s), B is the only alive node → B becomes leader and starts trading / saving
Follower trong 90 giây đầuFollower for the first 90 seconds

Nếu A có priority cao hơn (số nhỏ hơn) và heartbeat cuối của A còn trong 90 giây, server vẫn coi A là leader nên B đứng ở chế độ follower cho tới khi A hết hạn. Đặt InpCloudNodePriority khác nhau cho mỗi node; chi tiết ở Chuẩn tích hợp §10.

If A has the higher priority (smaller number) and its last heartbeat is still within 90 seconds, the server still treats A as leader, so B stays a follower until A expires. Give every node a distinct InpCloudNodePriority; details in Integration standard §10.

Scenario A — MT5 chính sập, mở MT5 ở máy khác (có CF)Scenario A — main MT5 crashes, MT5 opened on another machine (with CF)

Máy A: GoldenRobber chạy với magic=21478308, account=12345
       TMap save thường xuyên → Cloudflare D1 + fxconsole MySQL

Máy A SẬP (Windows crash, mất điện, internet down...)

Máy B: User attach EA cùng config (magic=21478308) trên account 12345
       OnInit:
         ├─ Local file: TRỐNG (máy mới chưa có file)
         ├─ Cloudflare D1: load OK ✓ (X entries)
         ├─ TicketMapSaveFile() để cache local
         └─ TicketMapRebuild() bổ sung từ comment

       → EA tiếp tục manage tickets như chưa từng sập
Machine A: GoldenRobber running with magic=21478308, account=12345
           TMap saved regularly → Cloudflare D1 + fxconsole MySQL

Machine A CRASHES (Windows crash, power cut, internet down...)

Machine B: the user attaches the EA with the same config (magic=21478308) on account 12345
           OnInit:
             ├─ Local file: EMPTY (the new machine has no file yet)
             ├─ Cloudflare D1: load OK ✓ (X entries)
             ├─ TicketMapSaveFile() to cache locally
             └─ TicketMapRebuild() fills in from comments

           → the EA keeps managing tickets as if nothing had crashed

Scenario B — Cloudflare D1 sậpScenario B — Cloudflare D1 is down

Máy A: Save → CF lỗi → MyApi OK ✓
       File local + MyApi vẫn save thành công
       Panel hiển thị: Cloud: CF=500 | MyApi=OK (đỏ)

Máy B (mới khởi động):
       OnInit → CF fail → fallback MyApi → load OK ✓
Machine A: Save → CF error → MyApi OK ✓
           Local file + MyApi still save successfully
           Panel shows: Cloud: CF=500 | MyApi=OK (red)

Machine B (just started):
           OnInit → CF fails → falls back to MyApi → load OK ✓

Scenario C — Cả CF + MyApi sậpScenario C — both CF and MyApi are down

Máy A: Save → cả 2 fail → chỉ local file save thành công
       Panel: Cloud: CF=-1 | MyApi=-1 (đỏ)
       EA log warning, vẫn hoạt động bình thường (file local OK)

Máy B (mới khởi động):
       OnInit → CF fail → MyApi fail → fallback TicketMapRebuild()
       Rebuild map từ comment positions đang mở
       → Mất data partial close trước đó nhưng EA vẫn track positions hiện tại
Machine A: Save → both fail → only the local file saves successfully
           Panel: Cloud: CF=-1 | MyApi=-1 (red)
           The EA logs a warning and keeps running normally (local file OK)

Machine B (just started):
           OnInit → CF fails → MyApi fails → falls back to TicketMapRebuild()
           Rebuilds the map from the comments of open positions
           → earlier partial-close data is lost, but the EA still tracks current positions

Trong production (CF trống), “MyApi sập” rơi thẳng vào nhánh này. Nếu fxconsole sập lâu, heartbeat thất bại quá 2 × InpCloudHeartbeatTimeoutSec thì EA tự nhận leader để không bỏ mặc vị thế.

In production (CF empty), “MyApi down” lands directly in this branch. If fxconsole stays down, once heartbeats have failed for more than 2 × InpCloudHeartbeatTimeoutSec the EA takes leadership itself so positions are not abandoned.

Kết luậnBottom line

EA luôn hoạt động được kể cả khi mọi cloud backend cùng sập. Local file là lưu trữ chính; cloud là phương án phục hồi cho trường hợp đổi máy.

The EA always keeps working, even when every cloud backend is down at once. The local file is the primary store; the cloud is the recovery path for moving to another machine.

Bảo trìMaintenance

Kiểm tra TMap đã syncChecking that TMap is synced

# fxconsole MySQL
cd monitor/fxconsole
php artisan tinker
>>> App\Models\TmapEntry::where('magic', '21478308')->count();
>>> App\Models\TmapEntry::where('magic', '21478308')->get();

# Cloudflare D1 (nếu dùng / if used)
wrangler d1 execute gr-tmap --remote \
  --command="SELECT COUNT(*) FROM tmap_entries WHERE magic='21478308'"
wrangler d1 execute gr-tmap --remote --command="SELECT * FROM tmap_entries LIMIT 10"

Dọn dữ liệu cũ (định kỳ)Cleaning up old data (periodically)

Xoá entry không được cập nhật quá 30 ngày:

Delete entries not updated for more than 30 days:

# MySQL
cd monitor/fxconsole
php artisan tinker
>>> App\Models\TmapEntry::where('updated_at', '<', now()->subDays(30))->delete();

# D1
wrangler d1 execute gr-tmap --remote \
  --command="DELETE FROM tmap_entries WHERE updated_at < datetime('now', '-30 days')"

Sau khi đổi codeAfter code changes

# fxconsole — sau khi pull code mới / after pulling new code
cd monitor/fxconsole
php artisan migrate
php artisan config:clear

# Cloudflare Worker — deploy production
cd cloudflare-tmap-worker
npm run deploy
npm run db:migrate      # nếu đổi schema D1 / if the D1 schema changed

Bảo mậtSecurity notes

  1. Token theo product: mỗi EA product có secret riêng (easupported.secret_token). LEGACY Guide gốc dùng một secret giống nhau ở cả 3 nơi (input EA, secret Cloudflare Worker, .env fxconsole).
  2. Chỉ HTTPS — không dùng HTTP.
  3. Xoay token: bấm Regenerate trên /admin/easupported rồi cập nhật .set của mọi terminal (và secret Worker nếu có bật CF) cùng lúc. LEGACY Guide gốc: cập nhật cả 3 nơi cùng lúc.
  4. Whitelist URL trong MT5 Options → Expert Advisors.
  5. Account number trong API là string — nếu broker đổi định dạng thì cần cập nhật.
  6. Không đưa token vào tài liệu, log hay .set được commit; dùng <secret> trong ví dụ.
  1. Per-product token: each EA product has its own secret (easupported.secret_token). LEGACY The original guide used one identical secret in all 3 places (EA input, Cloudflare Worker secret, fxconsole .env).
  2. HTTPS only — never HTTP.
  3. Rotating the token: click Regenerate on /admin/easupported, then update the .set on every terminal (and the Worker secret if CF is enabled) at the same time. LEGACY Original guide: update all 3 places at the same time.
  4. Whitelist the URLs in MT5 Options → Expert Advisors.
  5. The account number is a string in the API — if the broker changes its format, this needs updating.
  6. Never put the token in docs, logs or committed .set files; use <secret> in examples.

Xử lý sự cốTroubleshooting

EA không syncThe EA does not sync

  1. Kiểm tra WebRequest whitelist trong MT5 Options.
  2. Kiểm tra InpCloudSyncEnable = true.
  3. Xem log MT5 (tab Experts) — sẽ có thông báo lỗi.
  4. Test API trực tiếp bằng curl (ví dụ).
  5. Panel MyApi=-1: lỗi mạng hoặc host chưa có trong Allow WebRequest.
  1. Check the WebRequest whitelist in MT5 Options.
  2. Check InpCloudSyncEnable = true.
  3. Read the MT5 log (Experts tab) — it will contain an error message.
  4. Test the API directly with curl (examples).
  5. Panel MyApi=-1: network error, or the host is not in Allow WebRequest.

MyApi 404 / 401 (endpoint theo từng EA)MyApi 404 / 401 (per-EA endpoints)

  • 404: slug trong InpCloudMyApiDomain không tồn tại trên /admin/easupported.
  • 401: InpCloudBearerToken khác token của product (có thể vừa bấm Regenerate). Copy lại token vào .set.
  • Domain thiếu /easupported/goldenrobber: EA đang gọi endpoint legacy /api/tmap/*, vốn dùng secret toàn cục.
  • 404: the slug in InpCloudMyApiDomain does not exist on /admin/easupported.
  • 401: InpCloudBearerToken differs from the product's token (perhaps Regenerate was just clicked). Copy the token into the .set again.
  • Domain without /easupported/goldenrobber: the EA is calling the legacy /api/tmap/* endpoints, which use the global secret.

MySQL 401 UnauthorizedMySQL 401 Unauthorized LEGACY

  • TMAP_SECRET_KEY trong .env khác input EA.
  • Nạp lại config Laravel: php artisan config:clear.
  • TMAP_SECRET_KEY in .env differs from the EA input.
  • Reload the Laravel config: php artisan config:clear.

Cloudflare 401 UnauthorizedCloudflare 401 Unauthorized

  • Secret trong EA khác secret đã đặt qua wrangler secret put.
  • Đặt lại: npm run secret:set.
  • The secret in the EA differs from the one set via wrangler secret put.
  • Set it again: npm run secret:set.

D1 không có dữ liệu sau khi saveD1 has no data after a save

  • Kiểm tra database_id trong wrangler.toml đúng.
  • Chạy npm run db:migrate để chắc chắn schema đã được apply.
  • Test save bằng curl xem có lỗi không.
  • Check that database_id in wrangler.toml is correct.
  • Run npm run db:migrate to make sure the schema is applied.
  • Test a save with curl and look for errors.

Legacy và hiện tạiLegacy vs current

Thành phầnComponent LEGACY HIỆN TẠICURRENT
Endpoint MySQLMySQL endpoints /api/tmap/{save,load,clear,heartbeat} (TmapApiController) /easupported/{slug}/api/tmap/{save,load,clear,heartbeat} (EaSupportedApiController)
Auth Secret toàn cục TMAP_SECRET_KEY (tmap_secret_key)Global secret TMAP_SECRET_KEY (tmap_secret_key) Token theo product easupported.secret_token, quản lý trên /admin/easupportedPer-product token easupported.secret_token, managed on /admin/easupported
Input EAEA inputs InpCloudSecretKey, InpCloudCfUrl, InpCloudMyApiUrl (…/api/tmap) InpCloudBearerToken, InpCloudCfDomain, InpCloudMyApiDomain (https://host/easupported/goldenrobber); đổi sang *Domain từ v1.38, EA tự nối pathInpCloudBearerToken, InpCloudCfDomain, InpCloudMyApiDomain (https://host/easupported/goldenrobber); renamed to *Domain in v1.38, the EA appends the path
Trang settingsSettings page Trường TradingMap SecretTradingMap Secret field Đã gỡ; mỗi product tự giữ tokenRemoved; each product holds its own token
Cloudflare Backup #1, secret toàn cụcBackup #1, global secret Vẫn secret toàn cục; InpCloudCfDomain trống trong productionStill a global secret; InpCloudCfDomain empty in production
Ghi savesave writes Xoá hết + insert hết mỗi lượtDelete all + insert all on every sync Bỏ qua khi fingerprint trùng (fxconsole 2026.09.04.1, cả hai controller)Skipped when the fingerprint matches (fxconsole 2026.09.04.1, both controllers)

Cây file (guide gốc)File tree (original guide)

GoldenRobber/
├── monitor/fxconsole/                  # Laravel MySQL backend
│   ├── app/Http/Controllers/Api/
│   │   └── TmapApiController.php
│   ├── app/Models/
│   │   └── TmapEntry.php
│   ├── database/migrations/
│   │   └── 2026_04_25_000001_create_tmap_entries_table.php
│   ├── routes/api.php
│   ├── config/app.php
│   └── .env.example
│
├── cloudflare-tmap-worker/             # CF Worker + D1
│   ├── src/index.js
│   ├── schema.sql
│   ├── wrangler.toml
│   ├── package.json
│   └── README.md
│
└── docs/TMAP_DISASTER_RECOVERY_GUIDE.md     # File này

Bổ sung từ khi có endpoint theo từng EA: app/Http/Controllers/Api/EaSupportedApiController.php, routes/web.php, database/migrations/2026_08_29_000007_add_secret_token_to_easupported.php (xem File liên quan).

(“File này” = this file.) Added since the per-EA endpoints: app/Http/Controllers/Api/EaSupportedApiController.php, routes/web.php, database/migrations/2026_08_29_000007_add_secret_token_to_easupported.php (see Related files).