Architecture Concept · v0.1 Draft
A multi-tenant SaaS control plane that licenses brokers and delivers turnkey trading features — MT5 server plugins paired with white-label web frontends — onto broker-owned infrastructure.
Brokers who run their own MetaTrader 5 servers want modern client-facing functionality — a web terminal, copy trading, social trading, risk tooling — without building or operating any of it. The Hub is the product that sells them exactly that: a subscription service where a broker signs up, receives a license, and switches individual features on from a self-service console. Each feature arrives as a matched pair: a server-side component (an MT5 plugin or a gateway service that talks to the broker's MT5 server) plus a white-label web frontend branded for that broker.
The provider owns the Hub — the control plane, the licensing authority, the feature catalog, and the frontends. The broker owns their trading infrastructure — the MT5 server, the accounts, the market data, the money. That split is the core selling point: the broker's trading data never leaves the broker's infrastructure; the Hub only orchestrates.
| Term | Meaning |
|---|---|
| Provider | The SaaS operator (us). Owns the Hub, the catalog, the licenses, the frontend hosting. |
| Hub | The multi-tenant control plane: broker registry, licensing service, feature catalog, provisioning orchestrator, telemetry, and the two consoles. |
| Broker | A tenant. A brokerage company with its own MT5 server(s) that subscribes to Hub features. |
| Feature | A sellable capability (Web Terminal, Copy Trading, Price Alerts…). Described by a manifest; composed of components. |
| Component | A deployable part of a feature: mt5-plugin (native DLL on the MT5 server), gateway-service (module inside the broker's Gateway), or web-frontend (SPA hosted by the provider). |
| Gateway | The broker-side API service (evolution of today's manager-api): logs into the broker's MT5 server as a manager, exposes the API that feature frontends consume, hosts gateway-type feature modules. |
| Agent | The broker-side daemon that maintains a single outbound connection to the Hub: installs/updates components, delivers config and licenses, reports health. |
| Entitlement | One line inside a license: “feature X, tier Y, limits Z, until date D”. |
Two planes. The control plane (provider cloud) holds no trading data. The data plane (one per broker) holds all of it.
Traffic rules, in order of importance:
The Hub database is multi-tenant with a broker_id discriminator on every row and row-level enforcement in the API layer (single schema; per-tenant schemas are not warranted at this data volume — the Hub stores configuration, not trading history). Broker Console sessions are scoped to exactly one broker_id; provider operators carry an explicit cross-tenant role.
A broker record moves through a small, explicit state machine. Licensing (§6) is layered on top of an active broker; deactivating the broker overrides every license.
stateDiagram-v2 [*] --> Draft: create broker Draft --> Provisioning: issue license + agent token Provisioning --> Active: agent connected,
first feature deployed Active --> Suspended: manual suspend /
license expired + grace over Suspended --> Active: reactivate / renew Suspended --> Terminated: offboard Terminated --> [*]
| Operation | Where | Notes |
|---|---|---|
| Create broker | Provider Console | Company profile, contacts, contract refs; generates broker_id, agent enrollment token, initial license. |
| Edit broker | Provider Console | Profile, license terms, entitlements, limits. Every change audited. |
| Suspend / reactivate | Provider Console | Suspend = frontends show a neutral “service unavailable” page, gateways stop serving feature APIs, plugins go dormant. Reversible in minutes. |
| Terminate | Provider Console | Agent uninstalls components on command (or broker removes manually); license revoked; config retained 90 days, then purged. |
| Self-service edits | Broker Console | Branding, feature config, notification channels, own staff accounts — never license terms. |
The license is a signed document, not a database flag — because it must be enforceable on machines we do not control, including ones temporarily offline.
{
"license_id": "lic_01J9…",
"broker_id": "brk_honorpro",
"status": "active", // active | suspended
"issued_at": "2026-08-19T00:00:00Z",
"valid_until": "2027-08-19T00:00:00Z", // “active until” — the expiry the sales side manages
"grace_hours": 72, // offline tolerance
"entitlements": [
{ "feature": "web-terminal", "tier": "standard", "limits": { "concurrent_sessions": 500 } },
{ "feature": "copy-trading", "tier": "pro", "limits": { "masters": 50, "followers": 2000 } },
{ "feature": "price-alerts", "tier": "standard", "limits": { "watched_symbols": 200 } }
],
"signature": "ed25519:…" // signed by the Hub licensing key
}
valid_until is the commercial expiry; the wire copy's own freshness window is what makes the kill-switch fast.grace_hours, then degrade to read-only, then stop. A broker's internet blip never halts trading features instantly; a broker who cuts the cord to dodge expiry gets 72 hours, not forever.| Layer | Check | On failure |
|---|---|---|
| MT5 plugin | Verifies signature + expiry + its own feature flag at start and on each refresh | Feature logic goes dormant; plugin stays loaded but inert (no server restart needed) |
| Gateway | Same verification; also enforces entitlement limits (session counts, follower counts) | Serves 403 license_expired per feature; health endpoint reports the reason |
| Feature frontend | Hub-side tenant resolution checks license before serving the app shell | Renders the broker-branded “subscription expired — contact provider” page |
Statuses shown to operators: active expiring ≤ 30d grace expired suspended terminated. “Is this broker active, and until when” is a first-class column in the Provider Console, not a report.
Branding is a per-broker document applied uniformly by every feature frontend:
{broker}.hub.example.com per feature or per broker; custom domains via CNAME with automated TLS (the frontends already resolve the tenant from Host).dir stays LTR everywhere — established constraint from the terminal).Implementation: the frontend fleet is one deployment, many tenants. A request hits the frontend host, tenant is resolved from the domain, the branding document and license are fetched from the Hub (edge-cached, minutes-level TTL), theme tokens are injected as CSS variables. No per-broker builds; a rebrand is a config change.
A feature is defined by a manifest in the catalog. The manifest is what makes “broker clicks Enable, everything else happens” possible.
feature: copy-trading
version: 2.4.1
components:
- kind: mt5-plugin # native, runs inside the broker's MT5 server
artifact: copyengine-2.4.1-mt5build4620.zip # signed
mt5_min_build: 4560
- kind: gateway-service # Python module loaded by the broker's Gateway
artifact: gw-copytrading-2.4.1.whl
- kind: web-frontend # provider-hosted SPA route(s)
routes: [ "/copy", "/copy/admin" ]
config_schema: ./config.schema.json # drives the auto-generated settings UI
license_flag: copy-trading
depends_on: [ web-terminal-core ]
health: [ "plugin:copyengine:heartbeat", "gw:copytrading:ping" ]
stable / beta). Brokers sit on a channel; the provider promotes builds. Rollback = pin previous version.sequenceDiagram participant B as Broker admin participant H as Hub participant A as Agent (broker box) participant G as Gateway participant M as MT5 server B->>H: Enable "copy-trading" H->>H: check entitlement, resolve deps & version H->>A: deploy plan (artifacts, config, license) A->>A: download signed artifacts, verify signatures A->>M: install plugin (staged, server-safe window) A->>G: install gateway module, hot-reload A->>H: deployed OK + component health H->>B: feature live — link to its admin panel
MT5 plugin installation is the one step that may require a server restart (MetaQuotes constraint). The deploy plan marks such steps staged: the Agent prepares everything and either restarts within a broker-approved maintenance window or waits for the broker admin to confirm the restart from the console.
Three things run on broker infrastructure, all installed from one signed installer:
| Component | Runs on | Responsibilities |
|---|---|---|
| Hub Agent | Windows service on (or beside) the MT5 host | Outbound WSS to Hub; artifact download + signature verification; plugin/module install & upgrade; license fan-out; health & metrics reporting; remote log bundle on request. |
| Gateway | Windows or Linux box in the broker's network | Manager-API login to the broker's MT5; serves the feature APIs (REST + WS hubs) that frontends consume; loads gateway-type feature modules; enforces license limits; terminates trader TLS (or sits behind the broker's proxy). |
| MT5 plugins | Inside the broker's MT5 server process | Feature-specific server logic (copy engine, etc.). Each verifies the license independently. |
Enrollment: the Provider Console issues a one-time token; the broker (or our onboarding engineer) runs the installer, pastes the token, and the Agent appears in the Hub with a persistent identity (mTLS client cert issued on enrollment). Everything after that is remote.
Because the web terminal is itself a feature, the Broker Console is the natural home of what is today the terminal's admin area: one login, terminal administration under it. The current admin console (operators, dealing) continues to run gateway-side — it administers trading data that must stay broker-side — and is surfaced inside the Broker Console via SSO-embedded panel.
| Feature | Components | Source today |
|---|---|---|
| Web Terminal | web-frontend (HonorPro UI) + gateway core | web/src/honorpro + manager-api — exists, single-tenant |
| Terminal Admin | gateway-service + embedded panel | admin_page.py console — exists (split into static assets first, per OPS backlog) |
| Copy Trading | mt5-plugin (copy engine) + gateway-service + web-frontend | Copy engine — exists; needs the TicketMap scaling fix before large fleets |
| Social Trading | gateway-service + web-frontend | Patterns from ZT4 social — port, not rebuild |
| Trader Cabinet | gateway-service + web-frontend | Patterns from ZT4 trader cabinet |
| Price Alerts | gateway-service + Hub notification service | New — small; the pilot feature for the whole pipeline (§12) |
| Mobile app | white-label build pipeline (MAUI) | Exists single-brand; per-broker builds are a later phase |
The first fully Hub-native feature, and deliberately the pilot: it exercises the manifest, config schema, gateway module deployment, licensing and notification pipeline end-to-end while being small enough to ship early.
What it does: watches the broker's quote stream and notifies the broker's team in Telegram when quotes stop flowing during hours when the market should be trading — the classic “feed died at 3 a.m. Sunday? no — feed died mid-session and nobody noticed” insurance.
last_tick_at and an expected-cadence profile. Stall condition: now − last_tick_at > threshold while the symbol's market is open.
sequenceDiagram
participant G as Gateway module
participant H as Hub notification service
participant T as Telegram
G->>G: stall detected (market open, threshold passed)
G->>H: alert event {symbols, since, severity}
H->>T: message to broker's configured chat(s)
Note over G,H: repeat/escalate every N min while stalled
G->>H: recovery event
H->>T: "feed recovered, gap 4m 12s"
/start <code> to the bot). Option for the broker's own bot token later.{
"symbol_groups": [
{ "match": "*.z", "threshold_s": 30 },
{ "match": "XAU*", "threshold_s": 60 }
],
"calendar_overrides": [ { "date": "2026-12-25", "closed": true } ],
"telegram_chats": [ "-100123456789" ],
"repeat_min": 10,
"escalate_after_min": 30,
"open_close_quiet_multiplier": 3
}
brokers id, name, status, contacts, created_at
licenses id, broker_id, status, valid_until, grace_hours, signed_doc, issued_at
entitlements license_id, feature, tier, limits(json)
features key, name, description, config_schema_ref
feature_versions feature, version, channel, components(json), mt5_min_build
deployments id, broker_id, feature, version, state, plan(json), started_at, finished_at
agents id, broker_id, cert_fp, last_seen, agent_version, host_meta
branding broker_id, tokens(json), domains(json), locales(json), assets…
notif_channels broker_id, kind(telegram|email), address, verified
alert_events id, broker_id, feature, severity, payload, raised_at, resolved_at
audit_log id, actor, broker_id, action, before, after, at
users id, email, role, broker_id nullable (null ⇒ provider staff)
| API | Consumers | Shape |
|---|---|---|
| Hub Admin API | Provider Console | REST: /brokers, /brokers/{id}/license, /catalog/features, /fleet |
| Hub Tenant API | Broker Console, feature frontends (tenant resolution) | REST: /me/license, /me/features, /me/features/{f}/config, /me/branding, /me/channels |
| Agent channel | Hub ⇄ Agent | WSS, JSON messages: deploy.plan, deploy.result, license.push, health.report, alert.event, logs.request |
| Gateway feature APIs | Trader browsers / mobile | Per-feature REST + WS (today's manager-api API, namespaced per feature) |
Auth: Hub sessions are JWT with broker_id + role claims; agent channel is mTLS; frontends resolve tenants via signed, short-TTL tenant descriptors so a compromised edge cache cannot grant an expired broker service.
The Hub is not a green-field rewrite; it is a control plane wrapped around software that already runs in production for one broker.
| Exists today (mt5-terminal repo) | Becomes | Work |
|---|---|---|
HonorPro web terminal (web/) | Web Terminal feature frontend | Tenant resolution + branding tokens (theming groundwork exists — light-theme work) |
manager-api (Python FastAPI) | Gateway core | Modularize: core (MT5 session, pumps, auth) vs. feature modules; config from Hub instead of env-only |
| Copy engine (Windows, loopback) | Copy Trading mt5-plugin component | Package + license check + fix TicketMap scaling debt before multi-broker fleets |
Admin console (admin_page.py) | Terminal Admin embedded panel | Split the 2000-line HTML string into static assets (already planned), add Hub SSO |
| Saphyte auth integration | Per-broker pluggable CRM auth provider | Generalize: “CRM connector” becomes a per-broker Gateway config, Saphyte is connector #1 |
| ZT4 social / trader cabinet | Social Trading & Cabinet features | Port against the Gateway API |
| Current single-broker deployment | Tenant #1 | The migration test: HonorPro must move onto the Hub with zero trader-visible change |
| Phase | Delivers | Proves |
|---|---|---|
| 0 — Concept | This document; catalog + license format review | Shared vocabulary |
| 1 — Hub core | Broker registry + CRUD, licensing service (issue/sign/verify/expiry), Provider Console MVP, Broker Console shell with license visibility | “Active / inactive / until when” managed in one place |
| 2 — Agent + pilot feature | Agent (enroll, WSS, deploy, health), Gateway modularization, Price Alerts shipped end-to-end (deploy → config → detect → Telegram) | The whole delivery pipeline, on the smallest feature |
| 3 — Terminal as a feature | Tenant resolution + white-label in the terminal frontend; HonorPro migrated as tenant #1; Terminal Admin embedded via SSO | Multi-tenant frontends; zero-regression migration |
| 4 — Big features | Copy Trading (with plugin deploy + staged restart flow), then Social Trading, Cabinet | Plugin lifecycle on broker-owned MT5 servers |
| 5 — Scale & sell | Second real broker onboarded; billing hooks; mobile white-label pipeline | The business model |
mt5_min_build). How many concurrent MT5 server builds do we commit to supporting per plugin release?Ignore Rival Engines), define the compatibility checklist the onboarding flow must verify.Draft v0.1 — for review. Next step after sign-off: phase 1 technical spec (Hub core data model + licensing service API, detailed).