Skip to content

Instantly share code, notes, and snippets.

@zallesov
Created April 27, 2025 07:51
Show Gist options
  • Select an option

  • Save zallesov/fa94d504e69c65de138a51250062c13a to your computer and use it in GitHub Desktop.

Select an option

Save zallesov/fa94d504e69c65de138a51250062c13a to your computer and use it in GitHub Desktop.
Meta API Connector
import MetaApi from 'metaapi.cloud-sdk';
import { Order } from './types/Order';
import { PnL } from './types/PnL';
import { Position } from './types/Position';
import { error, log } from './logging';
import { ConditionalOrderProps, IBroker, SubmitFuturesOrderProps } from './IBroker';
import { MetatraderAccount, RpcMetaApiConnectionInstance } from 'metaapi.cloud-sdk';
export class MetaAPIBroker implements IBroker {
private api: MetaApi;
private account!: MetatraderAccount; // MetaTrader account type from SDK
private connection!: RpcMetaApiConnectionInstance; // RPC connection type from SDK
private static clients: Map<string, MetaAPIBroker> = new Map();
static getClient(token: string, login: string, password: string, serverName: string) {
if (!this.clients.has(token)) {
this.clients.set(token, new MetaAPIBroker(token, login, password, serverName));
}
return this.clients.get(token)!;
}
constructor(private token: string, private login: string, private password: string, private serverName: string) {
this.api = new MetaApi(this.token);
}
async connect() {
log('Connecting to MetaApi with account:', this.login);
if (!this.account) {
const accounts = await this.api.metatraderAccountApi.getAccountsWithInfiniteScrollPagination();
this.account = accounts.find(a => a.login === this.login && a.type.startsWith('cloud'))!;
if (!this.account) {
log('Adding MT5 account to MetaApi');
// @ts-ignore
this.account = await this.api.metatraderAccountApi.createAccount({
name: 'Test account',
type: 'cloud' as any,
login: this.login,
password: this.password,
server: this.serverName,
platform: 'mt5',
magic: 1000,
});
} else {
log('MT5 account already added to MetaApi', this.account.id);
}
}
// wait until account is deployed and connected to broker
// eslint-disable-next-line semi
log('Deploying account');
await this.account.deploy();
// eslint-disable-next-line semi
log('Waiting for API server to connect to broker (may take couple of minutes)');
await this.account.waitConnected();
// connect to MetaApi API
this.connection = this.account.getRPCConnection();
await this.connection.connect();
// wait until terminal state synchronized to the local state
log('Waiting for SDK to synchronize to terminal state (may take some time depending on your history size)');
await this.connection.waitSynchronized();
log('Connected to MetaApi with account:', this.account.id);
}
async getAccountCurrencyExchange(quoteCurrency: string, accountCurrency: string): Promise<number> {
log('Fetching account currency exchange rate for:', { quoteCurrency, accountCurrency });
const rate = await this.connection.getSymbolPrice(`${accountCurrency}${quoteCurrency}`, false);
log('Fetched account currency exchange rate:', rate);
return 1 / ((rate.ask + rate.bid) / 2);
}
async getCurrentPrice(symbol: string): Promise<number> {
log('Fetching current price for symbol:', symbol);
const price = await this.connection.getSymbolPrice(symbol, false);
log('Fetched current price:', price);
return (price.ask + price.bid) / 2;
}
async getAvailableBalance(coin: string): Promise<number> {
log('Fetching available balance for coin:', coin);
const balance = await this.connection.getAccountInformation();
log('Fetched available balance:', balance.balance);
return balance.balance;
}
async getOpenPositions(symbol: string): Promise<Position[]> {
log('Fetching open positions for symbol:', symbol);
const positions = await this.connection.getPositions();
log('Fetched open positions:', positions);
return positions
.filter(pos => pos.symbol === symbol)
.map(position => ({
symbol: position.symbol,
side: position.type === 'POSITION_TYPE_BUY' ? 'Buy' : 'Sell',
leverage: 1,
size: position.volume,
avgPrice: position.openPrice,
positionValue: position.volume * position.openPrice,
createdTime: new Date(position.time),
}));
}
async getOpenOrders(symbol: string): Promise<Order[] | undefined> {
log('Fetching open orders for symbol:', symbol);
const orders = await this.connection.getOrders();
log('Fetched open orders:', orders);
return orders
.filter(order => order.symbol === symbol)
.map(order => ({
symbol: order.symbol,
side: order.type.includes('BUY') ? 'Buy' : 'Sell',
quantity: order.volume,
orderType: order.type.includes('LIMIT') ? 'Limit' : 'Market',
limitPrice: order.openPrice,
orderId: order.id.toString(),
status: 'submitted',
executionTime: new Date(order.time),
leverage: 1,
stopLoss: order.stopLoss,
takeProfit: order.takeProfit,
}));
}
async getOpenOrder(symbol: string, side: 'buy' | 'sell'): Promise<Order | undefined> {
log('Fetching open order for symbol and side:', { symbol, side });
const order = await this.getOpenOrders(symbol);
log('Fetched open order:', order);
return order?.find(order => order.side.toLowerCase() === side.toLowerCase());
}
async cancelOrder(orderId: string, symbol: string): Promise<any> {
log('Cancelling order with ID:', orderId);
const response = await this.connection.cancelOrder(orderId);
log('Cancelled order response:', response);
return response;
}
async submitConditionalOrder(props: ConditionalOrderProps): Promise<Order> {
log('Submitting conditional order with props:', props);
try {
let response;
if (props.side === 'Buy') {
response = await this.connection.createStopLimitBuyOrder(
props.symbol,
Number(props.quantity),
Number(props.price),
Number(props.triggerPrice),
Number(props.stopLoss),
Number(props.takeProfit),
);
} else {
response = await this.connection.createStopLimitSellOrder(
props.symbol,
Number(props.quantity),
Number(props.price),
Number(props.triggerPrice),
Number(props.stopLoss),
Number(props.takeProfit),
);
}
const currentPrice = await this.getCurrentPrice(props.symbol);
log('Submitted conditional order response:', response);
return {
symbol: props.symbol,
side: props.side,
quantity: Number(props.quantity),
leverage: props.leverage,
orderType: 'Limit',
limitPrice: Number(props.price),
triggerPrice: Number(props.triggerPrice),
currentPrice,
orderId: response.orderId,
stopLoss: props.stopLoss ? Number(props.stopLoss) : undefined,
takeProfit: props.takeProfit ? Number(props.takeProfit) : undefined,
executionTime: new Date(),
status: 'submitted',
response
};
} catch (err) {
error('MetaAPI failed to submit conditional order', err);
throw err;
}
}
async submitFuturesOrder(props: SubmitFuturesOrderProps): Promise<Order> {
log('Submitting futures order with props:', props);
try {
const response = await this.connection.createLimitBuyOrder(
props.symbol,
Number(props.quantity),
Number(props.price),
props.stopLoss ? Number(props.stopLoss) : undefined,
props.takeProfit ? Number(props.takeProfit) : undefined,
);
const currentPrice = await this.getCurrentPrice(props.symbol);
log('Submitted futures order response:', response);
return {
symbol: props.symbol,
side: props.side,
quantity: Number(props.quantity),
leverage: props.leverage,
orderType: props.type,
limitPrice: props.price ? Number(props.price) : undefined,
currentPrice,
orderId: "orderId",
stopLoss: props.stopLoss ? Number(props.stopLoss) : undefined,
takeProfit: props.takeProfit ? Number(props.takeProfit) : undefined,
executionTime: new Date(),
status: 'submitted',
reduceOnly: props.reduceOnly,
response: "response"
};
} catch (err) {
error('MetaAPI failed to submit futures order', err);
throw err;
}
}
async getPnl(symbol: string): Promise<PnL> {
log('Fetching PnL for symbol:', symbol);
const deals = await this.connection.getDealsByTimeRange(
new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
new Date()
);
const filteredDeals = deals[0] //.filter(deal => deal.symbol === symbol);
if (filteredDeals.length === 0) {
return {
symbol,
closedPnl: 0,
side: 'Buy',
leverage: 1,
orderId: '',
cumEntryValue: 0,
avgEntryPrice: 0,
cumExitValue: 0,
avgExitPrice: 0,
closedSize: 0
};
}
return filteredDeals.reduce((acc, deal) => ({
symbol,
side: deal.type.includes('BUY') ? 'Buy' : 'Sell',
leverage: deal.leverage || 1,
orderId: deal.orderId,
closedPnl: acc.closedPnl + deal.profit,
cumEntryValue: acc.cumEntryValue + (deal.volume * deal.entryPrice),
avgEntryPrice: (acc.avgEntryPrice + deal.entryPrice) / 2,
cumExitValue: acc.cumExitValue + (deal.volume * deal.exitPrice),
avgExitPrice: (acc.avgExitPrice + deal.exitPrice) / 2,
closedSize: acc.closedSize + deal.volume
}), {
symbol,
closedPnl: 0,
side: 'Buy' as const,
leverage: 1,
orderId: '',
cumEntryValue: 0,
avgEntryPrice: 0,
cumExitValue: 0,
avgExitPrice: 0,
closedSize: 0
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment