Be connected. How to integrate PTMC with your broker
Hey there!
We are glad to present to the community our new Integration API. It’s a set of classes and methods, which allows you to integrate PTMC platform with any broker or data provider by yourself. All you need are notions of trading and C# language. This is the first version of our Integration API, and we plan to extend and develop its opportunities with taking into account users’ desires. That’s why we appreciate your ideas and remarks.
From the technical point, the integration consists in main methods realization, necessary for cooperation between the platform and a broker or a data provider: connection, order placement, history reception. Feel free to use any broker API in your integration, like REST, FIX, any proprietary protocols: there are no restrictions, everything depends upon your own preferences and broker’s data.
We are going to show you the opportunities of Integration API via viewing the real example of integration with new v20 REST API from Oanda. Information about this API is available on their official web site. The main emphasis in this article will be on Integration API, but in case you are interested in discovering full details of cooperation between PTMC and Oanda, see the source code in GitHub.
Preparation
First, execute the following steps:
- Create a dynamic library project in your development environment
- Add a link to PlatformAPI.dll from PTMC terminal installation path
- Describe inheritor from the class Vendor and override necessary methods.
To test the integration, place your library and all other ones bound to it in the folder of terminal location. After this, it will be available in the vendors list in the window “Connection settings”.
Now everything is ready to start work. Let’s go!
Connection
Work with any integration begins with mechanisms of connection to a broker adjustment. The API has the method Connect, and its realization makes your PTMC platform work with a broker or a data provider. Sometimes you may need additional settings, like an account type, demo or real. They are available in the terminal’s “Connection settings” window:
GetConnectionParameters method defines which settings the integration requires. In our case with Oanda we provide opportunity of choosing a connection type:
public override List<SettingItem> GetConnectionParameters() { List<SettingItem> parameters = new List<SettingItem>(); // Demo/Real connection type var settingItem = new SettingItemComboBox(CONNECTION, CONNECTION_DEMO, new List<string> { CONNECTION_DEMO, CONNECTION_REAL }); parameters.Add(settingItem); return parameters; }
This method results into SettingItem array of objects, each of which defines a type, a name and values of a setting.
Let’s realize a connection method. As its parameters, we get a set of the same settings, which we defined above in GetConnectionParameters method. These settings contain values that a user sets:
public override ConnectionResult Connect(List<SettingItem> parameters) { ConnectionResult result = new ConnectionResult(); bool isLive = false; string user = string.Empty; string password = string.Empty; // // Check passed parameters // SettingItem settingItem = parameters.GetItemByName(Vendor.LOGIN_PARAMETER_USER); if (settingItem != null && settingItem.Value is string) user = (string)settingItem.Value; settingItem = parameters.GetItemByName(Vendor.LOGIN_PARAMETER_PASSWORD); if (settingItem != null && settingItem.Value is string) password = (string)settingItem.Value; settingItem = parameters.GetItemByName(CONNECTION); if (settingItem != null && settingItem.Value is string) isLive = (string)settingItem.Value == CONNECTION_REAL; string returnedToken = password.Length > 20 ? password : string.Empty; if (string.IsNullOrEmpty(returnedToken)) { result.State = ConnectionState.Fail; result.Message = "User/password combination is not valid."; return result; } // // Connect to Oanda via passed parameters // Credentials.SetCredentials(isLive ? EEnvironment.Trade : EEnvironment.Practice, returnedToken); result.State = ConnectionState.Success; return result; }
Accounts and rules
Account is one of the clue objects, necessary for a correct platform’s work, that’s why our next step is a GetAccounts method overriding. This way we return information concerning all available accounts with actual data to the logged in user. They become visible to the user in the terminal for getting reports, for trading, data filtration, etc:
public override IList<Account> GetAccounts() { List<Account> accounts = new List<Account>(); // // Get accounts from Oanda an API // List<AccountOanda> accountsOanda = Rest.GetAccountListAsync(); ; if (accountsOanda.Count == 0) throw new Exception("The Oanda v20 only has access to the v20 accounts. Please, contact customer service to setup a sub-account that can be used with this platform."); for (int i = 0; i < accountsOanda.Count; i++) { // // Get detailed info // AccountSummaryOanda accountSummary = Rest.GetAccountSummary(accountsOanda[i].Id); if (accountSummary == null) continue; this.accountsSummaryOanda.Add(accountSummary); // // Create accounts and fill with available data // Account account = new Account(); account.AccountId = accountSummary.Id; account.AccountName = accountSummary.Name; account.Currency = accountSummary.Currency; account.marginWarningLevelPercent = accountSummary.MarginRate; account.Balance = accountSummary.Balance; account.RealizedPnl = accountSummary.Pl; account.UsedMargin = accountSummary.MarginUsed; account.AvailableMargin = accountSummary.MarginAvaliable; account.MaintranceMargin = accountSummary.MarginUsed;//?? OandaV20Utils.FillAccountAditionalInfo(account, null); accounts.Add(account); positions[accountSummary.Id] = new ConcurrentDictionary<string, Position>(); } return accounts; }
PTMC platform represents a large set of functions, which assure work with all assets: futures, options, stocks, forex. If a particular integration doesn’t provide a necessary data set, like Level2 quotes or tick history, you can hide unnecessary functions. Use GetRulesTable method for this:
public override Dictionary<Rule, object> GetRulesTable(Account account) { Dictionary<Rule, object> ruleSet = new Dictionary<Rule, object>(); ruleSet[Rule.FUNCTION_OE2014] = Vendor.NOT_ALLOWED; ruleSet[Rule.FUNCTION_CHART] = Vendor.ALLOWED; ruleSet[Rule.FUNCTION_CHAT] = Vendor.NOT_ALLOWED; ruleSet[Rule.FUNCTION_NEWS] = Vendor.NOT_ALLOWED; ruleSet[Rule.FUNCTION_SHOW_ORDERS] = Vendor.ALLOWED; ruleSet[Rule.FUNCTION_SHOW_POSITIONS] = Vendor.ALLOWED; ruleSet[Rule.FUNCTION_ACCOUNTS] = Vendor.ALLOWED; ruleSet[Rule.FUNCTION_ACCOUNTPERFOMANCE] = Vendor.NOT_ALLOWED; ruleSet[Rule.FUNCTION_WATCHLIST] = Vendor.ALLOWED; ruleSet[Rule.FUNCTION_FXBOARD] = Vendor.ALLOWED; ruleSet[Rule.FUNCTION_LEVEL3] = Vendor.NOT_ALLOWED; ruleSet[Rule.FUNCTION_MATRIX] = Vendor.ALLOWED; return ruleSet; }
Trading instruments
These trading objects are necessary for a trader to view history, to trade, get quotes and so on. You can specify all instruments, available at the moment of connection, and information about them in GetInstruments method. This mechanism is similar to getting accounts. Actually, the bigger part of the API works in accordance with this principle: the terminal requests data from a vendor, and it should be provided.
public override IList<Instrument> GetInstruments() { // // Get instruments from Oanda an API // List<InstrumentOanda> instrumentsOanda = Rest.GetAllInstrumentsAsync(accountsSummaryOanda[0].Id); List<Instrument> instruments = new List<Instrument>(); foreach (InstrumentOanda instrumentOanda in instrumentsOanda) { instrumentOanda.Name = instrumentOanda.Name.ToUpper(); this.instruments[instrumentOanda.Id] = instrumentOanda; // // Create instruments and fill with available data // Instrument instrument = new Instrument(); instrument.Id = instrumentOanda.Id; instrument.Name = instrumentOanda.Name.ToUpper(); instrument.InstrumentType = OandaV20Utils.GetInstrumentType(instrumentOanda.InstrumentType); instrument.InstrumentGroup = OandaV20Utils.GetInstrumentGroup(instrument.Name); instrument.DefaultHistoryType = HistoryDataTypes.Bid; instrument.LotSize = instrumentOanda.MinimumTradeSize; instrument.PointSize = Math.Pow(0.1, instrumentOanda.DisplayPrecision); instrument.Precision = (byte)instrumentOanda.DisplayPrecision; instrument.MaxLot = instrumentOanda.MaximumOrderUnits; OandaV20Utils.ExtractExp1Exp2(instrumentOanda.Id, out instrument.Exp1, out instrument.Exp2); instruments.Add(instrument); } return instruments; }
Subscription to quotes
After adding an instrument to a panel, like Watchlist or Market Depth, a user expects to see its quotes. For this realize SubscribeSymbol and UnSubscribeSymbol methods to see quotes for the instrument. As a parameter we get an instrument name to subscribe to and one of quotes types: Level1, Level2 or Trade. Oanda v20 provides only Level1 and Level2:
public override bool SubscribeSymbol(SubscribeQuotesParameters parameters) { // // Level1 subscription cache // if (parameters.subscribeType == SubscribeQuoteType.Level1) { if (!subscribedLevel1Instruments.Contains(parameters.symbol)) subscribedLevel1Instruments.Add(parameters.symbol); return true; } // // Level2 subscription cache // if (parameters.subscribeType == SubscribeQuoteType.Level2) { if (!subscribedLevel2Instruments.Contains(parameters.symbol)) subscribedLevel2Instruments.Add(parameters.symbol); return true; } return false; }
Orders, positions, trades
Positions, orders and trades are standard entities that provide data for trading platforms and usually related to an account and an instrument. That’s why it is logically to realize entities of account and instrument, then add maintenance of positions, orders and trades. A set of methods GetPendingOrders, GetPositions, GetTradesHistory allows a trader to view respective information in corresponding panels. The platform calls these methods and data right after a successful connection:
public override IList<Position> GetPositions() { List<Position> positions = new List<Position>(); foreach (AccountSummaryOanda account in accountsSummaryOanda) { // // Get positions from Oanda an API // List<TradeOanda> openTrades = Rest.GetOpenTrades(account.Id); foreach (TradeOanda tradeOanda in openTrades) { // // Create positions and fill with available data // Position position = new Position(); InstrumentOanda inst; if (!instruments.TryGetValue(tradeOanda.InstrumentId, out inst)) continue; position.AccountId = account.Id; position.PositionId = tradeOanda.Id; position.Symbol = inst.Name.ToUpper(); position.OpenPrice = tradeOanda.Price; position.Quantity = Math.Abs(tradeOanda.CurrentUnits); position.Side = OandaV20Utils.GetSide(tradeOanda.CurrentUnits); position.OpenTime = tradeOanda.OpenTime; position.ServerCalculationNetPL = position.ServerCalculationGrossPL = tradeOanda.UnrealizedPL; positions.Add(position); } } return positions; }
Additionally, there is a set of methods to report on user’s portfolio changes to the terminal. These changes can be position opening, order cancellation or modification, etc. A set of such methods includes OnOrderUpdated, OnOrderCancelled, OnPositionUpdated, OnPositionClosed.
History loading
Stepwise we reached chart and history loading. LoadTickHistory and LoadBarHistory are the main methods to define how and where to upload tick and bar history from. Oanda doesn’t provide tick history, that’s why we use only LoadBarHistory method. We get HistoryRequestParameters object as an incoming parameter to indicate which instrument, period and interval the user requested history for. This way we return a bars array to be displayed on the chart:
public override IList<BarHistoryItem> LoadBarHistory(HistoryRequestParameters requestParaeters, CancellationToken token) { DateTime fromTime = requestParaeters.FromTime; DateTime toTime = requestParaeters.ToTime; List<BarHistoryItem> res = null; // // Convert period to Oanda format // GranularityEnum? timeframe = OandaV20Utils.GetOandaTimeframe(requestParaeters.Period); InstrumentOanda inst = instruments.Where(x => x.Value.Name.ToUpper() == requestParaeters.Symbol).FirstOrDefault().Value; // // Prepare request and send to Oanda an API // CandlesRequest req = new CandlesRequest { InstrumentId = inst.Id, price = OandaV20Utils.GetOandaHistoryPrice(requestParaeters.HistoryType), granularity = timeframe, to = toTime }; List<Candle> response = Rest.GetCandles(req); // // Process responce and create BarHistoryItem items // foreach (Candle candle in response) { long ticks = candle.Time.Ticks; if (requestParaeters.HistoryType == HistoryDataTypes.Ask) res.Add(new BarHistoryItem() { LeftHistoryTime = candle.Time, Open = candle.Ask.Open, Close = candle.Ask.Close, High = candle.Ask.High, Low = candle.Ask.Low, Volume = candle.Volume }); else if (requestParaeters.HistoryType == HistoryDataTypes.Bid) res.Add(new BarHistoryItem() { LeftHistoryTime = candle.Time, Open = candle.Bid.Open, Close = candle.Bid.Close, High = candle.Bid.High, Low = candle.Bid.Low, Volume = candle.Volume }); } return res; }
History depth, type and period depend upon a particular integration. PTMC platform supports all basic types, but in case of need, eliminate inaccessible ones by using GetHistoryMetadata method:
public override HistoryMetadata GetHistoryMetadata() { return new HistoryMetadata() { // // Supported history types // HistoryTypes = new HistoryDataTypes[] { HistoryDataTypes.Bid, HistoryDataTypes.Ask, HistoryDataTypes.BidAskAverage }, // // Supported periods // Periods = new int[] { (int)HistoryPeriod.Minute, (int)HistoryPeriod.Hour, (int)HistoryPeriod.Day, (int)HistoryPeriod.Week, (int)HistoryPeriod.Month, } }; }
Trading operations
Our API allows to integrate PTMC platform with any data feed as well as with any broker, providing a trading interface. Such panels and functions, as Order Entry, Visual Trading from the chart, order cancellation or modification will work correctly if you realize a particular set of methods. The main one in the set is PlaceOrder method. Correctly process the incoming parameters from OrderParameters object and send an order placement request to a broker:
public override TradingOperationResult PlaceOrder(OrderParameters orderData) { var result = new TradingOperationResult(); // // Prepare request // var orderRequest = new PlaceOrderRequest(); OandaV20Utils.CreateOrderRequest(orderData, orderRequest); // // Send request to Oanda server // Rest.PostOrder(orderData.Account.AccountId, orderRequest); return result; }
There are also methods for opened orders and positions management: ModifyOrder, CancelOrder, ClosePosition.
Reports
It happens, the API provides one or several reports. You can allow the user to view them in the platform. Start with a general list of available reports definition and their required parameters, for example, an account or a report period. Use GetReportsMetaData for this:
public override IList<ReportMetaData> GetReportsMetaData() { List<ReportMetaData> result = new List<ReportMetaData>(); // // Oanda provides "Transaction history report" which allow us to filter data by time range and account // ReportMetaData reportType = new ReportMetaData((int)ReportTypeEnum.TransactionHistory, "Transaction history report"); reportType.Parameters.Add(new SettingItemAccountLookup(Vendor.REPORT_TYPE_PARAMETER_ACCOUNT)); reportType.Parameters.Add(new SettingItemDateTimePicker(Vendor.REPORT_TYPE_PARAMETER_DATETIME_FROM)); reportType.Parameters.Add(new SettingItemDateTimePicker(Vendor.REPORT_TYPE_PARAMETER_DATETIME_TO)); result.Add(reportType); return result; }
Next, define the mechanism of particular report’s generation. As parameters, you get a report name and parameters which are set by the user. This results into Report object, represented in the form of table, a set of columns, rows and cells with values:
public override Report GenerateReport(ReportMetaData reportType) { // // Check passed parameters // string account = reportType.Parameters.Single(p => p.Name == Vendor.REPORT_TYPE_PARAMETER_ACCOUNT).Value.ToString(); account = accountsSummaryOanda.Single(a => a.Name == account).Id; DateTime from = (DateTime)reportType.Parameters.Single(p => p.Name == Vendor.REPORT_TYPE_PARAMETER_DATETIME_FROM).Value; DateTime to = (DateTime)reportType.Parameters.Single(p => p.Name == Vendor.REPORT_TYPE_PARAMETER_DATETIME_TO).Value; // // Generate "Transaction history report" // if ((ReportTypeEnum)reportType.Id == ReportTypeEnum.TransactionHistory) { // // Get data from Oanda an API // List<Transaction> transactions = new List<Transaction>(); var transactionsPage = Rest.GetTransactionsPage(account, from, to); foreach (var page in transactionsPage.Pages) transactions.AddRange(Rest.GetTransactions(page)); // // Specify report columns // var report = new Report(); report.AddColumn("Time", AdditionalInfoItemComparingType.DateTime); report.AddColumn("Type", AdditionalInfoItemComparingType.String); report.AddColumn("Order id", AdditionalInfoItemComparingType.String); report.AddColumn("Balance", AdditionalInfoItemComparingType.Double); report.AddColumn("Financing", AdditionalInfoItemComparingType.Double); report.AddColumn("Account id", AdditionalInfoItemComparingType.String); // // Fill reports cells with received data // foreach (var transaction in transactions) { var reportRow = new ReportRow(); reportRow.AddCell(transaction.Time); reportRow.AddCell(transaction.TransactionType.ToString()); reportRow.AddCell(transaction.OrderID); reportRow.AddCell(transaction.AccountBalance); reportRow.AddCell(transaction.Financing); reportRow.AddCell(transaction.AccountID); report.Rows.Add(reportRow); } return report; } else return null; }
Conclusion
In this article we have covered the clue methods for PTMC platform integration. We haven’t described some additional methods, because they are simple and work in the similar way to the described ones do. We haven’t touched some peculiarities, just because Oanda is a Forex broker. These are Trade quotes, instrument search by pattern, work with options. We are going to realize new exchange integrations with Metastock and QuoteMedia, and to view the peculiarities using one of them as an example.
Make you own integrations and be brave to share your results in our codebase. Our technical support is here to help you, contact our LiveChat or email us to info@protrader.orgHave not tried PTMC yet? There is no better way to boost knowledge than to use it! Start trading with PTMC now!