✅ Subscribe to Real-Time Tick Data (OnSymbolTickAsync)¶
Stream: Real-time price tick updates for specified symbols on MT5. Returns continuous stream of bid/ask prices as they change.
API Information:
- SDK wrapper:
MT5Service.OnSymbolTickAsync(...)(from classMT5Service) - gRPC service:
mt5_term_api.SubscriptionService - Proto definition:
OnSymbolTick(defined inmt5-term-api-subscriptions.proto)
RPC¶
- Service:
mt5_term_api.SubscriptionService - Method:
OnSymbolTick(OnSymbolTickRequest) → stream OnSymbolTickReply - Low‑level client (generated):
SubscriptionService.SubscriptionServiceClient.OnSymbolTick(request, headers, deadline, cancellationToken) - SDK wrapper:
namespace mt5_term_api
{
public class MT5Service
{
public async IAsyncEnumerable<OnSymbolTickData> OnSymbolTickAsync(
string[] symbols,
[EnumeratorCancellation] CancellationToken cancellationToken = default);
}
}
Request message:
OnSymbolTickRequest { symbol_names }
Reply message (stream): OnSymbolTickReply { data: OnSymbolTickData }
🔽 Input¶
| Parameter | Type | Description |
|---|---|---|
symbols |
string[] |
Array of symbol names (e.g., ["EURUSD", "GBPUSD"]) |
cancellationToken |
CancellationToken |
Token to stop the stream |
⬆️ Output — OnSymbolTickData (stream)¶
| Field | Type | Description |
|---|---|---|
SymbolTick |
MrpcSubscriptionMqlTick |
Tick data for symbol |
TerminalInstanceGuidId |
string |
Terminal instance ID |
MrpcSubscriptionMqlTick — Tick structure¶
| Field | Type | Description |
|---|---|---|
Time |
Timestamp |
Time of last price update (UTC timestamp) |
Bid |
double |
Current Bid price |
Ask |
double |
Current Ask price |
Last |
double |
Price of last deal |
Volume |
uint64 |
Volume for current Last price |
TimeMsc |
int64 |
Time of price update in milliseconds |
Flags |
uint32 |
Tick flags |
VolumeReal |
double |
Real volume with greater accuracy |
Symbol |
string |
Symbol name |
💬 Just the essentials¶
- What it is. Real-time streaming of price ticks. Continuously sends bid/ask updates as prices change on server.
- Why you need it. Build real-time price monitoring, tick charts, high-frequency strategies, market scanners.
- Sanity check. Stream runs until cancelled. Use
CancellationTokento stop. HandleOperationCanceledExceptionwhen stopping.
🎯 Purpose¶
Use it for real-time price monitoring:
- Real-time price displays and charts.
- High-frequency trading strategies.
- Market scanners monitoring multiple symbols.
- Price alert systems.
- Tick-by-tick data collection.
🧩 Notes & Tips¶
- Streaming model: Use
await foreachto process events. Stream runs continuously until cancelled. - Cancellation: Always use
CancellationTokento stop stream gracefully. Callcts.Cancel()orcts.CancelAfter(timeout). - Multiple symbols: Can subscribe to multiple symbols in one stream. Ticks arrive as they happen for any subscribed symbol.
- Performance: Ticks arrive in real-time. Keep processing logic fast to avoid blocking stream.
- Error handling: Wrap in try-catch. Handle
OperationCanceledException(normal when stopping). - Parallel streams: Can run multiple streams in parallel using
Task.WhenAll(). - Time format:
TimeMscis Unix timestamp in milliseconds. Convert usingDateTimeOffset.FromUnixTimeMilliseconds().
🔗 Usage Examples¶
1) Basic tick streaming¶
// svc — MT5Service instance
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(10)); // Stop after 10 seconds
try
{
await foreach (var tick in svc.OnSymbolTickAsync(new[] { "EURUSD" }, cts.Token))
{
var t = tick.SymbolTick;
Console.WriteLine($"{t.Symbol}: Bid={t.Bid:F5} | Ask={t.Ask:F5}");
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Stream stopped");
}
2) Monitor multiple symbols¶
var symbols = new[] { "EURUSD", "GBPUSD", "USDJPY" };
var cts = new CancellationTokenSource();
await foreach (var tick in svc.OnSymbolTickAsync(symbols, cts.Token))
{
var t = tick.SymbolTick;
var time = DateTimeOffset.FromUnixTimeMilliseconds(t.TimeMsc).DateTime;
Console.WriteLine($"[{time:HH:mm:ss.fff}] {t.Symbol,-7} | Bid: {t.Bid:F5} | Ask: {t.Ask:F5} | Spread: {(t.Ask - t.Bid):F5}");
}
3) Price alert system¶
var symbol = "XAUUSD";
var alertLevel = 2000.0;
var cts = new CancellationTokenSource();
await foreach (var tick in svc.OnSymbolTickAsync(new[] { symbol }, cts.Token))
{
var t = tick.SymbolTick;
if (t.Bid >= alertLevel)
{
Console.WriteLine($"⚠ ALERT: {symbol} reached ${alertLevel}!");
Console.WriteLine($" Bid: {t.Bid:F2} | Ask: {t.Ask:F2}");
// Stop stream after alert
cts.Cancel();
}
}
4) Collect tick data with limit¶
var symbols = new[] { "EURUSD", "GBPUSD" };
var maxTicks = 100;
var tickCount = 0;
var cts = new CancellationTokenSource();
await foreach (var tick in svc.OnSymbolTickAsync(symbols, cts.Token))
{
tickCount++;
var t = tick.SymbolTick;
Console.WriteLine($"Tick #{tickCount}: {t.Symbol} @ {t.Bid:F5}");
if (tickCount >= maxTicks)
{
Console.WriteLine($"Collected {maxTicks} ticks, stopping...");
break; // Exit loop (stream stops automatically)
}
}
5) Calculate spread statistics¶
var symbol = "EURUSD";
var spreads = new List<double>();
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(60)); // Collect for 1 minute
try
{
await foreach (var tick in svc.OnSymbolTickAsync(new[] { symbol }, cts.Token))
{
var t = tick.SymbolTick;
var spread = (t.Ask - t.Bid) * 100000; // Spread in pips (for 5-digit pairs)
spreads.Add(spread);
if (spreads.Count % 10 == 0)
{
var avgSpread = spreads.Average();
var minSpread = spreads.Min();
var maxSpread = spreads.Max();
Console.WriteLine($"Spreads: Avg={avgSpread:F1} | Min={minSpread:F1} | Max={maxSpread:F1} pips");
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine($"\nFinal statistics ({spreads.Count} ticks):");
Console.WriteLine($" Average spread: {spreads.Average():F2} pips");
Console.WriteLine($" Min spread: {spreads.Min():F2} pips");
Console.WriteLine($" Max spread: {spreads.Max():F2} pips");
}
6) Run multiple streams in parallel¶
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30));
// Task 1: Monitor EURUSD
var task1 = Task.Run(async () =>
{
await foreach (var tick in svc.OnSymbolTickAsync(new[] { "EURUSD" }, cts.Token))
{
Console.WriteLine($"[EUR] {tick.SymbolTick.Bid:F5}");
}
}, cts.Token);
// Task 2: Monitor GBPUSD
var task2 = Task.Run(async () =>
{
await foreach (var tick in svc.OnSymbolTickAsync(new[] { "GBPUSD" }, cts.Token))
{
Console.WriteLine($"[GBP] {tick.SymbolTick.Bid:F5}");
}
}, cts.Token);
try
{
await Task.WhenAll(task1, task2);
}
catch (OperationCanceledException)
{
Console.WriteLine("All streams stopped");
}
7) Using ReadTicks extension helper (convenience wrapper)¶
// Extension method with built-in limits
int maxEvents = 20;
int durationSec = 5;
await foreach (var tick in svc.ReadTicks(
symbols: new[] { "EURUSD", "GBPUSD" },
maxEvents: maxEvents,
durationSec: durationSec))
{
var t = tick.SymbolTick;
Console.WriteLine($"{t.Symbol}: {t.Bid:F5}");
// Stream automatically stops after 20 events OR 5 seconds (whichever comes first)
}
8) Tick-based trading signal¶
var symbol = "EURUSD";
var cts = new CancellationTokenSource();
double? previousBid = null;
int consecutiveUps = 0;
await foreach (var tick in svc.OnSymbolTickAsync(new[] { symbol }, cts.Token))
{
var t = tick.SymbolTick;
if (previousBid.HasValue)
{
if (t.Bid > previousBid)
{
consecutiveUps++;
if (consecutiveUps >= 5)
{
Console.WriteLine($"🟢 BUY SIGNAL: {symbol} price increased {consecutiveUps} ticks in a row");
consecutiveUps = 0; // Reset counter
}
}
else
{
consecutiveUps = 0;
}
}
previousBid = t.Bid;
}