Getting started
This page takes you from an empty project to a program that connects to a ViiSync account, reads it, streams quotes, trades and downloads history. Every snippet is compiled as part of the documentation build.
1. Reference the library
ViiSync.AccountApi ships as one file, ViiSync.AccountApi.dll, built for net8.0 and net10.0. It is not published
on NuGet. You get the DLL for your target framework together with its XML documentation file
(ViiSync.AccountApi.xml, which gives you IntelliSense) and a .sha256 checksum.
Copy both files into your repository, for example under lib/, and reference the DLL:
<ItemGroup>
<Reference Include="ViiSync.AccountApi">
<HintPath>lib\ViiSync.AccountApi.dll</HintPath>
</Reference>
</ItemGroup>
The DLL depends on nothing but the .NET runtime. Its only public namespace is ViiSync.AccountApi:
using ViiSync.AccountApi;
2. Connect and log in
Create the API with the account login, password, server host and port, then call
Connect(). Connect opens the WebSocket, logs in and loads the account state
(symbols, positions, pending orders and account figures) before it returns.
// Port 443 connects over TLS to wss://<host>/ws; any other port uses ws://<host>:<port>/ws.
var api = new ViiSyncAccountApi(login, password, "server.example.com", 443);
api.OnConnectProgress += (_, e) => Console.WriteLine($"connect: {e.Progress}");
try
{
api.Connect(); // connect + log in + load symbols, positions, orders and account figures
}
catch (ConnectException ex) when (ex.Code == Msg.INVALID_ACCOUNT)
{
Console.WriteLine("login or password refused");
throw;
}
Port 443 connects over TLS to wss://<host>/ws. You can also pass the endpoint URL and set the timeouts explicitly:
var api = new ViiSyncAccountApi(login, password, new Uri("wss://server.example.com/ws"))
{
ConnectTimeout = 20_000, // connect + login + initial state, ms
ExecutionTimeout = 30_000, // how long a trade call waits for the server, ms
AutoReconnect = true, // reconnect after a dropped connection (the default)
};
api.Connect();
If the connection drops, the API reconnects by itself (AutoReconnect), logs in again and reloads the state. OnConnectProgress reports each stage.
A login with the account's investor password opens a read-only session: IsInvestor is true, and every trade or password call throws InvestorModeException.
3. Read the account
The account figures are kept current from the server's pushes, so reading them costs no round trip.
// Kept current from the server's pushes: reading these costs no round trip.
Console.WriteLine($"#{api.Account.Login} {api.AccountCurrency} 1:{api.AccountLeverage} {api.AccountMethod}");
Console.WriteLine($"balance {api.AccountBalance:N2} equity {api.AccountEquity:N2}");
Console.WriteLine($"margin {api.AccountMargin:N2} free {api.AccountFreeMargin:N2} level {api.MarginLevel:N2}%");
api.OnAccountUpdate += a => Console.WriteLine($"equity now {a.AccountEquity:N2}");
4. Symbols
Symbols holds the account's symbol catalog: the specification of each symbol (SymbolInfo), its trade settings in MT5's SymGroup shape and its weekly sessions.
foreach (var name in api.Symbols.Names)
{
SymbolInfo info = api.Symbols.GetInfo(name); // specification
SymGroup trade = api.Symbols.GetGroup(name); // trade settings in MT5's SymGroup shape
Console.WriteLine($"{name}: {info.Digits} digits, contract {info.ContractSize}, " +
$"lots {trade.MinLots}..{trade.MaxLots} step {trade.LotsStep}");
}
5. Quotes
Subscribe to a symbol to stream its quotes to OnQuote. GetQuote returns the latest quote.
api.OnQuote += (_, q) => Console.WriteLine($"{q.Symbol} {q.Bid}/{q.Ask} at {q.Time:HH:mm:ss.fff} UTC");
api.Subscribe("EURUSD");
// The latest quote: the streamed one when subscribed, otherwise one round trip to the server.
Quote? quote = api.GetQuote("EURUSD");
Console.WriteLine(quote is null ? "no price" : $"spread {quote.Ask - quote.Bid}");
api.Unsubscribe("EURUSD");
6. Trade
OrderSend takes MT5's parameter list. A market order returns the opened position; a pending order returns the working order. It returns after the server has answered, and throws ServerException with an MT5 return code when the server refuses the request (see Error codes).
var q = api.GetQuote("EURUSD") ?? throw new InvalidOperationException("no price");
// Same parameter list as MT5's OrderSend. With deviation > 0 and a price, a fill outside price ± deviation
// points is refused with REQUOTE; with deviation 0 the order fills at the live quote.
Order position = api.OrderSend("EURUSD", 0.10, q.Ask, OrderType.Buy,
sl: 0, tp: 0, deviation: 10, comment: "docs", expertID: 42);
Console.WriteLine($"opened position #{position.Ticket} at {position.OpenPrice}");
Modify the SL/TP of a position:
// SL/TP are absolute prices; 0 removes a level.
var info = api.Symbols.GetInfo("EURUSD");
api.OrderModify(position.Ticket, "EURUSD", position.Lots, 0, position.OrderType,
sl: Math.Round(q.Bid - 200 * info.Points, info.Digits), tp: 0);
Close part of it, then the rest:
// Lots below the position's volume close part of it; the rest stays open.
Order part = api.OrderClose(position.Ticket, "EURUSD", 0, 0.04, position.OrderType);
Console.WriteLine($"closed {part.CloseLots} lots, P/L {part.Profit:N2}");
Order closed = api.OrderClose(position.Ticket, "EURUSD", 0, 0, position.OrderType); // the rest
Console.WriteLine($"closed #{closed.Ticket}: P/L {closed.Profit:N2}, commission {closed.Commission:N2}");
Pending orders are placed, moved and cancelled the same way:
var q = api.GetQuote("EURUSD") ?? throw new InvalidOperationException("no price");
Order limit = api.OrderSend("EURUSD", 0.10, Math.Round(q.Bid - 0.0050, 5), OrderType.BuyLimit,
expiration: new Expiration { Type = ExpirationType.Specified, DateTime = DateTime.UtcNow.AddHours(4) });
Console.WriteLine($"pending order #{limit.Ticket} {limit.State}");
// Move it, then cancel it.
api.OrderModify(limit.Ticket, "EURUSD", limit.Lots, Math.Round(q.Bid - 0.0060, 5), limit.OrderType, sl: 0, tp: 0);
api.OrderDelete(limit.Ticket, limit.OrderType, "EURUSD", limit.Lots, 0);
On a hedging account, a position can be closed by an opposite one:
// Hedging accounts: close a position by an opposite one of the same symbol (no spread paid on the pair).
Order closed = api.OrderCloseBy(buyTicket, sellTicket);
Open positions and working orders are always at hand:
foreach (Order o in api.GetOpenedOrders())
Console.WriteLine(o.IsPending
? $"pending #{o.Ticket} {o.OrderType} {o.Lots} {o.Symbol} @ {o.OpenPrice}"
: $"position #{o.Ticket} {o.OrderType} {o.Lots} {o.Symbol} P/L {o.Profit:N2}");
Trading through a dropped connection
A trade call that loses its connection fails with code NO_CONNECTION (a ConnectException or ServerException), and
one whose answer never comes fails with REQUEST_TIMEOUT. In both cases the request may or may not have executed. OrderClientSafe
wraps the API and settles the outcome for you:
// Survives a dropped connection mid-call: waits for the reconnect, then settles the outcome from the server's
// state. An open is never re-sent (it is looked up instead), so it cannot double the exposure.
var safe = new OrderClientSafe(api) { TradeTimeoutSafe = 60_000 };
Order o = safe.OrderSend("EURUSD", 0.10, 0, OrderType.Buy);
safe.OrderClose(o.Ticket, "EURUSD", 0, 0, o.OrderType);
7. History
DownloadOrderHistory returns the trades closed in a window (one Order per closed position, built from its deals as the MT5 terminal does), every deal of the window and every order placed in it.
var to = DateTime.UtcNow;
var from = to.AddDays(-7);
OrderHistoryEventArgs h = api.DownloadOrderHistory(from, to);
Console.WriteLine($"{h.Orders.Count} closed trades, {h.InternalDeals.Count} deals, {h.InternalOrders.Count} orders");
foreach (Order trade in h.Orders)
Console.WriteLine($"#{trade.Ticket} {trade.Symbol} {trade.OpenTime:u} -> {trade.CloseTime:u} P/L {trade.Profit:N2}");
Bars come in any timeframe, and raw ticks are available too:
// Any timeframe in minutes: 1, 5, 15, 30, 60, 240, 1440, 10080 (W1), 43200 (MN1) ...
Bar[] h1 = api.DownloadQuoteHistory("EURUSD", DateTime.UtcNow.AddDays(-2), DateTime.UtcNow, 60);
TickBar[] ticks = api.DownloadTickHistory("EURUSD", DateTime.UtcNow.AddMinutes(-5), DateTime.UtcNow);
Console.WriteLine($"{h1.Length} H1 bars, {ticks.Length} ticks");
8. Events
The API raises MT5's events with MT5's delegate shapes, plus a few ViiSync extras (OnAccountUpdate, OnLog).
// Events are raised on the API's receive loop: keep handlers short and never block in them.
api.OnOrderUpdate += (_, u) => Console.WriteLine($"{u.Type} {u.TradeType} {u.Order}");
api.OnOrderProgress += (_, p) => Console.WriteLine($"request {p.TradeRequest.RequestId}: {p.Type} {p.TradeResult.Status}");
api.OnConnectProgress += (_, e) => Console.WriteLine($"connection: {e.Progress}");
api.OnLog += (_, line) => Console.WriteLine($"log: {line}");
Important
Events are raised on the API's receive loop. A slow handler delays every later push, quotes included. Handlers that
run longer than ProcessEventTimeoutMs are reported on OnLog and counted
in SlowEventHandlers. Hand long work to another thread.
9. Async and disconnect
Every blocking call has an …Async twin that takes a CancellationToken:
await api.ConnectAsync(ct);
Order o = await api.OrderSendAsync("EURUSD", 0.10, 0, OrderType.Buy, cancellation: ct);
await api.OrderCloseAsync(o.Ticket, "EURUSD", 0, 0, o.OrderType, cancellation: ct);
await api.DisconnectAsync();
api.Disconnect(); // or: using var api = new ViiSyncAccountApi(...); Dispose disconnects
Next
- Migrating from the MT5 API
- Sessions and time: every time is UTC
- The full API reference and the MT5 mapping