串接經紀商。如何使用 PTMC 的整合 API 來串接經紀商。
歡迎大家自行進行 API 整合,並願意的話,在這邊分享你的程式碼。如果需要,歡迎隨時聯繫我們,或寫信到 service@kcdatanet.com
歡迎各位使用 PTMC!
我們很高興向大家介紹 PTMC 的 Integration API。透過 Integration API,用戶可以將 PTMC 平台與任何券商 API 或是報價源進行整合。 要使用此 API,用戶必需清楚瞭解交易中的報價/下單概念,並且需要一定的 C#語言的基本實作能力。目前發佈的 Integration API 是 V1 版本,在未來,我們將參考用戶的需求及回饋,不斷的加以擴充及改善。所以如果各位有什麼想法,歡迎在這邊發表或是論壇中反應。
從技術角度來說,報價及下單整合必需實作 API 裡面的方法 (Method),這樣才能將 PTMC 平台與經紀商的報價/下單及報價源彼此串連。這樣才能更好的將用戶在券商中的帳戶資料、委託記錄、商品報價等完美的整合到 PTMC 平台之中。用戶可以在您自行開發的整合 API 中結合任何外部 API,如 REST,FIX,任何特定的專屬協議。基本上沒有限制,一切取決於您自己的偏好及經紀商 API 與報價源所提供的規範或限制。
在這邊,我們將透過 Oanda 的新 v20 REST API 的整合範例來展示如何使用 Integration API 來做整合。關於 Oanda API 的訊息可以在其官方網站找到。本文的主要重點將放在 Integration API 上,但是如果有興趣了解 PTMC 和 Oanda API 間的整合細節,請參閱 GitHub 中的原始碼。
準備工作
首先,執行下列步驟:
- 在 Visual Studio 中建立一個動態程式庫 (DLL) 專案
- 將位於 PTMC 安裝路徑中的 PlatformAPI.dll 加入專案的「參考 (Reference)」之中
- 建立一個從 Vendor 類別中繼承的經紀商類別,並準備覆寫類別中的 Method
要測試整合,必需將你建立的程式庫,及相依的檔案 (DLL 或其它必需資料) 複製到 PTMC 的文件夾中。之後,它將會在 “連接設定” 視窗中的經紀商列表中出現,並可供使用。
現在一切準備就緒。讓我們開始吧!
連結
任何 API 整合始於如何初始化經紀商 API 並連接其系統。Intergration API 內的 Connect 方法的目的就在於此。用戶必需實做此方法才能將 PTMC 與經紀商或報價商的 API 串連。有些時候可能還需要其它設定,像是帳戶類型為模擬或是真實交易帳戶。這些設定位於 PTMC 登入視窗的“連接設定”視窗中:
GetConnectionParameters 方法定義了 API 登入時所需要的必需設定參數。在 Oanda 的範例中,我們提供了選擇連接類型選項:
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; }
此方法會將結果儲存到 SettingItem 陣列物件,每個物件都定義了設定中的一個類型,名稱和數值。
在這邊,我們來實做一個連接方法。在前面的 GetConnectionParameters 方法中定義的值會做為傳入參數,因此在這邊會有一組相同的設定值:
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; }
帳戶及規則
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 平台支援相當多功能,以確保能夠好好的處理,如期貨,期權,股票,外匯等不同商品交易的需求。如果整合過程中,經紀商的 API 不支援像是 Level 2 報價或是 Tick 資料的話,則可以將這些用不到的功能隱藏起來。要達成此目的,您可以此使用 GetRulesTable 方法:
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; }
交易商品
這些交易物件,像是查詢歷史報價,進行交易,讀取報價等都是交易者一定會用到的。您可以在 GetInstruments 方法中指定連接時取得可交易的所有商品,以它們的相關資訊。此機制類似於讀取帳戶資訊。 實際上,絕大部分 API 的工作都是遵循這個原則:終端程式向經紀商系統發送各種資訊請求,而經紀商系統必需回應及傳回所請求的資料。
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; }
訂閱報價
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; }
訂單、倉位、交易
訂單、倉位、交易這三個資料是一個交易平台必備的標準資訊。這就是為什麼必需實做賬戶和商品實體,然後加入訂單、倉位、交易維護。 透過 GetPendingOrders,GetPositions,GetTradesHistory 允許交易者查看相應面板中的對應訊息。平台在成功連接後會立即呼叫這些方法和資料:
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; }
此外,還有一組方法用來回報給 PTMC 關於用戶的投資組合變化。這些變動可以是開倉,取消或修改訂單等。這些方法包括 OnOrderUpdated,OnOrderCancelled,OnPositionUpdated,OnPositionClosed。
載入歷史資料
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; }
歷史資料範圍區間,類型和週期取決於特定的整合方式。PTMC 平台支持所有基本類型,但如果必要的話,也透過使用 GetHistoryMetadata 方法來刪除無法存取訪問的資料型態:
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, } }; }
交易操作
PTMC 的 Integration API 提供了一個交易介面,可將平台與任何數據源及經紀商 API 進行整合。在實做交易介面功能後,則那些提供交易功能的面板,如訂單輸入面板,圖表中的視覺交易,訂單取消或修改將即可正常運作。交易介面中,其中一個主要方法就是 PlaceOrder 方法。只要正確處理 OrderParameters 物件中所傳入的參數,就可向經紀商發送訂單請求:
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; }
而開啟/修改訂單和關閉倉位則可使用:ModifyOrder,CancelOrder,ClosePosition。
報告
Intergration API 可提供了多種報告。允許用戶在 PTMC 平台中隨時查看。這一系列報告定義其所需參數,像是帳戶或是報告週期等。要取得報告需使用 GetReportsMetaData 方法:
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; }
接下來,定義特定報表的產生機制。做為參數,您將取得用戶設定的報告名稱和參數。這會讓 Report 物件以表格的方式傳回每個欄位內的數值:
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; }
結論
在這篇文章裡,我們介紹了關於如何將 PTMC 平台與經紀商 API 整合的一些方式。有一些額外的方法,因為它們很簡單,而且工作方式類似,因此沒有多加說明。同時,因為 Oanda 是外匯經紀商,所以我們也沒有觸及其它商品的特殊需求。像是成交報價,或是依照類型來搜尋商品,及查詢選擇權等部分。接下來我們會實做 Metastock 和 QuoteMedia 的 API 整合,並以其中一個為範例來涵蓋 Integration API 的其它特性。
歡迎大家自行進行 API 整合,並願意的話,在這邊分享你的程式碼。如果需要,歡迎隨時聯繫我們,或寫信到 service@kcdatanet.com