ποΈ 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 ordersorder_modifyβ before/after modifying ordersorder_closeβ before/after closing positionsconnectβ before/after connection attemptsquoteβ 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)
π Related DocumentationΒΆ
- PROJECT_MAP.md β Project structure and navigation
- MT4Account BASE β Low-level API master overview
- MT4Sugar API β High-level API documentation
- Orchestrators Guide β Trading workflows
- Presets Guide β Configuration presets
"May your architecture be solid, your data flow clean, and your trades profitable. ποΈ"