General information #
SignalR makes possible to have two-way connectivity with web server to get/send (streaming) data in real-time. For example, you can get quotes, margin updates of your MT4 clients, online users list, open orders at real time as it happens in MT4. Once you get subscribed to whatever you need you would get updates, no need to ask web server about new portion of data every time.
Quick examples #
Here you can get working examples. Simply open source code of any page below and get client side code which demonstrates basic while negotiating with SignalR endpoint.
This example shows data coming from server every second - current server time. No authentication needed.
https://cloud.MyWebAPI.com/DemoSignalR/Test - This example shows how you can get real-time date from WebAPI host server.
https://cloud.mywebapi.com/DemoSignalR/MT4 - Be aware, you need to adjust page source code (to make it work) to whatever you have for your installation (client_id, client_secret, tradeplatform_id, etc). Get page source, adjust settings and run the page.
How to use #
Add links to required libraries within your page's header:
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.ui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/aspnet-signalr/1.1.0/signalr.js"></script>
Then create connection to SignalR endpoint:
// access token obtained after authentication should be saved here
var access_token = '';
// create connection
const connection = new signalR.HubConnectionBuilder()
.withUrl('@Context.Request.Scheme://@Context.Request.Host/hubs/mt4/v1',
{
accessTokenFactory: () => {
//show("AccessTokenProvider()");
return access_token;
},
// let's skip negotiation and start right with websockets
transport: (signalR.HttpTransportType.WebSockets),
skipNegotiation: true
})
.configureLogging(signalR.LogLevel.Debug)
.build();
To connect just call:
await connection.start();
You need an authentication token obtained from authentication server (auth.cplugin.net). Once you get a token, provide it to SignalR client through accessTokenFactory() function
Features #
There are few hubs available to work with. Test and MT4. More details about each one you can find in corresponding section.
MT4 Hub #
To step further, first of all, you have to be Connected to WebSockets endpoint. Then you can subscribe/unsubscribe to/form events and receive updates.
This hub expose connectivity with MT4 server features. For example:
- get prices at real-time on your web page
- get clients' margin updates
- etc
Also assumed you have already connected to SignalR endpoint.
To get prices at real-time you can subscribe on interested symbols:
connection
.invoke("SubscribeToTicks", tradePlatform, "EURUSD")
.then((res) => { console.log("Subscribed to ticks (" + this + ") : " + res); })
.catch(err => console.error(err));
To determine with what server you are going to work you have to specify its identifier explicitly.
To unsubscribe there is opposite function:
connection
.invoke("UnsubscribeFromTicks", tradePlatform, "EURUSD")
.then((res) => { console.log("Unsubscribed from ticks (EURUSD) : " + res); })
.catch(err => console.error(err));
Once you get subscribed you start getting quotes immediately to your client side code:
connection.on("onTick", function(tradePlatform, tick) { your code here } )
Interfaces #
Up-to-date interface can always be downloaded from here: CPlugin.WebAPI.Realtime.ts (mywebapi.com)
import { HubConnection, IStreamResult } from "@microsoft/signalr"
export class TestHub {
constructor(private connection: HubConnection) {
}
getServerTime(): Promise<Date> {
return this.connection.invoke('GetServerTime');
}
ping(message: string): Promise<Void> {
return this.connection.invoke('Ping', message);
}
registerCallbacks(implementation: ITestHubCallbacks) {
this.connection.on('Pong', (message) => implementation.pong(message));
this.connection.on('OnClock', (dt) => implementation.onClock(dt));
}
unregisterCallbacks(implementation: ITestHubCallbacks) {
this.connection.off('Pong', (message) => implementation.pong(message));
this.connection.off('OnClock', (dt) => implementation.onClock(dt));
}
}
export interface ITestHubCallbacks {
pong(message: string): void;
onClock(dt: Date): void;
}
export class MT4Hub {
constructor(private connection: HubConnection) {
}
connect(args: ConnectArgs): Promise<boolean> {
return this.connection.invoke('Connect', args);
}
subscribeToTicks(args: SubscribeToTicksArgs): Promise<void> {
return this.connection.invoke('SubscribeToTicks', args);
}
subscribeToAllTicks(args: TradePlatformId): Promise<void> {
return this.connection.invoke('SubscribeToAllTicks', args);
}
unsubscribeFromAllTicks(args: TradePlatformId): Promise<void> {
return this.connection.invoke('UnsubscribeFromAllTicks', args);
}
unsubscribeFromTicks(args: SubscribeToTicksArgs): Promise<void> {
return this.connection.invoke('UnsubscribeFromTicks', args);
}
subscribeToSymbols(args: TradePlatformId): Promise<void> {
return this.connection.invoke('SubscribeToSymbols', args);
}
unsubscribeFromSymbols(args: TradePlatformId): Promise<void> {
return this.connection.invoke('UnsubscribeFromSymbols', args);
}
subscribeToOnlineUpdates(args: TradePlatformId): Promise<void> {
return this.connection.invoke('SubscribeToOnlineUpdates', args);
}
unsubscribeFromOnlineUpdates(args: TradePlatformId): Promise<void> {
return this.connection.invoke('UnsubscribeFromOnlineUpdates', args);
}
streamTrades(args: TradePlatformId): IStreamResult<TradeExEventPayload> {
return this.connection.stream('StreamTrades', args);
}
streamAllTicks(args: TradePlatformId): IStreamResult<SymbolInfo> {
return this.connection.stream('StreamAllTicks', args);
}
streamMarginCallUpdates(args: TradePlatformId): IStreamResult<MarginLevel> {
return this.connection.stream('StreamMarginCallUpdates', args);
}
streamUserUpdates(args: TradePlatformId): IStreamResult<UserUpdate> {
return this.connection.stream('StreamUserUpdates', args);
}
registerCallbacks(implementation: IMT4HubCallbacks) {
this.connection.on('OnMT4ConnectionStatus', (data) => implementation.onMT4ConnectionStatus(data));
this.connection.on('OnTick', (data) => implementation.onTick(data));
this.connection.on('OnTicks', (data) => implementation.onTicks(data));
this.connection.on('OnTicksOHLC', (data) => implementation.onTicksOHLC(data));
this.connection.on('OnMarginUpdate', (data) => implementation.onMarginUpdate(data));
this.connection.on('OnSymbolUpdate', (data) => implementation.onSymbolUpdate(data));
this.connection.on('OnOnlineUpdate', (data) => implementation.onOnlineUpdate(data));
this.connection.on('OnDebug', (data) => implementation.onDebug(data));
}
unregisterCallbacks(implementation: IMT4HubCallbacks) {
this.connection.off('OnMT4ConnectionStatus', (data) => implementation.onMT4ConnectionStatus(data));
this.connection.off('OnTick', (data) => implementation.onTick(data));
this.connection.off('OnTicks', (data) => implementation.onTicks(data));
this.connection.off('OnTicksOHLC', (data) => implementation.onTicksOHLC(data));
this.connection.off('OnMarginUpdate', (data) => implementation.onMarginUpdate(data));
this.connection.off('OnSymbolUpdate', (data) => implementation.onSymbolUpdate(data));
this.connection.off('OnOnlineUpdate', (data) => implementation.onOnlineUpdate(data));
this.connection.off('OnDebug', (data) => implementation.onDebug(data));
}
}
export interface IMT4HubCallbacks {
onMT4ConnectionStatus(data: MT4ConnectionStatusArgs): void;
onTick(data: TickArgs): void;
onTicks(data: TicksArgs): void;
onTicksOHLC(data: TicksOHLCArgs): void;
onMarginUpdate(data: MarginUpdateArgs): void;
onSymbolUpdate(data: SymbolUpdateArgs): void;
onOnlineUpdate(data: OnlineUpdateArgs): void;
onDebug(data: DebugArgs): void;
}
export class MT5Hub {
constructor(private connection: HubConnection) {
}
connect(args: ConnectArgs2): Promise<boolean> {
return this.connection.invoke('Connect', args);
}
registerCallbacks(implementation: IMT5HubCallbacks) {
this.connection.on('OnMT5ConnectionStatus', (tradePlatform, connected) => implementation.onMT5ConnectionStatus(tradePlatform, connected));
}
unregisterCallbacks(implementation: IMT5HubCallbacks) {
this.connection.off('OnMT5ConnectionStatus', (tradePlatform, connected) => implementation.onMT5ConnectionStatus(tradePlatform, connected));
}
}
export interface IMT5HubCallbacks {
onMT5ConnectionStatus(tradePlatform: string, connected: boolean): void;
}
export interface Void {
}
export interface ConnectArgs {
tradePlatform: string;
/** Timeout of pumping connection being UP and running. In milliseconds;
default = 30 seconds; */
timeoutMs: number;
}
export interface SubscribeToTicksArgs {
tradePlatform: string;
symbol: string | undefined;
}
export interface TradePlatformId {
tradePlatform: string;
}
export interface TradeExEventPayload extends TradeRecord {
/** add - order opened, delete - order closed, Update - IF login==0 THEN order deleted ELSE order updated */
updateType: TransactionType;
}
export enum TransactionType {
Add = "Add",
Delete = "Delete",
Update = "Update",
ChangeGrp = "ChangeGrp",
}
export interface TradeRecord {
order: number;
login: number;
symbol: string | undefined;
openTime: Date;
closeTime: Date;
expiration: Date;
gatewayVolume: number;
gatewayVolumeLots: number;
digits: number;
volume: number;
volumeLots: number;
tradeRecordState: TradeRecordState;
openPrice: number;
sl: number;
tp: number;
commission: number;
commissionAgent: number;
convReserv: string | undefined;
tradeRecordReason: TradeRecordReason;
storage: number;
closePrice: number;
profit: number;
taxes: number;
magic: number;
comment: string | undefined;
gatewayOrder: number;
activationType: ActivationType;
gatewayOpenPrice: number;
gatewayClosePrice: number;
marginRate: number;
timeStamp: Date;
apiData: number[] | undefined;
convRates: number[] | undefined;
tradeCommand: TradeCommand;
}
export enum TradeRecordState {
OpenNormal = "OpenNormal",
OpenRemand = "OpenRemand",
OpenRestored = "OpenRestored",
ClosedNormal = "ClosedNormal",
ClosedPart = "ClosedPart",
ClosedBy = "ClosedBy",
Deleted = "Deleted",
}
export enum TradeRecordReason {
Client = "Client",
Expert = "Expert",
Dealer = "Dealer",
Signal = "Signal",
Gateway = "Gateway",
Mobile = "Mobile",
Web = "Web",
API = "API",
}
export enum ActivationType {
None = "None",
SL = "SL",
TP = "TP",
Pending = "Pending",
Stopout = "Stopout",
StopOutRollback = "StopOutRollback",
PendingRollback = "PendingRollback",
TPRollback = "TPRollback",
SLRollback = "SLRollback",
}
export enum TradeCommand {
Buy = "Buy",
Sell = "Sell",
BuyLimit = "BuyLimit",
SellLimit = "SellLimit",
BuyStop = "BuyStop",
SellStop = "SellStop",
Balance = "Balance",
Credit = "Credit",
}
export interface SymbolInfo {
lastTime: Date | undefined;
visible: boolean;
symbol: string | undefined;
digits: number;
count: number;
type: number;
point: number;
spread: number;
spreadBalance: number;
direction: SymbolPriceDirection;
updateFlag: number;
bid: number;
ask: number;
high: number;
low: number;
commission: number;
commType: CommissionType;
}
export enum SymbolPriceDirection {
Up = "Up",
Down = "Down",
None = "None",
}
export enum CommissionType {
Money = "Money",
Pips = "Pips",
Percent = "Percent",
}
export interface MarginLevel {
group: string | undefined;
login: number;
leverage: number;
updated: number;
balance: number;
equity: number;
volume: number;
margin: number;
free: number;
level: number;
controllingType: MarginControllingType;
levelType: MarginLevelType;
}
export enum MarginControllingType {
Percent = "Percent",
Currency = "Currency",
}
export enum MarginLevelType {
Ok = "Ok",
MarginCall = "MarginCall",
StopOut = "StopOut",
}
export interface UserUpdate {
type: TransactionType;
user: UserRecord | undefined;
}
export interface UserRecord {
regDate: Date;
lastDate: Date;
timeStamp: Date;
login: number;
group: string | undefined;
password: string | undefined;
enable: number;
enableChangePassword: number;
enableReadOnly: number;
enableOTP: number;
enableReserved: number[] | undefined;
passwordInvestor: string | undefined;
passwordPhone: string | undefined;
name: string | undefined;
country: string | undefined;
city: string | undefined;
state: string | undefined;
zipCode: string | undefined;
address: string | undefined;
leadSource: string | undefined;
phone: string | undefined;
email: string | undefined;
comment: string | undefined;
id: string | undefined;
status: string | undefined;
leverage: number;
agentAccount: number;
lastIP: number;
balance: number;
prevMonthBalance: number;
prevBalance: number;
credit: number;
interestRate: number;
taxes: number;
prevMonthEquity: number;
prevEquity: number;
reserved2: number[] | undefined;
otpSecret: string | undefined;
secureReserved: string | undefined;
sendReports: number;
mqid: number;
userColor: number;
unused: string | undefined;
apiData: string | undefined;
}
export interface MT4ConnectionStatusArgs extends TradePlatformId {
connected: boolean;
}
export interface TickArgs extends TradePlatformId {
tick: Tick | undefined;
}
export interface Tick {
symbol: string | undefined;
bid: number;
ask: number;
lastTime: Date | undefined;
}
export interface TicksArgs extends TradePlatformId {
ticks: TickArgs[] | undefined;
}
export interface TicksOHLCArgs extends TradePlatformId {
ticks: OHLCS[] | undefined;
}
export interface OHLCS {
/** Priced being at the beginning of interval */
open: BidAsk | undefined;
/** highest ever bid and ask, calculated separately. */
maximum: BidAsk | undefined;
/** lowest ever bid and ask, calculated separately. */
minimum: BidAsk | undefined;
/** Priced being at the end of interval */
close: BidAsk | undefined;
symbol: string | undefined;
lastTime: Date | undefined;
/** number of ticks before groupping */
volume: number;
}
export interface BidAsk {
bid: number;
ask: number;
}
export interface MarginUpdateArgs extends TradePlatformId {
margins: MarginLevel[] | undefined;
}
export interface SymbolUpdateArgs extends TradePlatformId {
type: TransactionType;
symbol: ConSymbol | undefined;
}
export interface ConSymbol {
symbol: string | undefined;
description: string | undefined;
source: string | undefined;
currency: string | undefined;
type: number;
digits: number;
tradeMode: TradeMode;
backgroundColor: number;
count: number;
countOriginal: number;
realtime: number;
starting: Date;
expiration: Date;
sessions: ConSessions[] | undefined;
profitCalculationMode: ProfitCalculationMode;
profitReserved: number;
filter: number;
filterCounter: number;
filterLimit: number;
filterSmoothing: number;
filterReserved: number;
logging: number;
spread: number;
spreadBalance: number;
symbolExecMode: SymbolExecMode;
swapEnable: number;
swapType: SwapType;
swapLong: number;
swapShort: number;
swapRollover3Days: number;
contractSize: number;
tickValue: number;
tickSize: number;
stopsLevel: number;
gtcMode: GTCMode;
marginCalculationMode: MarginCalculationMode;
marginInitial: number;
marginMaintenance: number;
marginHedged: number;
marginDivider: number;
percentage: number;
point: number;
multiply: number;
bidTickValue: number;
askTickValue: number;
longOnly: number;
instantMaxVolume: number;
marginCurrency: string | undefined;
freezeLevel: number;
marginHedgedStrong: number;
valueDate: Date;
quotesDelay: number;
swapOpenPrice: number;
swapVariationMargin: number;
}
export enum TradeMode {
No = "No",
Close = "Close",
Full = "Full",
}
export interface ConSessions {
quoteOvernight: number;
tradeOvernight: number;
quote: ConSession[] | undefined;
trade: ConSession[] | undefined;
}
export interface ConSession {
openHour: number;
openMin: number;
closeHour: number;
closeMin: number;
align: number[] | undefined;
}
export enum ProfitCalculationMode {
Forex = "Forex",
CFD = "CFD",
Futures = "Futures",
}
export enum SymbolExecMode {
Request = "Request",
Instant = "Instant",
Market = "Market",
}
export enum SwapType {
Points = "Points",
Dollars = "Dollars",
Interest = "Interest",
MarginCurrency = "MarginCurrency",
}
export enum GTCMode {
Daily = "Daily",
GTC = "GTC",
DailyNoStops = "DailyNoStops",
}
export enum MarginCalculationMode {
Forex = "Forex",
CFD = "CFD",
Futures = "Futures",
CFDIndex = "CFDIndex",
CFDLeverage = "CFDLeverage",
}
export interface OnlineUpdateArgs extends TradePlatformId {
type: TransactionType;
login: number;
}
export interface DebugArgs extends TradePlatformId {
category: string | undefined;
message: string | undefined;
data: any | undefined;
}
export interface ConnectArgs2 extends TradePlatformId {
/** Timeout of pumping connection being UP and running. In milliseconds;
default = 30 seconds; */
timeoutMs: number;
}
Test hub #
This hub created to let you get general ideas working with SignalR. There are nothing special. It does not even require you to be authenticated.
To get fully working code navigate to https://cloud.myWebAPI.com/DemoSignalR/Test and get page source code.
To access hub you can get a link to it:
const connection = new signalR.HubConnectionBuilder()
.withUrl('https://cloud.myWebAPI.com/hubs/test/v1')
.configureLogging(signalR.LogLevel.Debug)
.build();
Method 'ping(string)'
// You can pass any string to this method:
connection
.invoke("Ping", "test message")
.then(() => console.log("Ping request sent"))
.catch(err => console.error(err));
Server will be returned back through client side callback 'pong' exactly the same you have sent there:
connection.on("pong",
function(msg) {
console.log("pong: " + msg);
});
Get server time
// Each second server will send its time through client side callback as of:
connection.on("onClock",
function(clock) {
console.log(clock);
});