Skip to content

πŸ—οΈ Architecture & Data FlowΒΆ

A practical map of how our Python SDK, gRPC services, and the MT4 terminal talk to each other β€” with just enough clarity to keep your margin level above 100%.


πŸ—ΊοΈ Big PictureΒΆ

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 MT4 Terminal                  β”‚
β”‚      (broker login, quotes, trades)           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ local/IPC
                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      mt4_term_api.* (gRPC server)             β”‚
β”‚      Services: MarketQuota, Trade, Account... β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ gRPC
                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Python SDK (MetaRpcMT4)                  β”‚
β”‚      MT4Account (package/MetaRpcMT4/...)      β”‚
β”‚      + generated pb2/pb2_grpc stubs           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ async/await
                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Service Layer (app/)                     β”‚
β”‚      MT4Service, MT4Service_Trade_mod         β”‚
β”‚      Connection mgmt, retry logic, hooks      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ async/await
                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Sugar API (app/MT4Sugar.py)              β”‚
β”‚      Pip-based ops, auto-calculations         β”‚
β”‚      User-friendly wrappers                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ async/await
                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Strategy Layer (Strategy/)               β”‚
β”‚      Orchestrators: market_one_shot, etc.     β”‚
β”‚      Presets: Balanced, MarketEURUSD, etc.    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ async/await
                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Your App / Examples                      β”‚
β”‚      (examples/, main*.py)                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

You write: high‑level await run_market_one_shot(...) or await sugar.buy_market(...) calls. Sugar does: pip↔price conversion, lot calculation, parameter validation. Service does: connection management, retries, hooks, rate limiting. SDK does: request building, metadata, deadlines, retries, unwraps .reply.data. Server does: talks to the real MT4 terminal and executes.


βš™οΈ Main Components (by folders)ΒΆ

πŸ”© SDK & gRPC contracts (client side)ΒΆ

  • package/MetaRpcMT4/mt4_account.py β€” MT4Account wrappers (public API).
  • package/MetaRpcMT4/mt4_term_api_*.py β€” pb2 messages (requests/replies/enums).
  • package/MetaRpcMT4/mt4_term_api_*_pb2_grpc.py β€” stubs (client bindings).
  • package/MetaRpcMT4/mrpc_mt4_error_pb2.py β€” errors/retcodes mapping.

🧠 Service layer (app/)¢

  • app/MT4Service.py β€” core service wrapper, connection management. πŸ”Œ
  • app/MT4Service_Trade_mod.py β€” trading operations wrapper. ☒️
  • app/MT4Sugar.py β€” high-level sugar API with pip-based operations. 🍬
  • app/Helper/config.py β€” settings loader (appsettings.json + env). βš™οΈ
  • app/Helper/hooks.py β€” event hooks system (pre/post operations). πŸͺ
  • app/Helper/patch_mt4_account.py β€” compatibility patches for MT4 quirks. 🧩
  • app/Helper/rate_limit.py β€” API rate limiting protection. πŸ›‘οΈ
  • app/Helper/errors.py β€” custom exceptions and error handling. 🚨

🎭 Strategy layer (Strategy/)¢

  • Strategy/orchestrator/*.py β€” 13 ready-to-use trading scenarios.
  • Strategy/presets/risk.py β€” 5 basic risk profiles (Conservative, Balanced, etc.).
  • Strategy/presets/risk_atr.py β€” 3 ATR-based adaptive risk profiles.
  • Strategy/presets/risk_profiles.py β€” symbol+style combinations.
  • Strategy/presets/risk_session.py β€” session-based risk management.
  • Strategy/presets/strategy_symbols.py β€” 30+ pre-configured symbols.

πŸ“š Documentation & ExamplesΒΆ

  • docs/MT4Account/** β€” low-level API specs & overviews.
  • docs/MT4Sugar/** β€” sugar API method documentation.
  • docs/Strategy/** β€” orchestrators & presets guides.
  • examples/** β€” runnable demo scripts.
  • main*.py β€” entry point demos.

πŸ”€ Data Flow (Unary Request)ΒΆ

Example: Buy market order with Sugar APIΒΆ

# 1. Your code
await sugar.buy_market("EURUSD", lots=0.1, sl_pips=20, tp_pips=40)

# 2. Sugar API (MT4Sugar.py)
# - Validates parameters
# - Converts pips to price (20 pips β†’ 0.0020 for EURUSD)
# - Builds order request
# - Calls MT4Service_Trade_mod

# 3. Service Layer (MT4Service_Trade_mod.py)
# - Checks rate limits
# - Fires pre-operation hooks
# - Calls MT4Service.order_send()
# - Handles retries on failure
# - Fires post-operation hooks

# 4. Core Service (MT4Service.py)
# - Ensures connection alive
# - Adds metadata (login, session)
# - Sets timeout/deadline
# - Calls MT4Account.order_send()

# 5. SDK (package/MetaRpcMT4/mt4_account.py)
# - Builds gRPC Request protobuf
# - Calls stub: ServiceStub.OrderSend(request, metadata, timeout)
# - Waits for Reply
# - Unwraps reply.data
# - Returns order ticket

# 6. gRPC Server (mt4_term_api)
# - Receives request
# - Calls MT4 terminal functions
# - Executes order
# - Returns Reply with ticket/retcode

# 7. Result flows back through layers
# - SDK unwraps reply
# - Service processes hooks
# - Sugar returns clean result
# - Your code gets ticket number

πŸ”„ Data Flow (Streaming)ΒΆ

Streams keep a channel open and push events. Use cancellation_event and keep handlers non‑blocking.

Example: Stream live ticksΒΆ

import asyncio

stop_event = asyncio.Event()

# 1. Your code subscribes
async for tick in sugar.on_symbol_tick(["EURUSD", "GBPUSD"], cancellation_event=stop_event):
    print(f"{tick.symbol}: {tick.bid}/{tick.ask}")

    if should_stop():
        stop_event.set()  # Signal to close stream

# 2. Sugar API
# - Validates symbols
# - Calls MT4Service.on_symbol_tick()

# 3. Service Layer
# - Ensures connection
# - Calls MT4Account.on_symbol_tick()
# - Monitors cancellation_event

# 4. SDK
# - Opens streaming gRPC channel
# - Yields events as they arrive
# - Closes on cancellation_event.set()

# 5. Server
# - Subscribes to terminal tick events
# - Streams ticks for requested symbols
# - Closes on client disconnect

Common streams:

  • on_symbol_tick β€” live ticks per symbol. πŸ“ˆ
  • on_trade β€” trade execution events (orders filled, positions changed). πŸ’Ό
  • on_opened_orders_tickets β€” stream of open ticket IDs. 🎫
  • on_opened_orders_profit β€” stream of profit updates. πŸ’°

Links: Streams Overview


🧩 Layer Responsibilities¢

Layer 1: Strategy Orchestrators 🎭¢

What: Complete trading workflows (market_one_shot, pending_bracket, spread_guard, etc.)

Responsibilities: - Combine multiple operations into scenarios - Implement trading logic (guards, timeouts, automation) - Use presets for configuration - Handle complex flows (wait for fill, activate trailing, etc.)

Example:

from Strategy.orchestrator.market_one_shot import run_market_one_shot
from Strategy.presets import MarketEURUSD, Balanced

result = await run_market_one_shot(svc, MarketEURUSD, Balanced)

Layer 2: Presets βš™οΈΒΆ

What: Reusable configurations (MarketEURUSD, Balanced, Conservative, etc.)

Responsibilities: - Define WHAT to trade (symbol, entry type, magic number) - Define HOW MUCH to risk (%, SL, TP, trailing) - Provide consistent configurations - Enable mixing and matching

Example:

from Strategy.presets import MarketEURUSD, Balanced

strategy = MarketEURUSD  # WHAT: EURUSD, market entry
risk = Balanced          # HOW MUCH: 1% risk, 20 pip SL, 40 pip TP

Layer 3: Sugar API 🍬¢

What: High-level convenience methods (buy_market, modify_sl_tp_by_pips, etc.)

Responsibilities: - Pip↔price conversions - Auto lot calculation by risk % - Symbol info caching - User-friendly parameter names - Input validation

Example:

await sugar.buy_market("EURUSD", lots=0.1, sl_pips=20, tp_pips=40)
# Converts pips to prices, validates, calls service layer

Layer 4: Service Wrappers πŸ”ŒΒΆ

What: MT4Service, MT4Service_Trade_mod

Responsibilities: - Connection management (connect, reconnect, ensure_connected) - Rate limiting (prevent API throttling) - Hook system (pre/post operation events) - Retry logic (handle transient failures) - Session management

Example:

svc = MT4Service(...)
await svc.ensure_connected()
result = await svc.order_send(request)

Layer 5: SDK (Low-level) πŸ”§ΒΆ

What: MT4Account (package/MetaRpcMT4/mt4_account.py)

Responsibilities: - gRPC request/response building - Protobuf serialization - Metadata handling - Timeout/deadline management - Reply unwrapping

Example:

from package.MetaRpcMT4.mt4_account import MT4Account

acct = MT4Account(...)
ticket = await acct.order_send(request)


πŸ”Œ Connection ManagementΒΆ

Connection FlowΒΆ

1. Load config (appsettings.json or env)
   ↓
2. Create MT4Service instance
   ↓
3. Call ensure_connected()
   ↓
4. Try connect_by_server_name()
   ↓
5. If fails, try connect_by_host_port() for each host in "access" list
   ↓
6. If all fail, retry with backoff
   ↓
7. Connection established βœ“
   ↓
8. Store session metadata
   ↓
9. Enable rate limiting
   ↓
10. Ready for operations

Connection StatesΒΆ

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Disconnectedβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ ensure_connected()
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Connecting  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ success
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     transient error
β”‚  Connected  β”‚ ◄──────────────────┐
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β”‚
       β”‚                           β”‚
       β”‚ operation                 β”‚
       β–Ό                           β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     auto-retry     β”‚
β”‚  Operating  β”‚ β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ disconnect()
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Disconnectedβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸͺ Hook SystemΒΆ

Hooks allow you to intercept operations at key points.

Hook TypesΒΆ

from app.Helper.hooks import HookManager

hooks = HookManager()

# Before operation
@hooks.before("order_send")
async def log_order(request):
    print(f"Sending order: {request.symbol} @ {request.volume}")

# After operation (success)
@hooks.after("order_send")
async def log_success(request, result):
    print(f"Order filled: ticket={result.ticket}")

# After operation (error)
@hooks.on_error("order_send")
async def log_error(request, error):
    print(f"Order failed: {error}")

Common Hook PointsΒΆ

  • order_send β€” before/after placing orders
  • order_modify β€” before/after modifying orders
  • order_close β€” before/after closing positions
  • connect β€” before/after connection attempts
  • quote β€” before/after fetching quotes

πŸ›‘οΈ Rate LimitingΒΆ

Prevents overwhelming the MT4 terminal with requests.

ConfigurationΒΆ

from app.Helper.rate_limit import RateLimiter

# Max 10 requests per second
limiter = RateLimiter(max_calls=10, period=1.0)

async def safe_operation():
    async with limiter:
        result = await svc.quote("EURUSD")
    return result

Default LimitsΒΆ

  • Market data: 20 req/sec
  • Trading operations: 5 req/sec
  • History queries: 10 req/sec

πŸ“Š Request/Response ExamplesΒΆ

Example 1: Get QuoteΒΆ

# High-level (Sugar)
quote = await sugar.last_quote("EURUSD")
# Returns: QuoteData(symbol="EURUSD", bid=1.10000, ask=1.10020, time=...)

# Low-level (SDK)
from package.MetaRpcMT4 import mt4_term_api_market_quota_pb2 as mq_pb2

request = mq_pb2.QuoteRequest(symbol="EURUSD")
reply = await acct.quote(request)
# Returns: QuoteReply.data with bid/ask/time

Example 2: Place Market OrderΒΆ

# High-level (Sugar)
ticket = await sugar.buy_market("EURUSD", lots=0.1, sl_pips=20, tp_pips=40)
# Sugar converts pips to prices, calls service

# Medium-level (Service)
result = await svc.order_send(
    symbol="EURUSD",
    order_type="market",
    volume=0.1,
    sl=1.09800,  # calculated from pips
    tp=1.10400,  # calculated from pips
)

# Low-level (SDK)
from package.MetaRpcMT4 import mt4_term_api_trading_action_pb2 as ta_pb2

request = ta_pb2.OrderSendRequest(
    symbol="EURUSD",
    order_type=ta_pb2.ORDER_TYPE_BUY,
    volume=0.1,
    sl=1.09800,
    tp=1.10400,
)
reply = await acct.order_send(request)
# Returns: OrderSendReply.data with ticket

πŸ” Debugging TipsΒΆ

Enable Verbose LoggingΒΆ

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("MT4Service")
logger.setLevel(logging.DEBUG)

Inspect gRPC CallsΒΆ

from app.Helper import grpc_debug

# Log all gRPC calls
grpc_debug.enable()

# Make calls
await svc.quote("EURUSD")
# Logs: Request, Reply, Duration

# Disable logging
grpc_debug.disable()

Check Connection StateΒΆ

is_connected = await svc.ping()
if not is_connected:
    await svc.ensure_connected()

Monitor Rate LimitsΒΆ

from app.Helper.rate_limit import get_limiter_stats

stats = get_limiter_stats()
print(f"Requests: {stats.calls} / {stats.limit}")
print(f"Window resets in: {stats.reset_in}s")

🎯 Performance Optimization¢

1. Cache Symbol InfoΒΆ

# Bad: fetch every time
for i in range(100):
    digits = await sugar.digits("EURUSD")

# Good: cache once
digits = await sugar.digits("EURUSD")  # Automatically cached
for i in range(100):
    # Use cached value
    pass

2. Batch Quote RequestsΒΆ

# Bad: one by one
quote1 = await sugar.last_quote("EURUSD")
quote2 = await sugar.last_quote("GBPUSD")

# Good: batch
quotes = await sugar.quotes(["EURUSD", "GBPUSD"])

3. Use Streams for Real-time DataΒΆ

# Bad: poll every second
while True:
    quote = await sugar.last_quote("EURUSD")
    await asyncio.sleep(1)

# Good: stream
async for tick in sugar.on_symbol_tick(["EURUSD"]):
    # Process tick immediately
    pass

4. Parallel OperationsΒΆ

import asyncio

# Execute multiple operations in parallel
results = await asyncio.gather(
    sugar.last_quote("EURUSD"),
    sugar.last_quote("GBPUSD"),
    sugar.opened_orders(),
)

πŸ§ͺ Testing StrategyΒΆ

Unit Tests (Mock SDK)ΒΆ

from unittest.mock import AsyncMock

acct = AsyncMock()
acct.quote.return_value = QuoteReply(...)

svc = MT4Service(acct)
result = await svc.quote("EURUSD")

Integration Tests (Demo Account)ΒΆ

# Set ENABLE_TRADING=0 for safe testing
import os
os.environ["ENABLE_TRADING"] = "0"

# All trading operations will be simulated
await sugar.buy_market("EURUSD", lots=0.1)  # No real order

Live Tests (Real Account)ΒΆ

# Set ENABLE_TRADING=1 and use SMALL lots
os.environ["ENABLE_TRADING"] = "1"

# Real trading with minimal risk
await sugar.buy_market("EURUSD", lots=0.01, sl_pips=20, tp_pips=40)


"May your architecture be solid, your data flow clean, and your trades profitable. πŸ—οΈ"