diff --git a/.gitignore b/.gitignore index 97d18aded4..81bc15f71e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ # Custom appsettings.Production.json -Samples/ +Releases/ # User-specific files *.suo diff --git a/Derivative/Pages/Profile.razor.cs b/Derivative/Pages/Profile.razor.cs index 83d8767278..4a53aa3bdd 100644 --- a/Derivative/Pages/Profile.razor.cs +++ b/Derivative/Pages/Profile.razor.cs @@ -142,7 +142,7 @@ protected double GetEstimate(double price, OptionInputModel inputModel) { var direction = inputModel.Position is OrderSideEnum.Buy ? 1.0 : -1.0; - if (inputModel.Side is Terminal.Core.Enums.OptionSideEnum.Share) + if (inputModel.Side is OptionSideEnum.Share) { return (price - inputModel.Price) * inputModel.Amount * direction; } diff --git a/Gateway/Alpaca/Libs/Adapter.cs b/Gateway/Alpaca/Libs/Adapter.cs index 4fa284cb21..6af77e63d3 100644 --- a/Gateway/Alpaca/Libs/Adapter.cs +++ b/Gateway/Alpaca/Libs/Adapter.cs @@ -472,7 +472,7 @@ protected virtual async Task GetConnection(ClientWebSocket ws, await ws.ReceiveAsync(data, cancellation.Token).ContinueWith(async o => { var response = await o; - var content = $"[{Encoding.ASCII.GetString(data).Trim(new[] { '\0', '[', ']' })}]"; + var content = $"[{Encoding.ASCII.GetString(data).Trim(['\0', '[', ']'])}]"; var message = JsonNode .Parse(content) ?.AsArray() @@ -480,8 +480,8 @@ await ws.ReceiveAsync(data, cancellation.Token).ContinueWith(async o => switch ($"{message?["T"]}".ToUpper()) { - case "Q": ProcessPoint(content); break; - case "T": ProcessTrade(content); break; + case "Q": OnPoint(content); break; + case "T": OnTrade(content); break; } }); } @@ -494,7 +494,7 @@ await ws.ReceiveAsync(data, cancellation.Token).ContinueWith(async o => /// Process quote from the stream /// /// - protected virtual void ProcessPoint(string content) + protected virtual void OnPoint(string content) { var streamPoint = JsonSerializer .Deserialize(content, _sender.Options) @@ -522,7 +522,7 @@ protected virtual void ProcessPoint(string content) /// Process quote from the stream /// /// - protected virtual void ProcessTrade(string content) + protected virtual void OnTrade(string content) { var orderMessage = JsonSerializer .Deserialize(content, _sender.Options) diff --git a/Gateway/InteractiveBrokers/Libs/Classes/EClient.cs b/Gateway/InteractiveBrokers/Libs/Classes/EClient.cs index 9cf9351ca6..069cca3076 100644 --- a/Gateway/InteractiveBrokers/Libs/Classes/EClient.cs +++ b/Gateway/InteractiveBrokers/Libs/Classes/EClient.cs @@ -1,19 +1,19 @@ -/* Copyright (C) 2024 Interactive Brokers LLC. All rights reserved. This code is subject to the terms - * and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. */ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; - -namespace IBApi +/* Copyright (C) 2024 Interactive Brokers LLC. All rights reserved. This code is subject to the terms + * and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. */ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace IBApi { - /** - * @class EClient - * @brief TWS/Gateway client class - * This client class contains all the available methods to communicate with IB. Up to thirty-two clients can be connected to a single instance of the TWS/Gateway simultaneously. From herein, the TWS/Gateway will be referred to as the Host. + /** + * @class EClient + * @brief TWS/Gateway client class + * This client class contains all the available methods to communicate with IB. Up to thirty-two clients can be connected to a single instance of the TWS/Gateway simultaneously. From herein, the TWS/Gateway will be referred to as the Host. */ public abstract class EClient { @@ -33,10 +33,10 @@ public abstract class EClient private string connectOptions = ""; protected bool allowRedirect; - /** - * @brief Constructor - * @param wrapper EWrapper's implementing class instance. Every message being delivered by IB to the API client will be forwarded to the EWrapper's implementing class. - * @sa EWrapper + /** + * @brief Constructor + * @param wrapper EWrapper's implementing class instance. Every message being delivered by IB to the API client will be forwarded to the EWrapper's implementing class. + * @sa EWrapper */ public EClient(EWrapper wrapper) { @@ -48,8 +48,8 @@ public EClient(EWrapper wrapper) AsyncEConnect = false; } - /** - * @brief Ignore. Used for IB's internal purposes. + /** + * @brief Ignore. Used for IB's internal purposes. */ public void SetConnectOptions(string connectOptions) { @@ -67,8 +67,8 @@ public void SetOptionalCapabilities(string optionalCapabilities) this.optionalCapabilities = optionalCapabilities; } - /** - * @brief Allows to switch between different current (V100+) and previous connection mechanisms. + /** + * @brief Allows to switch between different current (V100+) and previous connection mechanisms. */ public void DisableUseV100Plus() { @@ -76,8 +76,8 @@ public void DisableUseV100Plus() connectOptions = ""; } - /** - * @brief Reference to the EWrapper implementing object. + /** + * @brief Reference to the EWrapper implementing object. */ public EWrapper Wrapper => wrapper; @@ -87,15 +87,15 @@ public bool AllowRedirect set => allowRedirect = value; } - /** - * @brief returns the Host's version. Some of the API functionality might not be available in older Hosts and therefore it is essential to keep the TWS/Gateway as up to date as possible. + /** + * @brief returns the Host's version. Some of the API functionality might not be available in older Hosts and therefore it is essential to keep the TWS/Gateway as up to date as possible. */ public int ServerVersion => serverVersion; - /** - * @brief Indicates whether the API-TWS connection has been closed. - * Note: This function is not automatically invoked and must be by the API client. - * @returns true if connection has been established, false if it has not. + /** + * @brief Indicates whether the API-TWS connection has been closed. + * Note: This function is not automatically invoked and must be by the API client. + * @returns true if connection has been established, false if it has not. */ public bool IsConnected() => isConnected; @@ -153,8 +153,8 @@ protected void validateInvalidSymbols(string host) throw new EClientException(EClientErrors.INVALID_SYMBOL, optionalCapabilities); } } - /** - * @brief Initiates the message exchange between the client application and the TWS/IB Gateway + /** + * @brief Initiates the message exchange between the client application and the TWS/IB Gateway */ public void startApi() { @@ -184,14 +184,14 @@ public void startApi() public string optionalCapabilities { get; set; } - /** - * @brief Terminates the connection and notifies the EWrapper implementing class. - * @sa EWrapper::connectionClosed, eDisconnect + /** + * @brief Terminates the connection and notifies the EWrapper implementing class. + * @sa EWrapper::connectionClosed, eDisconnect */ public void Close() => eDisconnect(); - /** - * @brief Closes the socket connection and terminates its thread. + /** + * @brief Closes the socket connection and terminates its thread. */ public virtual void eDisconnect(bool resetState = true) { @@ -211,10 +211,10 @@ public virtual void eDisconnect(bool resetState = true) if (resetState) wrapper.connectionClosed(); } - /** - * @brief Requests completed orders.\n - * @param apiOnly - request only API orders.\n - * @sa EWrapper::completedOrder, EWrapper::completedOrdersEnd + /** + * @brief Requests completed orders.\n + * @param apiOnly - request only API orders.\n + * @sa EWrapper::completedOrder, EWrapper::completedOrdersEnd */ public void reqCompletedOrders(bool apiOnly) { @@ -230,9 +230,9 @@ public void reqCompletedOrders(bool apiOnly) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQCOMPLETEDORDERS); } - /** - * @brief Cancels tick-by-tick data.\n - * @param reqId - unique identifier of the request.\n + /** + * @brief Cancels tick-by-tick data.\n + * @param reqId - unique identifier of the request.\n */ public void cancelTickByTickData(int requestId) { @@ -248,14 +248,14 @@ public void cancelTickByTickData(int requestId) CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_CANCELTICKBYTICKDATA); } - /** - * @brief Requests tick-by-tick data.\n - * @param reqId - unique identifier of the request.\n - * @param contract - the contract for which tick-by-tick data is requested.\n - * @param tickType - tick-by-tick data type: "Last", "AllLast", "BidAsk" or "MidPoint".\n - * @param numberOfTicks - number of ticks.\n - * @param ignoreSize - ignore size flag.\n - * @sa EWrapper::tickByTickAllLast, EWrapper::tickByTickBidAsk, EWrapper::tickByTickMidPoint, Contract + /** + * @brief Requests tick-by-tick data.\n + * @param reqId - unique identifier of the request.\n + * @param contract - the contract for which tick-by-tick data is requested.\n + * @param tickType - tick-by-tick data type: "Last", "AllLast", "BidAsk" or "MidPoint".\n + * @param numberOfTicks - number of ticks.\n + * @param ignoreSize - ignore size flag.\n + * @sa EWrapper::tickByTickAllLast, EWrapper::tickByTickBidAsk, EWrapper::tickByTickMidPoint, Contract */ public void reqTickByTickData(int requestId, Contract contract, string tickType, int numberOfTicks, bool ignoreSize) { @@ -299,10 +299,10 @@ public void reqTickByTickData(int requestId, Contract contract, string tickType, CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQTICKBYTICKDATA); } - /** - * @brief Cancels a historical data request. - * @param reqId the request's identifier. - * @sa reqHistoricalData + /** + * @brief Cancels a historical data request. + * @param reqId the request's identifier. + * @sa reqHistoricalData */ public void cancelHistoricalData(int reqId) { @@ -313,14 +313,14 @@ public void cancelHistoricalData(int reqId) SendCancelRequest(OutgoingMessages.CancelHistoricalData, VERSION, reqId, EClientErrors.FAIL_SEND_CANHISTDATA); } - /** - * @brief Calculate the volatility for an option.\n - * Request the calculation of the implied volatility based on hypothetical option and its underlying prices.\n The calculation will be return in EWrapper's tickOptionComputation callback.\n - * @param reqId unique identifier of the request.\n - * @param contract the option's contract for which the volatility wants to be calculated.\n - * @param optionPrice hypothetical option price.\n - * @param underPrice hypothetical option's underlying price.\n - * @sa EWrapper::tickOptionComputation, cancelCalculateImpliedVolatility, Contract + /** + * @brief Calculate the volatility for an option.\n + * Request the calculation of the implied volatility based on hypothetical option and its underlying prices.\n The calculation will be return in EWrapper's tickOptionComputation callback.\n + * @param reqId unique identifier of the request.\n + * @param contract the option's contract for which the volatility wants to be calculated.\n + * @param optionPrice hypothetical option price.\n + * @param underPrice hypothetical option's underlying price.\n + * @sa EWrapper::tickOptionComputation, cancelCalculateImpliedVolatility, Contract */ public void calculateImpliedVolatility(int reqId, Contract contract, double optionPrice, double underPrice, //reserved for future use, must be blank @@ -364,14 +364,14 @@ public void calculateImpliedVolatility(int reqId, Contract contract, double opti CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQCALCIMPLIEDVOLAT); } - /** - * @brief Calculates an option's price based on the provided volatility and its underlying's price. \n - * The calculation will be return in EWrapper's tickOptionComputation callback.\n - * @param reqId request's unique identifier.\n - * @param contract the option's contract for which the price wants to be calculated.\n - * @param volatility hypothetical volatility.\n - * @param underPrice hypothetical underlying's price.\n - * @sa EWrapper::tickOptionComputation, cancelCalculateOptionPrice, Contract + /** + * @brief Calculates an option's price based on the provided volatility and its underlying's price. \n + * The calculation will be return in EWrapper's tickOptionComputation callback.\n + * @param reqId request's unique identifier.\n + * @param contract the option's contract for which the price wants to be calculated.\n + * @param volatility hypothetical volatility.\n + * @param underPrice hypothetical underlying's price.\n + * @sa EWrapper::tickOptionComputation, cancelCalculateOptionPrice, Contract */ public void calculateOptionPrice(int reqId, Contract contract, double volatility, double underPrice, //reserved for future use, must be blank @@ -415,11 +415,11 @@ public void calculateOptionPrice(int reqId, Contract contract, double volatility CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQCALCOPTIONPRICE); } - /** - * @brief Cancels the account's summary request. - * After requesting an account's summary, invoke this function to cancel it. - * @param reqId the identifier of the previously performed account request - * @sa reqAccountSummary + /** + * @brief Cancels the account's summary request. + * After requesting an account's summary, invoke this function to cancel it. + * @param reqId the identifier of the previously performed account request + * @sa reqAccountSummary */ public void cancelAccountSummary(int reqId) { @@ -428,10 +428,10 @@ public void cancelAccountSummary(int reqId) SendCancelRequest(OutgoingMessages.CancelAccountSummary, 1, reqId, EClientErrors.FAIL_SEND_CANACCOUNTDATA); } - /** - * @brief Cancels an option's implied volatility calculation request - * @param reqId the identifier of the implied volatility's calculation request. - * @sa calculateImpliedVolatility + /** + * @brief Cancels an option's implied volatility calculation request + * @param reqId the identifier of the implied volatility's calculation request. + * @sa calculateImpliedVolatility */ public void cancelCalculateImpliedVolatility(int reqId) { @@ -440,10 +440,10 @@ public void cancelCalculateImpliedVolatility(int reqId) SendCancelRequest(OutgoingMessages.CancelImpliedVolatility, 1, reqId, EClientErrors.FAIL_SEND_CANCALCIMPLIEDVOLAT); } - /** - * @brief Cancels an option's price calculation request - * @param reqId the identifier of the option's price's calculation request. - * @sa calculateOptionPrice + /** + * @brief Cancels an option's price calculation request + * @param reqId the identifier of the option's price's calculation request. + * @sa calculateOptionPrice */ public void cancelCalculateOptionPrice(int reqId) { @@ -452,10 +452,10 @@ public void cancelCalculateOptionPrice(int reqId) SendCancelRequest(OutgoingMessages.CancelOptionPrice, 1, reqId, EClientErrors.FAIL_SEND_CANCALCOPTIONPRICE); } - /** - * @brief Cancels Fundamental data request - * @param reqId the request's identifier. - * @sa reqFundamentalData + /** + * @brief Cancels Fundamental data request + * @param reqId the request's identifier. + * @sa reqFundamentalData */ public void cancelFundamentalData(int reqId) { @@ -464,10 +464,10 @@ public void cancelFundamentalData(int reqId) SendCancelRequest(OutgoingMessages.CancelFundamentalData, 1, reqId, EClientErrors.FAIL_SEND_CANFUNDDATA); } - /** - * @brief Cancels a RT Market Data request - * @param tickerId request's identifier - * @sa reqMktData + /** + * @brief Cancels a RT Market Data request + * @param tickerId request's identifier + * @sa reqMktData */ public void cancelMktData(int tickerId) { @@ -476,10 +476,10 @@ public void cancelMktData(int tickerId) SendCancelRequest(OutgoingMessages.CancelMarketData, 1, tickerId, EClientErrors.FAIL_SEND_CANMKT); } - /** - * @brief Cancel's market depth's request. - * @param tickerId request's identifier. - * @sa reqMarketDepth + /** + * @brief Cancel's market depth's request. + * @param tickerId request's identifier. + * @sa reqMarketDepth */ public void cancelMktDepth(int tickerId, bool isSmartDepth) { @@ -499,9 +499,9 @@ public void cancelMktDepth(int tickerId, bool isSmartDepth) CloseAndSend(tickerId, paramsList, lengthPos, EClientErrors.FAIL_SEND_CANMKTDEPTH); } - /** - * @brief Cancels IB's news bulletin subscription - * @sa reqNewsBulletins + /** + * @brief Cancels IB's news bulletin subscription + * @sa reqNewsBulletins */ public void cancelNewsBulletin() { @@ -509,11 +509,11 @@ public void cancelNewsBulletin() SendCancelRequest(OutgoingMessages.CancelNewsBulletin, 1, EClientErrors.FAIL_SEND_CORDER); } - /** - * @brief Cancels an active order placed by from the same API client ID.\n - * Note: API clients cannot cancel individual orders placed by other clients. Only reqGlobalCancel is available.\n - * @param orderId the order's client id - * @sa placeOrder, reqGlobalCancel + /** + * @brief Cancels an active order placed by from the same API client ID.\n + * Note: API clients cannot cancel individual orders placed by other clients. Only reqGlobalCancel is available.\n + * @param orderId the order's client id + * @sa placeOrder, reqGlobalCancel */ public void cancelOrder(int orderId, string manualOrderCancelTime) { @@ -540,9 +540,9 @@ public void cancelOrder(int orderId, string manualOrderCancelTime) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_CANCELPNL); } - /** - * @brief Cancels a previous position subscription request made with reqPositions - * @sa reqPositions + /** + * @brief Cancels a previous position subscription request made with reqPositions + * @sa reqPositions */ public void cancelPositions() { @@ -552,10 +552,10 @@ public void cancelPositions() SendCancelRequest(OutgoingMessages.CancelPositions, 1, EClientErrors.FAIL_SEND_CANPOSITIONS); } - /** - * @brief Cancels Real Time Bars' subscription - * @param tickerId the request's identifier. - * @sa reqRealTimeBars + /** + * @brief Cancels Real Time Bars' subscription + * @param tickerId the request's identifier. + * @sa reqRealTimeBars */ public void cancelRealTimeBars(int tickerId) { @@ -564,10 +564,10 @@ public void cancelRealTimeBars(int tickerId) SendCancelRequest(OutgoingMessages.CancelRealTimeBars, 1, tickerId, EClientErrors.FAIL_SEND_CANRTBARS); } - /** - * @brief Cancels Scanner Subscription - * @param tickerId the subscription's unique identifier. - * @sa reqScannerSubscription, ScannerSubscription, reqScannerParameters + /** + * @brief Cancels Scanner Subscription + * @param tickerId the subscription's unique identifier. + * @sa reqScannerSubscription, ScannerSubscription, reqScannerParameters */ public void cancelScannerSubscription(int tickerId) { @@ -576,18 +576,18 @@ public void cancelScannerSubscription(int tickerId) SendCancelRequest(OutgoingMessages.CancelScannerSubscription, 1, tickerId, EClientErrors.FAIL_SEND_CANSCANNER); } - /** - * @brief Exercises an options contract\n - * Note: this function is affected by a TWS setting which specifies if an exercise request must be finalized - * @param tickerId exercise request's identifier - * @param contract the option Contract to be exercised. - * @param exerciseAction set to 1 to exercise the option, set to 2 to let the option lapse. - * @param exerciseQuantity number of contracts to be exercised - * @param account destination account - * @param ovrd Specifies whether your setting will override the system's natural action. For example, if your action is "exercise" and the option is not in-the-money, by natural action the option would not exercise. If you have override set to "yes" the natural action would be overridden and the out-of-the money option would be exercised. Set to 1 to override, set to 0 not to. - * @param manualOrderTime Manual Order Time - * @param customerAccount Customer Account - * @param professionalCustomer Professional Customer + /** + * @brief Exercises an options contract\n + * Note: this function is affected by a TWS setting which specifies if an exercise request must be finalized + * @param tickerId exercise request's identifier + * @param contract the option Contract to be exercised. + * @param exerciseAction set to 1 to exercise the option, set to 2 to let the option lapse. + * @param exerciseQuantity number of contracts to be exercised + * @param account destination account + * @param ovrd Specifies whether your setting will override the system's natural action. For example, if your action is "exercise" and the option is not in-the-money, by natural action the option would not exercise. If you have override set to "yes" the natural action would be overridden and the out-of-the money option would be exercised. Set to 1 to override, set to 0 not to. + * @param manualOrderTime Manual Order Time + * @param customerAccount Customer Account + * @param professionalCustomer Professional Customer */ public void exerciseOptions(int tickerId, Contract contract, int exerciseAction, int exerciseQuantity, string account, int ovrd, string manualOrderTime, string customerAccount, bool professionalCustomer) { @@ -656,12 +656,12 @@ public void exerciseOptions(int tickerId, Contract contract, int exerciseAction, CloseAndSend(tickerId, paramsList, lengthPos, EClientErrors.FAIL_GENERIC); } - /** - * @brief Places or modifies an order - * @param id the order's unique identifier. Use a sequential id starting with the id received at the nextValidId method. If a new order is placed with an order ID less than or equal to the order ID of a previous order an error will occur. - * @param contract the order's contract - * @param order the order - * @sa EWrapper::nextValidId, reqAllOpenOrders, reqAutoOpenOrders, reqOpenOrders, cancelOrder, reqGlobalCancel, EWrapper::openOrder, EWrapper::orderStatus, Order, Contract + /** + * @brief Places or modifies an order + * @param id the order's unique identifier. Use a sequential id starting with the id received at the nextValidId method. If a new order is placed with an order ID less than or equal to the order ID of a previous order an error will occur. + * @param contract the order's contract + * @param order the order + * @sa EWrapper::nextValidId, reqAllOpenOrders, reqAutoOpenOrders, reqOpenOrders, cancelOrder, reqGlobalCancel, EWrapper::openOrder, EWrapper::orderStatus, Order, Contract */ public void placeOrder(int id, Contract contract, Order order) { @@ -1089,15 +1089,15 @@ public void placeOrder(int id, Contract contract, Order order) CloseAndSend(id, paramsList, lengthPos, EClientErrors.FAIL_SEND_ORDER); } - /** - * @brief Replaces Financial Advisor's settings - * A Financial Advisor can define three different configurations: - * 1. Groups: offer traders a way to create a group of accounts and apply a single allocation method to all accounts in the group. - * 3. Account Aliases: let you easily identify the accounts by meaningful names rather than account numbers. - * More information at https://www.interactivebrokers.com/en/?f=%2Fen%2Fsoftware%2Fpdfhighlights%2FPDF-AdvisorAllocations.php%3Fib_entity%3Dllc - * @param faDataType the configuration to change. Set to 1 or 3 as defined above. - * @param xml the xml-formatted configuration string - * @sa requestFA + /** + * @brief Replaces Financial Advisor's settings + * A Financial Advisor can define three different configurations: + * 1. Groups: offer traders a way to create a group of accounts and apply a single allocation method to all accounts in the group. + * 3. Account Aliases: let you easily identify the accounts by meaningful names rather than account numbers. + * More information at https://www.interactivebrokers.com/en/?f=%2Fen%2Fsoftware%2Fpdfhighlights%2FPDF-AdvisorAllocations.php%3Fib_entity%3Dllc + * @param faDataType the configuration to change. Set to 1 or 3 as defined above. + * @param xml the xml-formatted configuration string + * @sa requestFA */ public void replaceFA(int reqId, int faDataType, string xml) { @@ -1131,14 +1131,14 @@ public void replaceFA(int reqId, int faDataType, string xml) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_FA_REPLACE); } - /** - * @brief Requests the FA configuration - * A Financial Advisor can define three different configurations: - * 1. Groups: offer traders a way to create a group of accounts and apply a single allocation method to all accounts in the group. - * 3. Account Aliases: let you easily identify the accounts by meaningful names rather than account numbers. - * More information at https://www.interactivebrokers.com/en/?f=%2Fen%2Fsoftware%2Fpdfhighlights%2FPDF-AdvisorAllocations.php%3Fib_entity%3Dllc - * @param faDataType the configuration to change. Set to 1 or 3 as defined above. - * @sa replaceFA + /** + * @brief Requests the FA configuration + * A Financial Advisor can define three different configurations: + * 1. Groups: offer traders a way to create a group of accounts and apply a single allocation method to all accounts in the group. + * 3. Account Aliases: let you easily identify the accounts by meaningful names rather than account numbers. + * More information at https://www.interactivebrokers.com/en/?f=%2Fen%2Fsoftware%2Fpdfhighlights%2FPDF-AdvisorAllocations.php%3Fib_entity%3Dllc + * @param faDataType the configuration to change. Set to 1 or 3 as defined above. + * @sa replaceFA */ public void requestFA(int faDataType) { @@ -1159,46 +1159,46 @@ public void requestFA(int faDataType) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_FA_REQUEST); } - /** - * @brief Requests a specific account's summary.\n - * This method will subscribe to the account summary as presented in the TWS' Account Summary tab. The data is returned at EWrapper::accountSummary\n - * https://www.interactivebrokers.com/en/software/tws/accountwindowtop.htm - * @param reqId the unique request identifier.\n - * @param group set to "All" to return account summary data for all accounts, or set to a specific Advisor Account Group name that has already been created in TWS Global Configuration.\n - * @param tags a comma separated list with the desired tags: - * - AccountType — Identifies the IB account structure - * - NetLiquidation — The basis for determining the price of the assets in your account. Total cash value + stock value + options value + bond value - * - TotalCashValue — Total cash balance recognized at the time of trade + futures PNL - * - SettledCash — Cash recognized at the time of settlement - purchases at the time of trade - commissions - taxes - fees - * - AccruedCash — Total accrued cash value of stock, commodities and securities - * - BuyingPower — Buying power serves as a measurement of the dollar value of securities that one may purchase in a securities account without depositing additional funds - * - EquityWithLoanValue — Forms the basis for determining whether a client has the necessary assets to either initiate or maintain security positions. Cash + stocks + bonds + mutual funds - * - PreviousEquityWithLoanValue — Marginable Equity with Loan value as of 16:00 ET the previous day - * - GrossPositionValue — The sum of the absolute value of all stock and equity option positions - * - RegTEquity — Regulation T equity for universal account - * - RegTMargin — Regulation T margin for universal account - * - SMA — Special Memorandum Account: Line of credit created when the market value of securities in a Regulation T account increase in value - * - InitMarginReq — Initial Margin requirement of whole portfolio - * - MaintMarginReq — Maintenance Margin requirement of whole portfolio - * - AvailableFunds — This value tells what you have available for trading - * - ExcessLiquidity — This value shows your margin cushion, before liquidation - * - Cushion — Excess liquidity as a percentage of net liquidation value - * - FullInitMarginReq — Initial Margin of whole portfolio with no discounts or intraday credits - * - FullMaintMarginReq — Maintenance Margin of whole portfolio with no discounts or intraday credits - * - FullAvailableFunds — Available funds of whole portfolio with no discounts or intraday credits - * - FullExcessLiquidity — Excess liquidity of whole portfolio with no discounts or intraday credits - * - LookAheadNextChange — Time when look-ahead values take effect - * - LookAheadInitMarginReq — Initial Margin requirement of whole portfolio as of next period's margin change - * - LookAheadMaintMarginReq — Maintenance Margin requirement of whole portfolio as of next period's margin change - * - LookAheadAvailableFunds — This value reflects your available funds at the next margin change - * - LookAheadExcessLiquidity — This value reflects your excess liquidity at the next margin change - * - HighestSeverity — A measure of how close the account is to liquidation - * - DayTradesRemaining — The Number of Open/Close trades a user could put on before Pattern Day Trading is detected. A value of "-1" means that the user can put on unlimited day trades. - * - Leverage — GrossPositionValue / NetLiquidation - * - $LEDGER — Single flag to relay all cash balance tags*, only in base currency. - * - $LEDGER:CURRENCY — Single flag to relay all cash balance tags*, only in the specified currency. - * - $LEDGER:ALL — Single flag to relay all cash balance tags* in all currencies. - * @sa cancelAccountSummary, EWrapper::accountSummary, EWrapper::accountSummaryEnd + /** + * @brief Requests a specific account's summary.\n + * This method will subscribe to the account summary as presented in the TWS' Account Summary tab. The data is returned at EWrapper::accountSummary\n + * https://www.interactivebrokers.com/en/software/tws/accountwindowtop.htm + * @param reqId the unique request identifier.\n + * @param group set to "All" to return account summary data for all accounts, or set to a specific Advisor Account Group name that has already been created in TWS Global Configuration.\n + * @param tags a comma separated list with the desired tags: + * - AccountType — Identifies the IB account structure + * - NetLiquidation — The basis for determining the price of the assets in your account. Total cash value + stock value + options value + bond value + * - TotalCashValue — Total cash balance recognized at the time of trade + futures PNL + * - SettledCash — Cash recognized at the time of settlement - purchases at the time of trade - commissions - taxes - fees + * - AccruedCash — Total accrued cash value of stock, commodities and securities + * - BuyingPower — Buying power serves as a measurement of the dollar value of securities that one may purchase in a securities account without depositing additional funds + * - EquityWithLoanValue — Forms the basis for determining whether a client has the necessary assets to either initiate or maintain security positions. Cash + stocks + bonds + mutual funds + * - PreviousEquityWithLoanValue — Marginable Equity with Loan value as of 16:00 ET the previous day + * - GrossPositionValue — The sum of the absolute value of all stock and equity option positions + * - RegTEquity — Regulation T equity for universal account + * - RegTMargin — Regulation T margin for universal account + * - SMA — Special Memorandum Account: Line of credit created when the market value of securities in a Regulation T account increase in value + * - InitMarginReq — Initial Margin requirement of whole portfolio + * - MaintMarginReq — Maintenance Margin requirement of whole portfolio + * - AvailableFunds — This value tells what you have available for trading + * - ExcessLiquidity — This value shows your margin cushion, before liquidation + * - Cushion — Excess liquidity as a percentage of net liquidation value + * - FullInitMarginReq — Initial Margin of whole portfolio with no discounts or intraday credits + * - FullMaintMarginReq — Maintenance Margin of whole portfolio with no discounts or intraday credits + * - FullAvailableFunds — Available funds of whole portfolio with no discounts or intraday credits + * - FullExcessLiquidity — Excess liquidity of whole portfolio with no discounts or intraday credits + * - LookAheadNextChange — Time when look-ahead values take effect + * - LookAheadInitMarginReq — Initial Margin requirement of whole portfolio as of next period's margin change + * - LookAheadMaintMarginReq — Maintenance Margin requirement of whole portfolio as of next period's margin change + * - LookAheadAvailableFunds — This value reflects your available funds at the next margin change + * - LookAheadExcessLiquidity — This value reflects your excess liquidity at the next margin change + * - HighestSeverity — A measure of how close the account is to liquidation + * - DayTradesRemaining — The Number of Open/Close trades a user could put on before Pattern Day Trading is detected. A value of "-1" means that the user can put on unlimited day trades. + * - Leverage — GrossPositionValue / NetLiquidation + * - $LEDGER — Single flag to relay all cash balance tags*, only in base currency. + * - $LEDGER:CURRENCY — Single flag to relay all cash balance tags*, only in the specified currency. + * - $LEDGER:ALL — Single flag to relay all cash balance tags* in all currencies. + * @sa cancelAccountSummary, EWrapper::accountSummary, EWrapper::accountSummaryEnd */ public void reqAccountSummary(int reqId, string group, string tags) { @@ -1226,13 +1226,13 @@ public void reqAccountSummary(int reqId, string group, string tags) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQACCOUNTDATA); } - /** - * @brief Subscribes to a specific account's information and portfolio. - * Through this method, a single account's subscription can be started/stopped. As a result from the subscription, the account's information, portfolio and last update time will be received at EWrapper::updateAccountValue, EWrapper::updateAccountPortfolio, EWrapper::updateAccountTime respectively. All account values and positions will be returned initially, and then there will only be updates when there is a change in a position, or to an account value every 3 minutes if it has changed. - * Only one account can be subscribed at a time. A second subscription request for another account when the previous one is still active will cause the first one to be canceled in favour of the second one. Consider user reqPositions if you want to retrieve all your accounts' portfolios directly. - * @param subscribe set to true to start the subscription and to false to stop it. - * @param acctCode the account id (i.e. U123456) for which the information is requested. - * @sa reqPositions, EWrapper::updateAccountValue, EWrapper::updatePortfolio, EWrapper::updateAccountTime + /** + * @brief Subscribes to a specific account's information and portfolio. + * Through this method, a single account's subscription can be started/stopped. As a result from the subscription, the account's information, portfolio and last update time will be received at EWrapper::updateAccountValue, EWrapper::updateAccountPortfolio, EWrapper::updateAccountTime respectively. All account values and positions will be returned initially, and then there will only be updates when there is a change in a position, or to an account value every 3 minutes if it has changed. + * Only one account can be subscribed at a time. A second subscription request for another account when the previous one is still active will cause the first one to be canceled in favour of the second one. Consider user reqPositions if you want to retrieve all your accounts' portfolios directly. + * @param subscribe set to true to start the subscription and to false to stop it. + * @param acctCode the account id (i.e. U123456) for which the information is requested. + * @sa reqPositions, EWrapper::updateAccountValue, EWrapper::updatePortfolio, EWrapper::updateAccountTime */ public void reqAccountUpdates(bool subscribe, string acctCode) { @@ -1257,10 +1257,10 @@ public void reqAccountUpdates(bool subscribe, string acctCode) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQACCOUNTDATA); } - /** - * @brief Requests all *current* open orders in associated accounts at the current moment. The existing orders will be received via the openOrder and orderStatus events. - * Open orders are returned once; this function does not initiate a subscription - * @sa reqAutoOpenOrders, reqOpenOrders, EWrapper::openOrder, EWrapper::orderStatus, EWrapper::openOrderEnd + /** + * @brief Requests all *current* open orders in associated accounts at the current moment. The existing orders will be received via the openOrder and orderStatus events. + * Open orders are returned once; this function does not initiate a subscription + * @sa reqAutoOpenOrders, reqOpenOrders, EWrapper::openOrder, EWrapper::orderStatus, EWrapper::openOrderEnd */ public void reqAllOpenOrders() { @@ -1274,10 +1274,10 @@ public void reqAllOpenOrders() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_OORDER); } - /** - * @brief Requests status updates about future orders placed from TWS. Can only be used with client ID 0. - * @param autoBind if set to true, the newly created orders will be assigned an API order ID and implicitly associated with this client. If set to false, future orders will not be. - * @sa reqAllOpenOrders, reqOpenOrders, cancelOrder, reqGlobalCancel, EWrapper::openOrder, EWrapper::orderStatus + /** + * @brief Requests status updates about future orders placed from TWS. Can only be used with client ID 0. + * @param autoBind if set to true, the newly created orders will be assigned an API order ID and implicitly associated with this client. If set to false, future orders will not be. + * @sa reqAllOpenOrders, reqOpenOrders, cancelOrder, reqGlobalCancel, EWrapper::openOrder, EWrapper::orderStatus */ public void reqAutoOpenOrders(bool autoBind) { @@ -1292,12 +1292,12 @@ public void reqAutoOpenOrders(bool autoBind) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_OORDER); } - /** - * @brief Requests contract information.\n - * This method will provide all the contracts matching the contract provided. It can also be used to retrieve complete options and futures chains. This information will be returned at EWrapper:contractDetails. Though it is now (in API version > 9.72.12) advised to use reqSecDefOptParams for that purpose. \n - * @param reqId the unique request identifier.\n - * @param contract the contract used as sample to query the available contracts. Typically, it will contain the Contract::Symbol, Contract::Currency, Contract::SecType, Contract::Exchange\n - * @sa EWrapper::contractDetails, EWrapper::contractDetailsEnd + /** + * @brief Requests contract information.\n + * This method will provide all the contracts matching the contract provided. It can also be used to retrieve complete options and futures chains. This information will be returned at EWrapper:contractDetails. Though it is now (in API version > 9.72.12) advised to use reqSecDefOptParams for that purpose. \n + * @param reqId the unique request identifier.\n + * @param contract the contract used as sample to query the available contracts. Typically, it will contain the Contract::Symbol, Contract::Currency, Contract::SecType, Contract::Exchange\n + * @sa EWrapper::contractDetails, EWrapper::contractDetailsEnd */ public void reqContractDetails(int reqId, Contract contract) { @@ -1362,9 +1362,9 @@ public void reqContractDetails(int reqId, Contract contract) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQCONTRACT); } - /** - * @brief Requests TWS's current time. - * @sa EWrapper::currentTime + /** + * @brief Requests TWS's current time. + * @sa EWrapper::currentTime */ public void reqCurrentTime() { @@ -1380,12 +1380,12 @@ public void reqCurrentTime() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQCURRTIME); } - /** - * @brief Requests current day's (since midnight) executions matching the filter. - * Only the current day's executions can be retrieved. Along with the executions, the CommissionReport will also be returned. The execution details will arrive at EWrapper:execDetails - * @param reqId the request's unique identifier. - * @param filter the filter criteria used to determine which execution reports are returned. - * @sa EWrapper::execDetails, EWrapper::commissionReport, ExecutionFilter + /** + * @brief Requests current day's (since midnight) executions matching the filter. + * Only the current day's executions can be retrieved. Along with the executions, the CommissionReport will also be returned. The execution details will arrive at EWrapper:execDetails + * @param reqId the request's unique identifier. + * @param filter the filter criteria used to determine which execution reports are returned. + * @sa EWrapper::execDetails, EWrapper::commissionReport, ExecutionFilter */ public void reqExecutions(int reqId, ExecutionFilter filter) { @@ -1425,18 +1425,18 @@ public void reqExecutions(int reqId, ExecutionFilter filter) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_EXEC); } - /** - * @brief Legacy/DEPRECATED. Requests the contract's fundamental data. - * Fundamental data is returned at EWrapper::fundamentalData - * @param reqId the request's unique identifier. - * @param contract the contract's description for which the data will be returned. - * @param reportType there are three available report types: - * - ReportSnapshot: Company overview - * - ReportsFinSummary: Financial summary - * - ReportRatios: Financial ratios - * - ReportsFinStatements: Financial statements - * - RESC: Analyst estimates - * @sa EWrapper::fundamentalData + /** + * @brief Legacy/DEPRECATED. Requests the contract's fundamental data. + * Fundamental data is returned at EWrapper::fundamentalData + * @param reqId the request's unique identifier. + * @param contract the contract's description for which the data will be returned. + * @param reportType there are three available report types: + * - ReportSnapshot: Company overview + * - ReportsFinSummary: Financial summary + * - ReportRatios: Financial ratios + * - ReportsFinStatements: Financial statements + * - RESC: Analyst estimates + * @sa EWrapper::fundamentalData */ public void reqFundamentalData(int reqId, Contract contract, string reportType, //reserved for future use, must be blank @@ -1480,10 +1480,10 @@ public void reqFundamentalData(int reqId, Contract contract, string reportType, CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQFUNDDATA); } - /** - * @brief Cancels all active orders.\n - * This method will cancel ALL open orders including those placed directly from TWS. - * @sa cancelOrder + /** + * @brief Cancels all active orders.\n + * This method will cancel ALL open orders including those placed directly from TWS. + * @sa cancelOrder */ public void reqGlobalCancel() { @@ -1500,48 +1500,48 @@ public void reqGlobalCancel() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQGLOBALCANCEL); } - /** - * @brief Requests contracts' historical data. - * When requesting historical data, a finishing time and date is required along with a duration string. For example, having: - * - endDateTime: 20130701 23:59:59 GMT - * - durationStr: 3 D - * will return three days of data counting backwards from July 1st 2013 at 23:59:59 GMT resulting in all the available bars of the last three days until the date and time specified. It is possible to specify a timezone optionally. The resulting bars will be returned in EWrapper::historicalData - * @param tickerId the request's unique identifier. - * @param contract the contract for which we want to retrieve the data. - * @param endDateTime request's ending time with format yyyyMMdd HH:mm:ss {TMZ} - * @param durationStr the amount of time for which the data needs to be retrieved: - * - " S (seconds) - * - " D (days) - * - " W (weeks) - * - " M (months) - * - " Y (years) - * @param barSizeSetting the size of the bar: - * - 1 sec - * - 5 secs - * - 15 secs - * - 30 secs - * - 1 min - * - 2 mins - * - 3 mins - * - 5 mins - * - 15 mins - * - 30 mins - * - 1 hour - * - 1 day - * @param whatToShow the kind of information being retrieved: - * - TRADES - * - MIDPOINT - * - BID - * - ASK - * - BID_ASK - * - HISTORICAL_VOLATILITY - * - OPTION_IMPLIED_VOLATILITY - * - FEE_RATE - * - SCHEDULE - * @param useRTH set to 0 to obtain the data which was also generated outside of the Regular Trading Hours, set to 1 to obtain only the RTH data - * @param formatDate set to 1 to obtain the bars' time as yyyyMMdd HH:mm:ss, set to 2 to obtain it like system time format in seconds - * @param keepUpToDate set to True to received continuous updates on most recent bar data. If True, and endDateTime cannot be specified. - * @sa EWrapper::historicalData + /** + * @brief Requests contracts' historical data. + * When requesting historical data, a finishing time and date is required along with a duration string. For example, having: + * - endDateTime: 20130701 23:59:59 GMT + * - durationStr: 3 D + * will return three days of data counting backwards from July 1st 2013 at 23:59:59 GMT resulting in all the available bars of the last three days until the date and time specified. It is possible to specify a timezone optionally. The resulting bars will be returned in EWrapper::historicalData + * @param tickerId the request's unique identifier. + * @param contract the contract for which we want to retrieve the data. + * @param endDateTime request's ending time with format yyyyMMdd HH:mm:ss {TMZ} + * @param durationStr the amount of time for which the data needs to be retrieved: + * - " S (seconds) + * - " D (days) + * - " W (weeks) + * - " M (months) + * - " Y (years) + * @param barSizeSetting the size of the bar: + * - 1 sec + * - 5 secs + * - 15 secs + * - 30 secs + * - 1 min + * - 2 mins + * - 3 mins + * - 5 mins + * - 15 mins + * - 30 mins + * - 1 hour + * - 1 day + * @param whatToShow the kind of information being retrieved: + * - TRADES + * - MIDPOINT + * - BID + * - ASK + * - BID_ASK + * - HISTORICAL_VOLATILITY + * - OPTION_IMPLIED_VOLATILITY + * - FEE_RATE + * - SCHEDULE + * @param useRTH set to 0 to obtain the data which was also generated outside of the Regular Trading Hours, set to 1 to obtain only the RTH data + * @param formatDate set to 1 to obtain the bars' time as yyyyMMdd HH:mm:ss, set to 2 to obtain it like system time format in seconds + * @param keepUpToDate set to True to received continuous updates on most recent bar data. If True, and endDateTime cannot be specified. + * @sa EWrapper::historicalData */ public void reqHistoricalData(int tickerId, Contract contract, string endDateTime, string durationStr, string barSizeSetting, string whatToShow, int useRTH, int formatDate, bool keepUpToDate, List chartOptions) @@ -1614,10 +1614,10 @@ public void reqHistoricalData(int tickerId, Contract contract, string endDateTim CloseAndSend(tickerId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQHISTDATA); } - /** - * @brief Requests the next valid order ID at the current moment. - * @param numIds deprecated- this parameter will not affect the value returned to nextValidId - * @sa EWrapper::nextValidId + /** + * @brief Requests the next valid order ID at the current moment. + * @param numIds deprecated- this parameter will not affect the value returned to nextValidId + * @sa EWrapper::nextValidId */ public void reqIds(int numIds) { @@ -1633,9 +1633,9 @@ public void reqIds(int numIds) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_GENERIC); } - /** - * @brief Requests the accounts to which the logged user has access to. - * @sa EWrapper::managedAccounts + /** + * @brief Requests the accounts to which the logged user has access to. + * @sa EWrapper::managedAccounts */ public void reqManagedAccts() { @@ -1649,30 +1649,30 @@ public void reqManagedAccts() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_GENERIC); } - /** - * @brief Requests real time market data. - * Returns market data for an instrument either in real time or 10-15 minutes delayed (depending on the market data type specified) - * @param tickerId the request's identifier - * @param contract the Contract for which the data is being requested - * @param genericTickList comma separated ids of the available generic ticks: - * - 100 Option Volume (currently for stocks) - * - 101 Option Open Interest (currently for stocks) - * - 104 Historical Volatility (currently for stocks) - * - 105 Average Option Volume (currently for stocks) - * - 106 Option Implied Volatility (currently for stocks) - * - 162 Index Future Premium - * - 165 Miscellaneous Stats - * - 221 Mark Price (used in TWS P&L computations) - * - 225 Auction values (volume, price and imbalance) - * - 233 RTVolume - contains the last trade price, last trade size, last trade time, total volume, VWAP, and single trade flag. - * - 236 Shortable - * - 256 Inventory - * - 258 Fundamental Ratios - * - 411 Realtime Historical Volatility - * - 456 IBDividends - * @param snapshot for users with corresponding real time market data subscriptions. A true value will return a one-time snapshot, while a false value will provide streaming data. - * @param regulatory snapshot for US stocks requests NBBO snapshots for users which have "US Securities Snapshot Bundle" subscription but not corresponding Network A, B, or C subscription necessary for streaming market data. One-time snapshot of current market price that will incur a fee of 1 cent to the account per snapshot. - * @sa cancelMktData, EWrapper::tickPrice, EWrapper::tickSize, EWrapper::tickString, EWrapper::tickEFP, EWrapper::tickGeneric, EWrapper::tickOptionComputation, EWrapper::tickSnapshotEnd + /** + * @brief Requests real time market data. + * Returns market data for an instrument either in real time or 10-15 minutes delayed (depending on the market data type specified) + * @param tickerId the request's identifier + * @param contract the Contract for which the data is being requested + * @param genericTickList comma separated ids of the available generic ticks: + * - 100 Option Volume (currently for stocks) + * - 101 Option Open Interest (currently for stocks) + * - 104 Historical Volatility (currently for stocks) + * - 105 Average Option Volume (currently for stocks) + * - 106 Option Implied Volatility (currently for stocks) + * - 162 Index Future Premium + * - 165 Miscellaneous Stats + * - 221 Mark Price (used in TWS P&L computations) + * - 225 Auction values (volume, price and imbalance) + * - 233 RTVolume - contains the last trade price, last trade size, last trade time, total volume, VWAP, and single trade flag. + * - 236 Shortable + * - 256 Inventory + * - 258 Fundamental Ratios + * - 411 Realtime Historical Volatility + * - 456 IBDividends + * @param snapshot for users with corresponding real time market data subscriptions. A true value will return a one-time snapshot, while a false value will provide streaming data. + * @param regulatory snapshot for US stocks requests NBBO snapshots for users which have "US Securities Snapshot Bundle" subscription but not corresponding Network A, B, or C subscription necessary for streaming market data. One-time snapshot of current market price that will incur a fee of 1 cent to the account per snapshot. + * @sa cancelMktData, EWrapper::tickPrice, EWrapper::tickSize, EWrapper::tickString, EWrapper::tickEFP, EWrapper::tickGeneric, EWrapper::tickOptionComputation, EWrapper::tickSnapshotEnd */ public void reqMktData(int tickerId, Contract contract, string genericTickList, bool snapshot, bool regulatorySnaphsot, List mktDataOptions) { @@ -1751,15 +1751,15 @@ public void reqMktData(int tickerId, Contract contract, string genericTickList, CloseAndSend(tickerId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQMKT); } - /** - * @brief Switches data type returned from reqMktData request to "frozen", "delayed" or "delayed-frozen" market data. Requires TWS/IBG v963+.\n - * The API can receive frozen market data from Trader Workstation. Frozen market data is the last data recorded in our system.\n During normal trading hours, the API receives real-time market data. Invoking this function with argument 2 requests a switch to frozen data immediately or after the close.\n When the market reopens, the market data type will automatically switch back to real time if available. - * @param marketDataType: - * by default only real-time (1) market data is enabled - * sending 1 (real-time) disables frozen, delayed and delayed-frozen market data - * sending 2 (frozen) enables frozen market data - * sending 3 (delayed) enables delayed and disables delayed-frozen market data - * sending 4 (delayed-frozen) enables delayed and delayed-frozen market data + /** + * @brief Switches data type returned from reqMktData request to "frozen", "delayed" or "delayed-frozen" market data. Requires TWS/IBG v963+.\n + * The API can receive frozen market data from Trader Workstation. Frozen market data is the last data recorded in our system.\n During normal trading hours, the API receives real-time market data. Invoking this function with argument 2 requests a switch to frozen data immediately or after the close.\n When the market reopens, the market data type will automatically switch back to real time if available. + * @param marketDataType: + * by default only real-time (1) market data is enabled + * sending 1 (real-time) disables frozen, delayed and delayed-frozen market data + * sending 2 (frozen) enables frozen market data + * sending 3 (delayed) enables delayed and disables delayed-frozen market data + * sending 4 (delayed-frozen) enables delayed and delayed-frozen market data */ public void reqMarketDataType(int marketDataType) { @@ -1776,13 +1776,13 @@ public void reqMarketDataType(int marketDataType) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQMARKETDATATYPE); } - /** - * @brief Requests the contract's market depth (order book).\n This request must be direct-routed to an exchange and not smart-routed. The number of simultaneous market depth requests allowed in an account is calculated based on a formula that looks at an accounts equity, commissions, and quote booster packs. - * @param tickerId the request's identifier - * @param contract the Contract for which the depth is being requested - * @param numRows the number of rows on each side of the order book - * @param isSmartDepth flag indicates that this is smart depth request - * @sa cancelMktDepth, EWrapper::updateMktDepth, EWrapper::updateMktDepthL2 + /** + * @brief Requests the contract's market depth (order book).\n This request must be direct-routed to an exchange and not smart-routed. The number of simultaneous market depth requests allowed in an account is calculated based on a formula that looks at an accounts equity, commissions, and quote booster packs. + * @param tickerId the request's identifier + * @param contract the Contract for which the depth is being requested + * @param numRows the number of rows on each side of the order book + * @param isSmartDepth flag indicates that this is smart depth request + * @sa cancelMktDepth, EWrapper::updateMktDepth, EWrapper::updateMktDepthL2 */ public void reqMarketDepth(int tickerId, Contract contract, int numRows, bool isSmartDepth, List mktDepthOptions) { @@ -1826,10 +1826,10 @@ public void reqMarketDepth(int tickerId, Contract contract, int numRows, bool is CloseAndSend(tickerId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQMKTDEPTH); } - /** - * @brief Subscribes to IB's News Bulletins - * @param allMessages if set to true, will return all the existing bulletins for the current day, set to false to receive only the new bulletins. - * @sa cancelNewsBulletin, EWrapper::updateNewsBulletin + /** + * @brief Subscribes to IB's News Bulletins + * @param allMessages if set to true, will return all the existing bulletins for the current day, set to false to receive only the new bulletins. + * @sa cancelNewsBulletin, EWrapper::updateNewsBulletin */ public void reqNewsBulletins(bool allMessages) { @@ -1845,9 +1845,9 @@ public void reqNewsBulletins(bool allMessages) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_GENERIC); } - /** - * @brief Requests all open orders places by this specific API client (identified by the API client id). For client ID 0, this will bind previous manual TWS orders. - * @sa reqAllOpenOrders, reqAutoOpenOrders, placeOrder, cancelOrder, reqGlobalCancel, EWrapper::openOrder, EWrapper::orderStatus, EWrapper::openOrderEnd + /** + * @brief Requests all open orders places by this specific API client (identified by the API client id). For client ID 0, this will bind previous manual TWS orders. + * @sa reqAllOpenOrders, reqAutoOpenOrders, placeOrder, cancelOrder, reqGlobalCancel, EWrapper::openOrder, EWrapper::orderStatus, EWrapper::openOrderEnd */ public void reqOpenOrders() { @@ -1861,9 +1861,9 @@ public void reqOpenOrders() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_OORDER); } - /** - * @brief Subscribes to position updates for all accessible accounts. All positions sent initially, and then only updates as positions change. - * @sa cancelPositions, EWrapper::position, EWrapper::positionEnd + /** + * @brief Subscribes to position updates for all accessible accounts. All positions sent initially, and then only updates as positions change. + * @sa cancelPositions, EWrapper::position, EWrapper::positionEnd */ public void reqPositions() { @@ -1879,19 +1879,19 @@ public void reqPositions() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQPOSITIONS); } - /** - * @brief Requests real time bars\n - * Currently, only 5 seconds bars are provided. This request is subject to the same pacing as any historical data request: no more than 60 API queries in more than 600 seconds.\n Real time bars subscriptions are also included in the calculation of the number of Level 1 market data subscriptions allowed in an account. - * @param tickerId the request's unique identifier. - * @param contract the Contract for which the depth is being requested - * @param barSize currently being ignored - * @param whatToShow the nature of the data being retrieved: - * - TRADES - * - MIDPOINT - * - BID - * - ASK - * @param useRTH set to 0 to obtain the data which was also generated ourside of the Regular Trading Hours, set to 1 to obtain only the RTH data - * @sa cancelRealTimeBars, EWrapper::realtimeBar + /** + * @brief Requests real time bars\n + * Currently, only 5 seconds bars are provided. This request is subject to the same pacing as any historical data request: no more than 60 API queries in more than 600 seconds.\n Real time bars subscriptions are also included in the calculation of the number of Level 1 market data subscriptions allowed in an account. + * @param tickerId the request's unique identifier. + * @param contract the Contract for which the depth is being requested + * @param barSize currently being ignored + * @param whatToShow the nature of the data being retrieved: + * - TRADES + * - MIDPOINT + * - BID + * - ASK + * @param useRTH set to 0 to obtain the data which was also generated ourside of the Regular Trading Hours, set to 1 to obtain only the RTH data + * @sa cancelRealTimeBars, EWrapper::realtimeBar */ public void reqRealTimeBars(int tickerId, Contract contract, int barSize, string whatToShow, bool useRTH, List realTimeBarsOptions) { @@ -1935,10 +1935,10 @@ public void reqRealTimeBars(int tickerId, Contract contract, int barSize, string CloseAndSend(tickerId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQRTBARS); } - /** - * @brief Requests an XML list of scanner parameters valid in TWS. \n - * Not all parameters are valid from API scanner. - * @sa reqScannerSubscription + /** + * @brief Requests an XML list of scanner parameters valid in TWS. \n + * Not all parameters are valid from API scanner. + * @sa reqScannerSubscription */ public void reqScannerParameters() { @@ -1953,11 +1953,11 @@ public void reqScannerParameters() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQSCANNERPARAMETERS); } - /** - * @brief Starts a subscription to market scan results based on the provided parameters. - * @param reqId the request's identifier - * @param subscription summary of the scanner subscription including its filters. - * @sa reqScannerParameters, ScannerSubscription, EWrapper::scannerData + /** + * @brief Starts a subscription to market scan results based on the provided parameters. + * @param reqId the request's identifier + * @param subscription summary of the scanner subscription including its filters. + * @sa reqScannerParameters, ScannerSubscription, EWrapper::scannerData */ public void reqScannerSubscription(int reqId, ScannerSubscription subscription, List scannerSubscriptionOptions, List scannerSubscriptionFilterOptions) => reqScannerSubscription(reqId, subscription, Util.TagValueListToString(scannerSubscriptionOptions), Util.TagValueListToString(scannerSubscriptionFilterOptions)); @@ -2014,16 +2014,16 @@ public void reqScannerSubscription(int reqId, ScannerSubscription subscription, CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQSCANNER); } - /** - * @brief Changes the TWS/GW log level. - * The default is 2 = ERROR\n - * 5 = DETAIL is required for capturing all API messages and troubleshooting API programs\n - * Valid values are:\n - * 1 = SYSTEM\n - * 2 = ERROR\n - * 3 = WARNING\n - * 4 = INFORMATION\n - * 5 = DETAIL\n + /** + * @brief Changes the TWS/GW log level. + * The default is 2 = ERROR\n + * 5 = DETAIL is required for capturing all API messages and troubleshooting API programs\n + * Valid values are:\n + * 1 = SYSTEM\n + * 2 = ERROR\n + * 3 = WARNING\n + * 4 = INFORMATION\n + * 5 = DETAIL\n */ public void setServerLogLevel(int logLevel) { @@ -2041,8 +2041,8 @@ public void setServerLogLevel(int logLevel) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_SERVER_LOG_LEVEL); } - /** - * @brief For IB's internal purpose. Allows to provide means of verification between the TWS and third party programs. + /** + * @brief For IB's internal purpose. Allows to provide means of verification between the TWS and third party programs. */ public void verifyRequest(string apiName, string apiVersion) { @@ -2074,8 +2074,8 @@ public void verifyRequest(string apiName, string apiVersion) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_VERIFYREQUEST); } - /** - * @brief For IB's internal purpose. Allows to provide means of verification between the TWS and third party programs. + /** + * @brief For IB's internal purpose. Allows to provide means of verification between the TWS and third party programs. */ public void verifyMessage(string apiData) { @@ -2101,8 +2101,8 @@ public void verifyMessage(string apiData) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_VERIFYMESSAGE); } - /** - * @brief For IB's internal purpose. Allows to provide means of verification between the TWS and third party programs. + /** + * @brief For IB's internal purpose. Allows to provide means of verification between the TWS and third party programs. */ public void verifyAndAuthRequest(string apiName, string apiVersion, string opaqueIsvKey) { @@ -2135,8 +2135,8 @@ public void verifyAndAuthRequest(string apiName, string apiVersion, string opaqu CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_VERIFYANDAUTHREQUEST); } - /** - * @brief For IB's internal purpose. Allows to provide means of verification between the TWS and third party programs. + /** + * @brief For IB's internal purpose. Allows to provide means of verification between the TWS and third party programs. */ public void verifyAndAuthMessage(string apiData, string xyzResponse) { @@ -2163,9 +2163,9 @@ public void verifyAndAuthMessage(string apiData, string xyzResponse) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_VERIFYANDAUTHMESSAGE); } - /** - * @brief Requests all available Display Groups in TWS - * @param requestId is the ID of this request + /** + * @brief Requests all available Display Groups in TWS + * @param requestId is the ID of this request */ public void queryDisplayGroups(int requestId) { @@ -2182,10 +2182,10 @@ public void queryDisplayGroups(int requestId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_QUERYDISPLAYGROUPS); } - /** - * @brief Integrates API client and TWS window grouping. - * @param requestId is the Id chosen for this subscription request - * @param groupId is the display group for integration + /** + * @brief Integrates API client and TWS window grouping. + * @param requestId is the Id chosen for this subscription request + * @param groupId is the display group for integration */ public void subscribeToGroupEvents(int requestId, int groupId) { @@ -2203,14 +2203,14 @@ public void subscribeToGroupEvents(int requestId, int groupId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_SUBSCRIBETOGROUPEVENTS); } - /** - * @brief Updates the contract displayed in a TWS Window Group - * @param requestId is the ID chosen for this request - * @param contractInfo is an encoded value designating a unique IB contract. Possible values include: - * 1. none = empty selection - * 2. contractID@exchange - any non-combination contract. Examples 8314@SMART for IBM SMART; 8314@ARCA for IBM ARCA - * 3. combo= if any combo is selected - * Note: This request from the API does not get a TWS response unless an error occurs. + /** + * @brief Updates the contract displayed in a TWS Window Group + * @param requestId is the ID chosen for this request + * @param contractInfo is an encoded value designating a unique IB contract. Possible values include: + * 1. none = empty selection + * 2. contractID@exchange - any non-combination contract. Examples 8314@SMART for IBM SMART; 8314@ARCA for IBM ARCA + * 3. combo= if any combo is selected + * Note: This request from the API does not get a TWS response unless an error occurs. */ public void updateDisplayGroup(int requestId, string contractInfo) { @@ -2237,8 +2237,8 @@ public void updateDisplayGroup(int requestId, string contractInfo) CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_UPDATEDISPLAYGROUP); } - /** - * @brief Cancels a TWS Window Group subscription + /** + * @brief Cancels a TWS Window Group subscription */ public void unsubscribeFromGroupEvents(int requestId) { @@ -2255,13 +2255,13 @@ public void unsubscribeFromGroupEvents(int requestId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_UNSUBSCRIBEFROMGROUPEVENTS); } - /** - * @brief Requests position subscription for account and/or model - * Initially all positions are returned, and then updates are returned for any position changes in real time. - * @param requestId - Request's identifier - * @param account - If an account Id is provided, only the account's positions belonging to the specified model will be delivered - * @param modelCode - The code of the model's positions we are interested in. - * @sa cancelPositionsMulti, EWrapper::positionMulti, EWrapper::positionMultiEnd + /** + * @brief Requests position subscription for account and/or model + * Initially all positions are returned, and then updates are returned for any position changes in real time. + * @param requestId - Request's identifier + * @param account - If an account Id is provided, only the account's positions belonging to the specified model will be delivered + * @param modelCode - The code of the model's positions we are interested in. + * @sa cancelPositionsMulti, EWrapper::positionMulti, EWrapper::positionMultiEnd */ public void reqPositionsMulti(int requestId, string account, string modelCode) { @@ -2289,10 +2289,10 @@ public void reqPositionsMulti(int requestId, string account, string modelCode) CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQPOSITIONSMULTI); } - /** - * @brief Cancels positions request for account and/or model - * @param requestId - the identifier of the request to be canceled. - * @sa reqPositionsMulti + /** + * @brief Cancels positions request for account and/or model + * @param requestId - the identifier of the request to be canceled. + * @sa reqPositionsMulti */ public void cancelPositionsMulti(int requestId) { @@ -2309,13 +2309,13 @@ public void cancelPositionsMulti(int requestId) CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_CANPOSITIONSMULTI); } - /** - * @brief Requests account updates for account and/or model - * @param reqId identifier to label the request - * @param account account values can be requested for a particular account - * @param modelCode values can also be requested for a model - * @param ledgerAndNLV returns light-weight request; only currency positions as opposed to account values and currency positions - * @sa cancelAccountUpdatesMulti, EWrapper::accountUpdateMulti, EWrapper::accountUpdateMultiEnd + /** + * @brief Requests account updates for account and/or model + * @param reqId identifier to label the request + * @param account account values can be requested for a particular account + * @param modelCode values can also be requested for a model + * @param ledgerAndNLV returns light-weight request; only currency positions as opposed to account values and currency positions + * @sa cancelAccountUpdatesMulti, EWrapper::accountUpdateMulti, EWrapper::accountUpdateMultiEnd */ public void reqAccountUpdatesMulti(int requestId, string account, string modelCode, bool ledgerAndNLV) { @@ -2344,10 +2344,10 @@ public void reqAccountUpdatesMulti(int requestId, string account, string modelCo CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQACCOUNTUPDATESMULTI); } - /** - * @brief Cancels account updates request for account and/or model - * @param requestId account subscription to cancel - * @sa reqAccountUpdatesMulti + /** + * @brief Cancels account updates request for account and/or model + * @param requestId account subscription to cancel + * @sa reqAccountUpdatesMulti */ public void cancelAccountUpdatesMulti(int requestId) { @@ -2364,14 +2364,14 @@ public void cancelAccountUpdatesMulti(int requestId) CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_CANACCOUNTUPDATESMULTI); } - /** - * @brief Requests security definition option parameters for viewing a contract's option chain - * @param reqId the ID chosen for the request - * @param underlyingSymbol - * @param futFopExchange The exchange on which the returned options are trading. Can be set to the empty string "" for all exchanges. - * @param underlyingSecType The type of the underlying security, i.e. STK - * @param underlyingConId the contract ID of the underlying security - * @sa EWrapper::securityDefinitionOptionParameter + /** + * @brief Requests security definition option parameters for viewing a contract's option chain + * @param reqId the ID chosen for the request + * @param underlyingSymbol + * @param futFopExchange The exchange on which the returned options are trading. Can be set to the empty string "" for all exchanges. + * @param underlyingSecType The type of the underlying security, i.e. STK + * @param underlyingConId the contract ID of the underlying security + * @sa EWrapper::securityDefinitionOptionParameter */ public void reqSecDefOptParams(int reqId, string underlyingSymbol, string futFopExchange, string underlyingSecType, int underlyingConId) { @@ -2399,9 +2399,9 @@ public void reqSecDefOptParams(int reqId, string underlyingSymbol, string futFop CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQSECDEFOPTPARAMS); } - /** - * @brief Requests pre-defined Soft Dollar Tiers. This is only supported for registered professional advisors and hedge and mutual funds who have configured Soft Dollar Tiers in Account Management. Refer to: https://www.interactivebrokers.com/en/software/am/am/manageaccount/requestsoftdollars.htm?Highlight=soft%20dollar%20tier - * @sa EWrapper::softDollarTiers + /** + * @brief Requests pre-defined Soft Dollar Tiers. This is only supported for registered professional advisors and hedge and mutual funds who have configured Soft Dollar Tiers in Account Management. Refer to: https://www.interactivebrokers.com/en/software/am/am/manageaccount/requestsoftdollars.htm?Highlight=soft%20dollar%20tier + * @sa EWrapper::softDollarTiers */ public void reqSoftDollarTiers(int reqId) { @@ -2416,9 +2416,9 @@ public void reqSoftDollarTiers(int reqId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQSOFTDOLLARTIERS); } - /** - * @brief Requests family codes for an account, for instance if it is a FA, IBroker, or associated account. - * @sa EWrapper::familyCodes + /** + * @brief Requests family codes for an account, for instance if it is a FA, IBroker, or associated account. + * @sa EWrapper::familyCodes */ public void reqFamilyCodes() { @@ -2432,11 +2432,11 @@ public void reqFamilyCodes() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQFAMILYCODES); } - /** - * @brief Requests matching stock symbols - * @param reqId id to specify the request - * @param pattern - either start of ticker symbol or (for larger strings) company name - * @sa EWrapper::symbolSamples + /** + * @brief Requests matching stock symbols + * @param reqId id to specify the request + * @param pattern - either start of ticker symbol or (for larger strings) company name + * @sa EWrapper::symbolSamples */ public void reqMatchingSymbols(int reqId, string pattern) { @@ -2461,9 +2461,9 @@ public void reqMatchingSymbols(int reqId, string pattern) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQMATCHINGSYMBOLS); } - /** - * @brief Requests venues for which market data is returned to updateMktDepthL2 (those with market makers) - * @sa EWrapper::mktDepthExchanges + /** + * @brief Requests venues for which market data is returned to updateMktDepthL2 (those with market makers) + * @sa EWrapper::mktDepthExchanges */ public void reqMktDepthExchanges() { @@ -2477,11 +2477,11 @@ public void reqMktDepthExchanges() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQMKTDEPTHEXCHANGES); } - /** - * @brief Returns the mapping of single letter codes to exchange names given the mapping identifier - * @param reqId id of the request - * @param bboExchange mapping identifier received from EWrapper.tickReqParams - * @sa EWrapper::smartComponents + /** + * @brief Returns the mapping of single letter codes to exchange names given the mapping identifier + * @param reqId id of the request + * @param bboExchange mapping identifier received from EWrapper.tickReqParams + * @sa EWrapper::smartComponents */ public void reqSmartComponents(int reqId, string bboExchange) { @@ -2506,9 +2506,9 @@ public void reqSmartComponents(int reqId, string bboExchange) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQSMARTCOMPONENTS); } - /** - * @brief Requests news providers which the user has subscribed to. - * @sa EWrapper::newsProviders + /** + * @brief Requests news providers which the user has subscribed to. + * @sa EWrapper::newsProviders */ public void reqNewsProviders() { @@ -2522,13 +2522,13 @@ public void reqNewsProviders() CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQNEWSPROVIDERS); } - /** - * @brief Requests news article body given articleId. - * @param requestId id of the request - * @param providerCode short code indicating news provider, e.g. FLY - * @param articleId id of the specific article - * @param newsArticleOptions reserved for internal use. Should be defined as null. - * @sa EWrapper::newsArticle, + /** + * @brief Requests news article body given articleId. + * @param requestId id of the request + * @param providerCode short code indicating news provider, e.g. FLY + * @param articleId id of the specific article + * @param newsArticleOptions reserved for internal use. Should be defined as null. + * @sa EWrapper::newsArticle, */ public void reqNewsArticle(int requestId, string providerCode, string articleId, List newsArticleOptions) { @@ -2555,16 +2555,16 @@ public void reqNewsArticle(int requestId, string providerCode, string articleId, CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQNEWSARTICLE); } - /** - * @brief Requests historical news headlines - * @param requestId - * @param conId - contract id of ticker - * @param providerCodes - a '+'-separated list of provider codes - * @param startDateTime - marks the (exclusive) start of the date range. The format is yyyy-MM-dd HH:mm:ss.0 - * @param endDateTime - marks the (inclusive) end of the date range. The format is yyyy-MM-dd HH:mm:ss.0 - * @param totalResults - the maximum number of headlines to fetch (1 - 300) - * @param historicalNewsOptions reserved for internal use. Should be defined as null. - * @sa EWrapper::historicalNews, EWrapper::historicalNewsEnd + /** + * @brief Requests historical news headlines + * @param requestId + * @param conId - contract id of ticker + * @param providerCodes - a '+'-separated list of provider codes + * @param startDateTime - marks the (exclusive) start of the date range. The format is yyyy-MM-dd HH:mm:ss.0 + * @param endDateTime - marks the (inclusive) end of the date range. The format is yyyy-MM-dd HH:mm:ss.0 + * @param totalResults - the maximum number of headlines to fetch (1 - 300) + * @param historicalNewsOptions reserved for internal use. Should be defined as null. + * @sa EWrapper::historicalNews, EWrapper::historicalNewsEnd */ public void reqHistoricalNews(int requestId, int conId, string providerCodes, string startDateTime, string endDateTime, int totalResults, List historicalNewsOptions) { @@ -2594,14 +2594,14 @@ public void reqHistoricalNews(int requestId, int conId, string providerCodes, st CloseAndSend(requestId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQHISTORICALNEWS); } - /** - * @brief Returns the timestamp of earliest available historical data for a contract and data type - * @param tickerId - an identifier for the request - * @param contract - contract object for which head timestamp is being requested - * @param whatToShow - type of data for head timestamp - "BID", "ASK", "TRADES", etc - * @param useRTH - use regular trading hours only, 1 for yes or 0 for no - * @param formatDate - @param formatDate set to 1 to obtain the bars' time as yyyyMMdd HH:mm:ss, set to 2 to obtain it like system time format in seconds - * @sa headTimeStamp + /** + * @brief Returns the timestamp of earliest available historical data for a contract and data type + * @param tickerId - an identifier for the request + * @param contract - contract object for which head timestamp is being requested + * @param whatToShow - type of data for head timestamp - "BID", "ASK", "TRADES", etc + * @param useRTH - use regular trading hours only, 1 for yes or 0 for no + * @param formatDate - @param formatDate set to 1 to obtain the bars' time as yyyyMMdd HH:mm:ss, set to 2 to obtain it like system time format in seconds + * @sa headTimeStamp */ public void reqHeadTimestamp(int tickerId, Contract contract, string whatToShow, int useRTH, int formatDate) { @@ -2629,9 +2629,9 @@ public void reqHeadTimestamp(int tickerId, Contract contract, string whatToShow, CloseAndSend(tickerId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQHEADTIMESTAMP); } - /** - * @brief Cancels a pending reqHeadTimeStamp request\n - * @param tickerId Id of the request + /** + * @brief Cancels a pending reqHeadTimeStamp request\n + * @param tickerId Id of the request */ public void cancelHeadTimestamp(int tickerId) { @@ -2647,13 +2647,13 @@ public void cancelHeadTimestamp(int tickerId) } - /** - * @brief Returns data histogram of specified contract\n - * @param tickerId - an identifier for the request\n - * @param contract - Contract object for which histogram is being requested\n - * @param useRTH - use regular trading hours only, 1 for yes or 0 for no\n - * @param period - period of which data is being requested, e.g. "3 days"\n - * @sa histogramData + /** + * @brief Returns data histogram of specified contract\n + * @param tickerId - an identifier for the request\n + * @param contract - Contract object for which histogram is being requested\n + * @param useRTH - use regular trading hours only, 1 for yes or 0 for no\n + * @param period - period of which data is being requested, e.g. "3 days"\n + * @sa histogramData */ public void reqHistogramData(int tickerId, Contract contract, bool useRTH, string period) { @@ -2680,10 +2680,10 @@ public void reqHistogramData(int tickerId, Contract contract, bool useRTH, strin CloseAndSend(tickerId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQHISTOGRAMDATA); } - /** - * @brief Cancels an active data histogram request - * @param tickerId - identifier specified in reqHistogramData request - * @sa reqHistogramData, histogramData + /** + * @brief Cancels an active data histogram request + * @param tickerId - identifier specified in reqHistogramData request + * @sa reqHistogramData, histogramData */ public void cancelHistogramData(int tickerId) { @@ -2699,12 +2699,12 @@ public void cancelHistogramData(int tickerId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_CANCELHISTOGRAMDATA); } - /** - * @brief Requests details about a given market rule\n - * The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price\n - * A list of market rule ids can be obtained by invoking reqContractDetails on a particular contract. The returned market rule ID list will provide the market rule ID for the instrument in the correspond valid exchange list in contractDetails.\n - * @param marketRuleId - the id of market rule\n - * @sa EWrapper::marketRule + /** + * @brief Requests details about a given market rule\n + * The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price\n + * A list of market rule ids can be obtained by invoking reqContractDetails on a particular contract. The returned market rule ID list will provide the market rule ID for the instrument in the correspond valid exchange list in contractDetails.\n + * @param marketRuleId - the id of market rule\n + * @sa EWrapper::marketRule */ public void reqMarketRule(int marketRuleId) { @@ -2720,10 +2720,10 @@ public void reqMarketRule(int marketRuleId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQMARKETRULE); } - /** - * @brief Creates subscription for real time daily PnL and unrealized PnL updates - * @param account account for which to receive PnL updates - * @param modelCode specify to request PnL updates for a specific model + /** + * @brief Creates subscription for real time daily PnL and unrealized PnL updates + * @param account account for which to receive PnL updates + * @param modelCode specify to request PnL updates for a specific model */ public void reqPnL(int reqId, string account, string modelCode) { @@ -2749,9 +2749,9 @@ public void reqPnL(int reqId, string account, string modelCode) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQPNL); } - /** - * @brief cancels subscription for real time updated daily PnL - * params reqId + /** + * @brief cancels subscription for real time updated daily PnL + * params reqId */ public void cancelPnL(int reqId) { @@ -2767,13 +2767,13 @@ public void cancelPnL(int reqId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_CANCELPNL); } - /** - * @brief Requests real time updates for daily PnL of individual positions - * @param reqId - * @param account account in which position exists - * @param modelCode model in which position exists - * @param conId contract ID (conId) of contract to receive daily PnL updates for. - * Note: does not return message if invalid conId is entered + /** + * @brief Requests real time updates for daily PnL of individual positions + * @param reqId + * @param account account in which position exists + * @param modelCode model in which position exists + * @param conId contract ID (conId) of contract to receive daily PnL updates for. + * Note: does not return message if invalid conId is entered */ public void reqPnLSingle(int reqId, string account, string modelCode, int conId) { @@ -2800,9 +2800,9 @@ public void reqPnLSingle(int reqId, string account, string modelCode, int conId) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQPNLSINGLE); } - /** - * @brief Cancels real time subscription for a positions daily PnL information - * @param reqId + /** + * @brief Cancels real time subscription for a positions daily PnL information + * @param reqId */ public void cancelPnLSingle(int reqId) { @@ -2818,17 +2818,17 @@ public void cancelPnLSingle(int reqId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_REQPNLSINGLE); } - /** - * @brief Requests historical Time&Sales data for an instrument - * @param reqId id of the request - * @param contract Contract object that is subject of query - * @param startDateTime ,i.e. "20170701 12:01:00". Uses TWS timezone specified at login. - * @param endDateTime ,i.e. "20170701 13:01:00". In TWS timezone. Exactly one of start time and end time has to be defined. - * @param numberOfTicks Number of distinct data points. Max currently 1000 per request. - * @param whatToShow (Bid_Ask, Midpoint, Trades) Type of data requested. - * @param useRth Data from regular trading hours (1), or all available hours (0) - * @param ignoreSize A filter only used when the source price is Bid_Ask - * @param miscOptions should be defined as null, reserved for internal use + /** + * @brief Requests historical Time&Sales data for an instrument + * @param reqId id of the request + * @param contract Contract object that is subject of query + * @param startDateTime ,i.e. "20170701 12:01:00". Uses TWS timezone specified at login. + * @param endDateTime ,i.e. "20170701 13:01:00". In TWS timezone. Exactly one of start time and end time has to be defined. + * @param numberOfTicks Number of distinct data points. Max currently 1000 per request. + * @param whatToShow (Bid_Ask, Midpoint, Trades) Type of data requested. + * @param useRth Data from regular trading hours (1), or all available hours (0) + * @param ignoreSize A filter only used when the source price is Bid_Ask + * @param miscOptions should be defined as null, reserved for internal use */ public void reqHistoricalTicks(int reqId, Contract contract, string startDateTime, string endDateTime, int numberOfTicks, string whatToShow, int useRth, bool ignoreSize, @@ -2862,9 +2862,9 @@ public void reqHistoricalTicks(int reqId, Contract contract, string startDateTim CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQHISTORICALTICKS); } - /** - * @brief Requests metadata from the WSH calendar - * @param reqId + /** + * @brief Requests metadata from the WSH calendar + * @param reqId */ public void reqWshMetaData(int reqId) { @@ -2888,9 +2888,9 @@ public void reqWshMetaData(int reqId) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQ_WSH_META_DATA); } - /** - * @brief Cancels pending request for WSH metadata - * @param reqId + /** + * @brief Cancels pending request for WSH metadata + * @param reqId */ public void cancelWshMetaData(int reqId) { @@ -2906,10 +2906,10 @@ public void cancelWshMetaData(int reqId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_CAN_WSH_META_DATA); } - /** - * @brief Requests event data from the wSH calendar - * @param reqId - * @param conId contract ID (conId) of contract to receive WSH Event Data for. + /** + * @brief Requests event data from the wSH calendar + * @param reqId + * @param conId contract ID (conId) of contract to receive WSH Event Data for. */ public void reqWshEventData(int reqId, WshEventData wshEventData) { @@ -2967,9 +2967,9 @@ public void reqWshEventData(int reqId, WshEventData wshEventData) CloseAndSend(reqId, paramsList, lengthPos, EClientErrors.FAIL_SEND_REQ_WSH_EVENT_DATA); } - /** - * @brief Cancels pending WSH event data request - * @param reqId + /** + * @brief Cancels pending WSH event data request + * @param reqId */ public void cancelWshEventData(int reqId) { @@ -2985,9 +2985,9 @@ public void cancelWshEventData(int reqId) CloseAndSend(paramsList, lengthPos, EClientErrors.FAIL_SEND_CAN_WSH_EVENT_DATA); } - /** - * @brief Requests user info - * @param reqId + /** + * @brief Requests user info + * @param reqId */ public void reqUserInfo(int reqId) { @@ -3406,5 +3406,5 @@ public byte[] ReadAtLeastNBytes(int msgSize) public byte[] ReadByteArray(int msgSize) => new BinaryReader(tcpStream).ReadBytes(msgSize); public bool AsyncEConnect { get; set; } - } -} + } +} diff --git a/Gateway/Schwab/Libs/Adapter.cs b/Gateway/Schwab/Libs/Adapter.cs index dc7ccacc9b..9592d311bb 100644 --- a/Gateway/Schwab/Libs/Adapter.cs +++ b/Gateway/Schwab/Libs/Adapter.cs @@ -12,8 +12,9 @@ using System.Net.WebSockets; using System.Text; using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading; using System.Threading.Tasks; -using System.Timers; using Terminal.Core.Domains; using Terminal.Core.Enums; using Terminal.Core.Extensions; @@ -29,6 +30,11 @@ public class Adapter : Gateway /// protected string _accountCode; + /// + /// User preferences + /// + protected UserDataMessage _userData; + /// /// HTTP client /// @@ -65,6 +71,7 @@ public class Adapter : Gateway public Adapter() { DataUri = "https://api.schwabapi.com"; + StreamUri = "wss://streamer-api.schwab.com/ws"; _connections = []; } @@ -78,14 +85,36 @@ public override async Task> Connect() try { + var ws = new ClientWebSocket(); + var scheduler = new ScheduleService(); + await Disconnect(); _sender = new Service(); - await GetAccount([]); await UpdateToken("/v1/oauth/token"); + await GetAccount([]); + + _streamer = await GetConnection(ws, scheduler); - var interval = new Timer(TimeSpan.FromMinutes(1)); + var userData = await GetUserData(); + var streamData = userData.Data.Streamer.FirstOrDefault(); + + await SendStream(_streamer, new StreamInputMessage + { + Service = "ADMIN", + Command = "LOGIN", + CustomerId = streamData.CustomerId, + CorrelationId = streamData.CorrelationId, + Parameters = new StreamLoginMessage + { + Channel = streamData.Channel, + FunctionId = streamData.FunctionId, + Authorization = Scope.AccessToken + } + }); + + var interval = new System.Timers.Timer(TimeSpan.FromMinutes(1)); interval.Enabled = true; interval.Elapsed += async (sender, e) => await UpdateToken("/v1/oauth/token"); @@ -119,6 +148,22 @@ public override async Task> Subscribe(InstrumentModel { await Unsubscribe(instrument); + var streamData = _userData.Streamer.FirstOrDefault(); + + await SendStream(_streamer, new StreamInputMessage + { + Service = ExternalMap.GetStreamService(instrument), + Command = "ADD", + CustomerId = streamData.CustomerId, + CorrelationId = streamData.CorrelationId, + Parameters = new StreamLoginMessage + { + Channel = streamData.Channel, + FunctionId = streamData.FunctionId, + Authorization = Scope.AccessToken + } + }); + response.Data = StatusEnum.Success; } catch (Exception e) @@ -132,12 +177,28 @@ public override async Task> Subscribe(InstrumentModel /// /// Save state and dispose /// - public override Task> Disconnect() + public override async Task> Disconnect() { var response = new ResponseModel(); try { + var streamData = _userData.Streamer.FirstOrDefault(); + + await SendStream(_streamer, new StreamInputMessage + { + Service = "ADMIN", + Command = "LOGOUT", + CustomerId = streamData.CustomerId, + CorrelationId = streamData.CorrelationId, + Parameters = new StreamLoginMessage + { + Channel = streamData.Channel, + FunctionId = streamData.FunctionId, + Authorization = Scope.AccessToken + } + }); + _connections?.ForEach(o => o?.Dispose()); _connections?.Clear(); @@ -148,7 +209,7 @@ public override Task> Disconnect() response.Errors.Add(new ErrorModel { ErrorMessage = $"{e}" }); } - return Task.FromResult(response); + return response; } /// @@ -417,6 +478,67 @@ public override async Task>> GetPositions(Positi return response; } + /// + /// Send data to web socket stream + /// + /// + /// + /// + /// + protected virtual Task SendStream(ClientWebSocket ws, object data, CancellationTokenSource cancellation = null) + { + var content = JsonSerializer.Serialize(data, _sender.Options); + var message = Encoding.ASCII.GetBytes(content); + + return ws.SendAsync( + message, + WebSocketMessageType.Binary, + true, + cancellation?.Token ?? CancellationToken.None); + } + + /// + /// Web socket stream + /// + /// + /// + /// + protected virtual async Task GetConnection(ClientWebSocket ws, ScheduleService scheduler) + { + var source = new UriBuilder(StreamUri); + var cancellation = new CancellationTokenSource(); + + await ws.ConnectAsync(source.Uri, cancellation.Token); + + scheduler.Send(async () => + { + while (ws.State is WebSocketState.Open) + { + var data = new byte[byte.MaxValue]; + + await ws.ReceiveAsync(data, cancellation.Token).ContinueWith(async o => + { + var response = await o; + var content = $"[{Encoding.ASCII.GetString(data).Trim(['\0', '[', ']'])}]"; + var message = JsonNode + .Parse(content) + ?.AsArray() + ?.FirstOrDefault(); + + Console.WriteLine(Encoding.ASCII.GetString(data)); + + //switch ($"{message?["T"]}".ToUpper()) + //{ + // case "Q": OnPoint(content); break; + // case "T": OnTrade(content); break; + //} + }); + } + }); + + return ws; + } + /// /// Send data to the API /// @@ -450,6 +572,7 @@ public override async Task>> GetPositions(Positi return await _sender.Send(message, _sender.Options); } + /// /// Refresh token /// @@ -490,6 +613,30 @@ protected virtual async Task UpdateToken(string source) } } + /// + /// Refresh token + /// + /// + /// + /// + protected async Task> GetUserData() + { + var response = new ResponseModel(); + + try + { + var userResponse = await SendData($"/trader/v1/userPreference"); + + _userData = response.Data = userResponse.Data; + } + catch (Exception e) + { + response.Errors = [new ErrorModel { ErrorMessage = $"{e}" }]; + } + + return response; + } + /// /// Send order /// @@ -518,7 +665,7 @@ protected virtual async Task> CreateOrder(OrderModel o } inResponse.Data.Transaction.Id = orderId; - inResponse.Data.Transaction.Status = Terminal.Core.Enums.OrderStatusEnum.Filled; + inResponse.Data.Transaction.Status = OrderStatusEnum.Filled; Account.Orders[order.Id] = order; } @@ -551,7 +698,7 @@ protected virtual async Task> DeleteOrder(OrderModel o } inResponse.Data = order; - inResponse.Data.Transaction.Status = Terminal.Core.Enums.OrderStatusEnum.Canceled; + inResponse.Data.Transaction.Status = OrderStatusEnum.Canceled; } catch (Exception e) { diff --git a/Gateway/Schwab/Libs/Maps/ExternalMap.cs b/Gateway/Schwab/Libs/Maps/ExternalMap.cs index 6bed6709e0..4ed81202ce 100644 --- a/Gateway/Schwab/Libs/Maps/ExternalMap.cs +++ b/Gateway/Schwab/Libs/Maps/ExternalMap.cs @@ -1,6 +1,6 @@ using Schwab.Messages; -using System; using System.Linq; +using Terminal.Core.Domains; using Terminal.Core.Enums; using Terminal.Core.Models; @@ -8,6 +8,19 @@ namespace Schwab.Mappers { public class ExternalMap { + public static string GetStreamService(InstrumentModel instrument) + { + switch (instrument.Type) + { + case InstrumentEnum.Shares: return "LEVELONE_EQUITIES"; + case InstrumentEnum.Futures: return "LEVELONE_FUTURES"; + case InstrumentEnum.Currencies: return "LEVELONE_FOREX"; + case InstrumentEnum.Options: return instrument.Name.StartsWith('/') ? "LEVELONE_FUTURES_OPTIONS" : "LEVELONE_OPTIONS"; + } + + return null; + } + /// /// Convert remote order from brokerage to local record /// diff --git a/Gateway/Schwab/Libs/Messages/OfferMessage.cs b/Gateway/Schwab/Libs/Messages/OfferMessage.cs new file mode 100644 index 0000000000..758832f956 --- /dev/null +++ b/Gateway/Schwab/Libs/Messages/OfferMessage.cs @@ -0,0 +1,15 @@ +namespace Schwab.Messages +{ + using System.Text.Json.Serialization; + + public partial class OfferMessage + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("level2Permissions")] + public bool? Level2Permissions { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mktDataPermission")] + public string MktDataPermission { get; set; } + } +} diff --git a/Gateway/Schwab/Libs/Messages/StreamInputMessage.cs b/Gateway/Schwab/Libs/Messages/StreamInputMessage.cs new file mode 100644 index 0000000000..3cf1c0eb27 --- /dev/null +++ b/Gateway/Schwab/Libs/Messages/StreamInputMessage.cs @@ -0,0 +1,32 @@ +namespace Schwab.Messages +{ + using System.Collections.Generic; + using System.Text.Json.Serialization; + + public partial class StreamInputMessage + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestid")] + public long? Requestid { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("service")] + public string Service { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("command")] + public string Command { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("SchwabClientCustomerId")] + public string CustomerId { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("SchwabClientCorrelId")] + public string CorrelationId { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parameters")] + public object Parameters { get; set; } + } +} diff --git a/Gateway/Schwab/Libs/Messages/StreamLoginMessage.cs b/Gateway/Schwab/Libs/Messages/StreamLoginMessage.cs new file mode 100644 index 0000000000..47a2c6dee3 --- /dev/null +++ b/Gateway/Schwab/Libs/Messages/StreamLoginMessage.cs @@ -0,0 +1,19 @@ +namespace Schwab.Messages +{ + using System.Text.Json.Serialization; + + public partial class StreamLoginMessage + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("Authorization")] + public string Authorization { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("SchwabClientChannel")] + public string Channel { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("SchwabClientFunctionId")] + public string FunctionId { get; set; } + } +} diff --git a/Gateway/Schwab/Libs/Messages/StreamParamsMessage.cs b/Gateway/Schwab/Libs/Messages/StreamParamsMessage.cs new file mode 100644 index 0000000000..758b3f9c53 --- /dev/null +++ b/Gateway/Schwab/Libs/Messages/StreamParamsMessage.cs @@ -0,0 +1,15 @@ +namespace Schwab.Messages +{ + using System.Text.Json.Serialization; + + public partial class Parameters + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("keys")] + public string Keys { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fields")] + public string Fields { get; set; } + } +} diff --git a/Gateway/Schwab/Libs/Messages/StreamerMessage.cs b/Gateway/Schwab/Libs/Messages/StreamerMessage.cs new file mode 100644 index 0000000000..85e08cb5d4 --- /dev/null +++ b/Gateway/Schwab/Libs/Messages/StreamerMessage.cs @@ -0,0 +1,28 @@ +namespace Schwab.Messages +{ + using System; + using System.Text.Json.Serialization; + + public partial class StreamerMessage + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("streamerSocketUrl")] + public string SocketUrl { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("schwabClientCustomerId")] + public string CustomerId { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("schwabClientCorrelId")] + public string CorrelationId { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("schwabClientChannel")] + public string Channel { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("schwabClientFunctionId")] + public string FunctionId { get; set; } + } +} diff --git a/Gateway/Schwab/Libs/Messages/UserAccountMessage.cs b/Gateway/Schwab/Libs/Messages/UserAccountMessage.cs new file mode 100644 index 0000000000..3f1aa33f22 --- /dev/null +++ b/Gateway/Schwab/Libs/Messages/UserAccountMessage.cs @@ -0,0 +1,35 @@ +namespace Schwab.Messages +{ + using System.Text.Json.Serialization; + + public partial class UserAccountMessage + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("accountNumber")] + public long? AccountNumber { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("primaryAccount")] + public bool? PrimaryAccount { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("nickName")] + public string NickName { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("displayAcctId")] + public string DisplayAcctId { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoPositionEffect")] + public bool? AutoPositionEffect { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("accountColor")] + public string AccountColor { get; set; } + } +} diff --git a/Gateway/Schwab/Libs/Messages/UserDataMessage.cs b/Gateway/Schwab/Libs/Messages/UserDataMessage.cs new file mode 100644 index 0000000000..f751965eb0 --- /dev/null +++ b/Gateway/Schwab/Libs/Messages/UserDataMessage.cs @@ -0,0 +1,20 @@ +namespace Schwab.Messages +{ + using System.Collections.Generic; + using System.Text.Json.Serialization; + + public partial class UserDataMessage + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("accounts")] + public List Accounts { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("streamerInfo")] + public List Streamer { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("offers")] + public List Offers { get; set; } + } +} diff --git a/Terminal/Components/OptionPageComponent.razor.cs b/Terminal/Components/OptionPageComponent.razor.cs index ec1c9d2736..069d2021fc 100644 --- a/Terminal/Components/OptionPageComponent.razor.cs +++ b/Terminal/Components/OptionPageComponent.razor.cs @@ -14,6 +14,7 @@ using Terminal.Core.Indicators; using Terminal.Core.Models; using Terminal.Models; +using Terminal.Services; using Ib = InteractiveBrokers; using Sc = Schwab; using Scm = Schwab.Messages; @@ -161,21 +162,6 @@ public virtual Ib.Adapter CreateIbAccount() }; } - /// - /// Get position delta - /// - /// - /// - public virtual double GetDelta(OrderModel o) - { - var volume = o.Transaction?.Volume; - var leverage = o.Transaction?.Instrument?.Leverage; - var delta = o.Transaction?.Instrument?.Derivative?.Variable?.Delta; - var side = o.Side is OrderSideEnum.Buy ? 1.0 : -1.0; - - return ((delta ?? volume) * leverage * side) ?? 0; - } - /// /// Process tick data /// @@ -187,7 +173,7 @@ public virtual async Task OnUpdate(PointModel point, int days, Action(point)); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Volume", "CallPutVolume", new AreaShape { Y = calls.Sum(o => o.Point.Volume) - puts.Sum(o => o.Point.Volume), Component = com }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Delta", "CallDelta", new AreaShape { Y = calls.Sum(o => o.Derivative.Variable.Delta), Component = comUp }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Delta", "PutDelta", new AreaShape { Y = puts.Sum(o => o.Derivative.Variable.Delta), Component = comDown }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Gamma", "CallGamma", new AreaShape { Y = calls.Sum(o => o.Derivative.Variable.Gamma), Component = comUp }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Gamma", "PutGamma", new AreaShape { Y = -puts.Sum(o => o.Derivative.Variable.Gamma), Component = comDown }); + View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Volume", "CallVolume", new AreaShape { Y = calls.Sum(o => o.Point.Volume), Component = comUp }); + View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Volume", "PutVolume", new AreaShape { Y = -puts.Sum(o => o.Point.Volume), Component = comDown }); + View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Delta", "CallPutDelta", new AreaShape { Y = calls.Sum(o => o.Derivative.Variable.Delta) + puts.Sum(o => o.Derivative.Variable.Delta), Component = com }); + View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Gamma", "CallPutGamma", new AreaShape { Y = calls.Sum(o => o.Derivative.Variable.Gamma) - puts.Sum(o => o.Derivative.Variable.Gamma), Component = com }); View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Vega", "CallVega", new AreaShape { Y = calls.Sum(o => o.Derivative.Variable.Vega), Component = comUp }); View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Vega", "PutVega", new AreaShape { Y = -puts.Sum(o => o.Derivative.Variable.Vega), Component = comDown }); View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Theta", "CallTheta", new AreaShape { Y = calls.Sum(o => o.Derivative.Variable.Theta), Component = comUp }); View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Theta", "PutTheta", new AreaShape { Y = -puts.Sum(o => o.Derivative.Variable.Theta), Component = comDown }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Puts", "PutBids", new AreaShape { Y = putBids, Component = comUp }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Puts", "PutAsks", new AreaShape { Y = -putAsks, Component = comDown }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Calls", "CallBids", new AreaShape { Y = callBids, Component = comUp }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Calls", "CallAsks", new AreaShape { Y = -callAsks, Component = comDown }); - View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Ratios", "LongShortRatio", new AreaShape { Y = (callBids - callAsks) - (putBids - putAsks), Component = com }); + View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Puts", "PutBidsAsks", new AreaShape { Y = putBids - putAsks, Component = com }); + View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Calls", "CallBidsAsks", new AreaShape { Y = callBids - callAsks, Component = com }); + View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Ratios", "CallPutDomRatio", new AreaShape { Y = (callBids - callAsks) - (putBids - putAsks), Component = com }); var performance = Performance.Calculate([account]); @@ -239,11 +222,12 @@ public virtual async Task OnUpdate(PointModel point, int days, Action o.Side is OrderSideEnum.Buy)) .Concat(posCalls.Where(o => o.Side is OrderSideEnum.Sell)); - PositionsView.UpdateItems(point.Time.Value.Ticks, "Assets", "BasisDelta", new AreaShape { Y = basisPositions.Sum(GetDelta), Component = comUp }); - PositionsView.UpdateItems(point.Time.Value.Ticks, "Assets", "OptionDelta", new AreaShape { Y = optionPositions.Sum(GetDelta), Component = comDown }); - PositionsView.UpdateItems(point.Time.Value.Ticks, "Volume", "LongShortVolume", new AreaShape { Y = longs.Sum(o => o.Transaction.Instrument.Point.Volume) - shorts.Sum(o => o.Transaction.Instrument.Point.Volume), Component = com }); - PositionsView.UpdateItems(point.Time.Value.Ticks, "Delta", "LongDelta", new AreaShape { Y = longs.Sum(GetDelta), Component = comUp }); - PositionsView.UpdateItems(point.Time.Value.Ticks, "Delta", "ShortDelta", new AreaShape { Y = shorts.Sum(GetDelta), Component = comDown }); + PositionsView.UpdateItems(point.Time.Value.Ticks, "Assets", "BasisDelta", new AreaShape { Y = basisPositions.Sum(TradeService.GetDelta), Component = comUp }); + PositionsView.UpdateItems(point.Time.Value.Ticks, "Assets", "OptionDelta", new AreaShape { Y = optionPositions.Sum(TradeService.GetDelta), Component = comDown }); + PositionsView.UpdateItems(point.Time.Value.Ticks, "Volume", "LongVolume", new AreaShape { Y = longs.Sum(o => o.Transaction.Instrument.Point.Volume), Component = comUp }); + PositionsView.UpdateItems(point.Time.Value.Ticks, "Volume", "ShortVolume", new AreaShape { Y = -shorts.Sum(o => o.Transaction.Instrument.Point.Volume), Component = comDown }); + PositionsView.UpdateItems(point.Time.Value.Ticks, "Delta", "LongDelta", new AreaShape { Y = longs.Sum(TradeService.GetDelta), Component = comUp }); + PositionsView.UpdateItems(point.Time.Value.Ticks, "Delta", "ShortDelta", new AreaShape { Y = shorts.Sum(TradeService.GetDelta), Component = comDown }); PositionsView.UpdateItems(point.Time.Value.Ticks, "Gamma", "LongGamma", new AreaShape { Y = longs.Sum(o => o.Transaction.Instrument.Derivative?.Variable?.Gamma ?? 0), Component = comUp }); PositionsView.UpdateItems(point.Time.Value.Ticks, "Gamma", "ShortGamma", new AreaShape { Y = -shorts.Sum(o => o.Transaction.Instrument.Derivative?.Variable?.Gamma ?? 0), Component = comDown }); PositionsView.UpdateItems(point.Time.Value.Ticks, "Theta", "LongTheta", new AreaShape { Y = -longs.Sum(o => o.Transaction.Instrument.Derivative?.Variable?.Theta ?? 0), Component = comUp }); @@ -289,7 +273,7 @@ protected void ShowEstimates(PointModel point) var chartPoints = minusPercents.Concat(plusPercents).Select((o, i) => { var step = inputModel.Price + inputModel.Price * o; - var sum = GetEstimate(step, point.Time.Value, inputModel); + var sum = TradeService.GetEstimate(step, point.Time.Value, inputModel); var shape = new Shape(); sums[o] = sums.TryGetValue(o, out var s) ? s + sum : sum; @@ -377,463 +361,5 @@ protected void ShowStrikes(PointModel point, IList options) StrikesView.Composers.ForEach(composer => composer.ShowIndex = o => $"{groups.ElementAtOrDefault((int)o)?.Key}"); StrikesView.UpdateOrdinals(chartPoints); } - - /// - /// Estimated PnL for shares or options - /// - /// - /// - /// - protected double GetEstimate(double price, DateTime date, OptionInputModel inputModel) - { - var direction = inputModel.Position is OrderSideEnum.Buy ? 1.0 : -1.0; - - if (inputModel.Side is not OptionSideEnum.Put && inputModel.Side is not OptionSideEnum.Call) - { - return (price - inputModel.Price) * inputModel.Amount * direction; - } - - var optionSide = Enum.GetName(inputModel.Side.GetType(), inputModel.Side); - var days = Math.Max((inputModel.Date - date).Value.TotalDays / 250.0, double.Epsilon); - var estimate = OptionService.Premium(optionSide, price, inputModel.Strike, days, 0.25, 0.05, 0); - - return (estimate - inputModel.Premium) * inputModel.Amount * direction * 100; - } - - /// - /// Get option chain - /// - /// - /// - /// - public virtual async Task> GetOptions(PointModel point, int days = 1) - { - var adapter = View.Adapters["Sim"]; - var account = adapter.Account; - var screener = new OptionScreenerModel - { - Name = Instrument.Name, - MinDate = point.Time, - MaxDate = point.Time.Value.AddDays(days), - Point = point - }; - - var options = await adapter.GetOptions(screener, []); - var nextOptions = options - .Data - .OrderBy(o => o.Derivative.Expiration) - .ThenBy(o => o.Derivative.Strike) - .ThenBy(o => o.Derivative.Side) - .ToList(); - - return nextOptions; - } - - /// - /// Close positions - /// - /// - /// - public async Task ClosePositions(InstrumentEnum? instrumentType = null) - { - var adapter = View.Adapters["Sim"]; - - foreach (var position in adapter.Account.Positions.Values.ToList()) - { - var order = new OrderModel - { - Side = position.Side is OrderSideEnum.Buy ? OrderSideEnum.Sell : OrderSideEnum.Buy, - Type = OrderTypeEnum.Market, - Transaction = new() - { - Volume = position.Transaction.Volume, - Instrument = position.Transaction.Instrument - } - }; - - var insEmpty = instrumentType is null; - var insShares = instrumentType is InstrumentEnum.Shares && position.Transaction.Instrument.Derivative is null; - var insOptions = instrumentType is InstrumentEnum.Options && position.Transaction.Instrument.Derivative is not null; - - if (insEmpty || insShares || insOptions) - { - await adapter.CreateOrders(order); - } - } - } - - /// - /// Create short straddle strategy - /// - /// - /// - /// - public virtual IList GetCondor(PointModel point, IList options) - { - var range = point.Last * 0.01; - var shortPut = options - .Where(o => o.Derivative.Side is OptionSideEnum.Put) - .Where(o => o.Derivative.Strike <= point.Last) - .LastOrDefault(); - - var longPut = options - .Where(o => o.Derivative.Side is OptionSideEnum.Put) - .Where(o => o.Derivative.Strike < shortPut.Derivative.Strike - range) - .LastOrDefault(); - - var shortCall = options - .Where(o => o.Derivative.Side is OptionSideEnum.Call) - .Where(o => o.Derivative.Strike >= point.Last) - .FirstOrDefault(); - - var longCall = options - .Where(o => o.Derivative.Side is OptionSideEnum.Call) - .Where(o => o.Derivative.Strike > shortCall.Derivative.Strike + range) - .FirstOrDefault(); - - var order = new OrderModel - { - Type = OrderTypeEnum.Market, - Instruction = InstructionEnum.Group, - Orders = - [ - new OrderModel - { - Side = OrderSideEnum.Buy, - Instruction = InstructionEnum.Side, - Transaction = new() { Volume = 1, Instrument = longPut } - }, - new OrderModel - { - Side = OrderSideEnum.Buy, - Instruction = InstructionEnum.Side, - Transaction = new() { Volume = 1, Instrument = longCall } - }, - new OrderModel - { - Side = OrderSideEnum.Sell, - Instruction = InstructionEnum.Side, - Transaction = new() { Volume = 1, Instrument = shortPut } - }, - new OrderModel - { - Side = OrderSideEnum.Sell, - Instruction = InstructionEnum.Side, - Transaction = new() { Volume = 1, Instrument = shortCall } - } - ] - }; - - return [order]; - } - - /// - /// Create short straddle strategy - /// - /// - /// - /// - public virtual IList GetShortStraddle(PointModel point, IList options) - { - var shortPut = options - .Where(o => o.Derivative.Side is OptionSideEnum.Put) - .Where(o => o.Derivative.Strike >= point.Last) - .FirstOrDefault(); - - var shortCall = options - .Where(o => o.Derivative.Side is OptionSideEnum.Call) - .Where(o => o.Derivative.Strike >= point.Last) - .FirstOrDefault(); - - var order = new OrderModel - { - Type = OrderTypeEnum.Market, - Instruction = InstructionEnum.Group, - Orders = - [ - new OrderModel - { - Side = OrderSideEnum.Sell, - Instruction = InstructionEnum.Side, - Price = shortPut.Point.Bid, - Transaction = new() { Volume = 1, Instrument = shortPut } - }, - new OrderModel - { - Side = OrderSideEnum.Sell, - Instruction = InstructionEnum.Side, - Price = shortCall.Point.Bid, - Transaction = new() { Volume = 1, Instrument = shortCall } - } - ] - }; - - return [order]; - } - - /// - /// Hedge each delta change with shares - /// - /// - /// - public virtual IList GetShareHedge(PointModel point) - { - var account = View.Adapters["Sim"].Account; - var basisDelta = Math.Round(account - .Positions - .Values - .Where(o => o.Transaction.Instrument.Derivative is null) - .Sum(GetDelta), MidpointRounding.ToZero); - - var optionDelta = Math.Round(account - .Positions - .Values - .Where(o => o.Transaction.Instrument.Derivative is not null) - .Sum(GetDelta), MidpointRounding.ToZero); - - var delta = optionDelta + basisDelta; - - if (Math.Abs(delta) > 0) - { - var order = new OrderModel - { - Type = OrderTypeEnum.Market, - Side = delta < 0 ? OrderSideEnum.Buy : OrderSideEnum.Sell, - Transaction = new() { Volume = Math.Abs(delta), Instrument = point.Instrument } - }; - - return [order]; - } - - return []; - } - - /// - /// Open share position in the direction of option delta - /// - /// - /// - public virtual IList GetShareDirection(PointModel point) - { - var account = View.Adapters["Sim"].Account; - var basisDelta = account - .Positions - .Values - .Where(o => o.Transaction.Instrument.Derivative is null) - .Sum(GetDelta); - - var optionDelta = account - .Positions - .Values - .Where(o => o.Transaction.Instrument.Derivative is not null) - .Sum(GetDelta); - - var isOversold = basisDelta < 0 && optionDelta > 0; - var isOverbought = basisDelta > 0 && optionDelta < 0; - - if (basisDelta is 0 || isOversold || isOverbought) - { - var order = new OrderModel - { - Type = OrderTypeEnum.Market, - Side = optionDelta > 0 ? OrderSideEnum.Buy : OrderSideEnum.Sell, - Transaction = new() { Volume = 100, Instrument = point.Instrument } - }; - - return [order]; - } - - return []; - } - - /// - /// Create credit spread strategy - /// - /// - /// - /// - /// - public virtual IList GetCreditSpread(OptionSideEnum side, PointModel point, IList options) - { - var adapter = View.Adapters["Sim"]; - var account = adapter.Account; - var sideOptions = options.Where(o => Equals(o.Derivative.Side, side)); - var order = new OrderModel - { - Type = OrderTypeEnum.Market, - Orders = - [ - new OrderModel - { - Side = OrderSideEnum.Sell, - Instruction = InstructionEnum.Side, - Transaction = new TransactionModel { Volume = 1 } - }, - new OrderModel - { - Side = OrderSideEnum.Buy, - Instruction = InstructionEnum.Side, - Transaction = new TransactionModel { Volume = 1 } - } - ] - }; - - switch (side) - { - case OptionSideEnum.Put: - - var put = order.Orders[0].Transaction.Instrument = sideOptions - .Where(o => o.Derivative.Strike <= point.Last - point.Last * 0.001) - .LastOrDefault(); - - order.Orders[1].Transaction.Instrument = sideOptions - .Where(o => o.Derivative.Strike <= point.Last - point.Last * 0.005) - .LastOrDefault(); - - break; - - case OptionSideEnum.Call: - - var call = order.Orders[0].Transaction.Instrument = sideOptions - .Where(o => o.Derivative.Strike >= point.Last + point.Last * 0.001) - .FirstOrDefault(); - - order.Orders[1].Transaction.Instrument = sideOptions - .Where(o => o.Derivative.Strike >= point.Last + point.Last * 0.005) - .FirstOrDefault(); - - break; - } - - return [order]; - } - - /// - /// Create debit spread strategy - /// - /// - /// - /// - /// - public virtual IList GetDebigSpread(OptionSideEnum side, PointModel point, IList options) - { - var adapter = View.Adapters["Sim"]; - var account = adapter.Account; - var sideOptions = options.Where(o => Equals(o.Derivative.Side, side)); - var order = new OrderModel - { - Type = OrderTypeEnum.Market, - Orders = - [ - new OrderModel - { - Side = OrderSideEnum.Buy, - Instruction = InstructionEnum.Side, - Transaction = new TransactionModel { Volume = 1 } - }, - new OrderModel - { - Side = OrderSideEnum.Sell, - Instruction = InstructionEnum.Side, - Transaction = new TransactionModel { Volume = 1 } - } - ] - }; - - switch (side) - { - case OptionSideEnum.Put: - - var put = order.Orders[0].Transaction.Instrument = sideOptions - .Where(o => o.Derivative.Strike >= point.Last + point.Last * 0.001) - .FirstOrDefault(); - - order.Orders[1].Transaction.Instrument = sideOptions - .Where(o => o.Derivative.Strike <= point.Last - point.Last * 0.005) - .LastOrDefault(); - - break; - - case OptionSideEnum.Call: - - var call = order.Orders[0].Transaction.Instrument = sideOptions - .Where(o => o.Derivative.Strike <= point.Last - point.Last * 0.001) - .LastOrDefault(); - - order.Orders[1].Transaction.Instrument = sideOptions - .Where(o => o.Derivative.Strike >= point.Last + point.Last * 0.005) - .FirstOrDefault(); - - break; - } - - return [order]; - } - - /// - /// Create PMCC strategy - /// - /// - /// - /// - /// - public virtual IList GetPmCover(OptionSideEnum side, PointModel point, IList options) - { - var adapter = View.Adapters["Sim"]; - var account = adapter.Account; - var sideOptions = options.Where(o => Equals(o.Derivative.Side, side)); - var minDate = options.First().Derivative.Expiration; - var maxDate = options.Last().Derivative.Expiration; - var longOptions = sideOptions.Where(o => o.Derivative.Expiration >= maxDate); - var shortOptions = sideOptions.Where(o => o.Derivative.Expiration <= minDate); - var order = new OrderModel - { - Type = OrderTypeEnum.Market, - Orders = - [ - new OrderModel - { - Side = OrderSideEnum.Buy, - Instruction = InstructionEnum.Side, - Transaction = new TransactionModel { Volume = 2 } - }, - new OrderModel - { - Side = OrderSideEnum.Sell, - Instruction = InstructionEnum.Side, - Transaction = new TransactionModel { Volume = 1 } - } - ] - }; - - switch (side) - { - case OptionSideEnum.Put: - - var put = order.Orders[0].Transaction.Instrument = longOptions - .Where(o => o.Derivative.Strike > point.Last) - .FirstOrDefault(); - - order.Orders[1].Transaction.Instrument = shortOptions - .Where(o => o.Derivative.Strike < point.Last) - .LastOrDefault(); - - break; - - case OptionSideEnum.Call: - - var call = order.Orders[0].Transaction.Instrument = longOptions - .Where(o => o.Derivative.Strike < point.Last) - .LastOrDefault(); - - order.Orders[1].Transaction.Instrument = shortOptions - .Where(o => o.Derivative.Strike > point.Last) - .FirstOrDefault(); - - break; - } - - return [order]; - } } } diff --git a/Terminal/Pages/Options/Condor.razor.cs b/Terminal/Pages/Options/Condor.razor.cs index af8ade8bf0..9f895d7ef3 100644 --- a/Terminal/Pages/Options/Condor.razor.cs +++ b/Terminal/Pages/Options/Condor.razor.cs @@ -3,6 +3,7 @@ using Terminal.Components; using Terminal.Core.Domains; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Options { @@ -45,7 +46,7 @@ await OptionView.OnUpdate(point, 1, async options => { if (account.Orders.Count is 0 && account.Positions.Count is 0) { - var orders = OptionView.GetCondor(point, options); + var orders = TradeService.GetCondor(adapter, point, options); var orderResponse = await adapter.CreateOrders([.. orders]); } }); diff --git a/Terminal/Pages/Options/CoveredDirection.razor.cs b/Terminal/Pages/Options/CoveredDirection.razor.cs index 6caef0319f..d761131da3 100644 --- a/Terminal/Pages/Options/CoveredDirection.razor.cs +++ b/Terminal/Pages/Options/CoveredDirection.razor.cs @@ -4,6 +4,7 @@ using Terminal.Core.Domains; using Terminal.Core.Enums; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Options { @@ -46,7 +47,7 @@ await OptionView.OnUpdate(point, 30, async options => { if (account.Orders.Count is 0 && account.Positions.Count is 0) { - var orders = OptionView.GetPmCover(OptionSideEnum.Call, point, options); + var orders = TradeService.GetPmCover(adapter, point, OptionSideEnum.Call, options); var orderResponse = await adapter.CreateOrders([.. orders]); } }); diff --git a/Terminal/Pages/Options/CreditSpreadReversal.razor.cs b/Terminal/Pages/Options/CreditSpreadReversal.razor.cs index cf6adf45d8..dde4e08407 100644 --- a/Terminal/Pages/Options/CreditSpreadReversal.razor.cs +++ b/Terminal/Pages/Options/CreditSpreadReversal.razor.cs @@ -7,8 +7,10 @@ using System.Threading.Tasks; using Terminal.Components; using Terminal.Core.Domains; +using Terminal.Core.Enums; using Terminal.Core.Indicators; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Options { @@ -68,26 +70,26 @@ await OptionView.OnUpdate(point, 1, async options => if (rsi.Values.Count > rsi.Interval) { - if (rsi.Point.Last < 30 && posSide is not Core.Enums.OptionSideEnum.Put) + if (rsi.Point.Last < 30 && posSide is not OptionSideEnum.Put) { - var orders = OptionView.GetCreditSpread(Core.Enums.OptionSideEnum.Put, point, options); + var orders = TradeService.GetCreditSpread(adapter, point, OptionSideEnum.Put, options); if (orders.Count > 0) { Price = point.Last.Value; - await OptionView.ClosePositions(); + await TradeService.ClosePositions(adapter); await adapter.CreateOrders([.. orders]); } } - if (rsi.Point.Last > 70 && posSide is not Core.Enums.OptionSideEnum.Call) + if (rsi.Point.Last > 70 && posSide is not OptionSideEnum.Call) { - var orders = OptionView.GetCreditSpread(Core.Enums.OptionSideEnum.Call, point, options); + var orders = TradeService.GetCreditSpread(adapter, point, OptionSideEnum.Call, options); if (orders.Count > 0) { Price = -point.Last.Value; - await OptionView.ClosePositions(); + await TradeService.ClosePositions(adapter); await adapter.CreateOrders([.. orders]); } } diff --git a/Terminal/Pages/Options/DebitSpreadReversal.razor.cs b/Terminal/Pages/Options/DebitSpreadReversal.razor.cs index 12c893cb07..207339f6c0 100644 --- a/Terminal/Pages/Options/DebitSpreadReversal.razor.cs +++ b/Terminal/Pages/Options/DebitSpreadReversal.razor.cs @@ -7,8 +7,10 @@ using System.Threading.Tasks; using Terminal.Components; using Terminal.Core.Domains; +using Terminal.Core.Enums; using Terminal.Core.Indicators; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Options { @@ -68,26 +70,26 @@ await OptionView.OnUpdate(point, 1, async options => if (rsi.Values.Count > rsi.Interval) { - if (rsi.Point.Last < 30 && posSide is not Core.Enums.OptionSideEnum.Call) + if (rsi.Point.Last < 30 && posSide is not OptionSideEnum.Call) { - var orders = OptionView.GetDebigSpread(Core.Enums.OptionSideEnum.Call, point, options); + var orders = TradeService.GetDebigSpread(adapter, point, OptionSideEnum.Call, options); if (orders.Count > 0) { Price = point.Last.Value; - await OptionView.ClosePositions(); + await TradeService.ClosePositions(adapter); await adapter.CreateOrders([.. orders]); } } - if (rsi.Point.Last > 70 && posSide is not Core.Enums.OptionSideEnum.Put) + if (rsi.Point.Last > 70 && posSide is not OptionSideEnum.Put) { - var orders = OptionView.GetDebigSpread(Core.Enums.OptionSideEnum.Put, point, options); + var orders = TradeService.GetDebigSpread(adapter, point, OptionSideEnum.Put, options); if (orders.Count > 0) { Price = -point.Last.Value; - await OptionView.ClosePositions(); + await TradeService.ClosePositions(adapter); await adapter.CreateOrders([.. orders]); } } @@ -99,7 +101,7 @@ await OptionView.OnUpdate(point, 1, async options => .Positions .Values .Where(o => o.Transaction.Instrument.Derivative is not null) - .Sum(OptionView.GetDelta), MidpointRounding.ToZero); + .Sum(TradeService.GetDelta), MidpointRounding.ToZero); } OptionView.View.ChartsView.UpdateItems(point.Time.Value.Ticks, "Indicators", "Rsi", new LineShape { Y = rsi.Point.Last }); diff --git a/Terminal/Pages/Options/ShortStraddleDirection.razor.cs b/Terminal/Pages/Options/ShortStraddleDirection.razor.cs index 99469446e7..0f342c641c 100644 --- a/Terminal/Pages/Options/ShortStraddleDirection.razor.cs +++ b/Terminal/Pages/Options/ShortStraddleDirection.razor.cs @@ -3,6 +3,7 @@ using Terminal.Components; using Terminal.Core.Domains; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Options { @@ -45,13 +46,13 @@ await OptionView.OnUpdate(point, 1, async options => { if (account.Orders.Count is 0 && account.Positions.Count is 0) { - var orders = OptionView.GetShortStraddle(point, options); + var orders = TradeService.GetShortStraddle(adapter, point, options); var orderResponse = await adapter.CreateOrders([.. orders]); } if (account.Positions.Count > 0) { - var orders = OptionView.GetShareDirection(point); + var orders = TradeService.GetShareDirection(adapter, point); if (orders.Count > 0) { diff --git a/Terminal/Pages/Options/ShortStraddleHedge.razor.cs b/Terminal/Pages/Options/ShortStraddleHedge.razor.cs index fe58ab706b..a82cce0a7f 100644 --- a/Terminal/Pages/Options/ShortStraddleHedge.razor.cs +++ b/Terminal/Pages/Options/ShortStraddleHedge.razor.cs @@ -3,6 +3,7 @@ using Terminal.Components; using Terminal.Core.Domains; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Options { @@ -45,13 +46,13 @@ await OptionView.OnUpdate(point, 1, async options => { if (account.Orders.Count is 0 && account.Positions.Count is 0) { - var orders = OptionView.GetShortStraddle(point, options); + var orders = TradeService.GetShortStraddle(adapter, point, options); var orderResponse = await adapter.CreateOrders([.. orders]); } if (account.Positions.Count > 0) { - var shareOrders = OptionView.GetShareHedge(point); + var shareOrders = TradeService.GetShareHedge(adapter, point); if (shareOrders.Count > 0) { diff --git a/Terminal/Pages/Options/StrikeFollower.razor.cs b/Terminal/Pages/Options/StrikeFollower.razor.cs index b43ad5d6a1..686f8d85a3 100644 --- a/Terminal/Pages/Options/StrikeFollower.razor.cs +++ b/Terminal/Pages/Options/StrikeFollower.razor.cs @@ -6,6 +6,7 @@ using Terminal.Core.Domains; using Terminal.Core.Enums; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Options { @@ -71,7 +72,7 @@ await OptionView.OnUpdate(point, 1, async options => if (!Equals(nextSide, curSide)) { - await OptionView.ClosePositions(); + await TradeService.ClosePositions(adapter); var orderResponse = await adapter.CreateOrders(order); } } @@ -139,7 +140,7 @@ public virtual OrderModel GetNextOrder(double strike, PointModel point, IList Equals(o.Derivative.Side, side)) .Where(o => o.Derivative.Strike >= point.Last) diff --git a/Terminal/Pages/Shares/Lock.razor.cs b/Terminal/Pages/Shares/Lock.razor.cs index 7a296b32a1..244bf33d11 100644 --- a/Terminal/Pages/Shares/Lock.razor.cs +++ b/Terminal/Pages/Shares/Lock.razor.cs @@ -12,6 +12,7 @@ using Terminal.Core.Enums; using Terminal.Core.Indicators; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Shares { @@ -112,7 +113,7 @@ protected async Task OnData(PointModel point) { if (performance.Point.Last - account.Balance > 5) { - await ClosePositions(); + await TradeService.ClosePositions(adapter); } } @@ -131,7 +132,7 @@ protected async Task OpenPositions(double? amountX, double? amountY) var instrumentX = account.Instruments[_assetX]; var instrumentY = account.Instruments[_assetY]; - await ClosePositions(); + await TradeService.ClosePositions(adapter); await adapter.CreateOrders( [ new OrderModel @@ -148,38 +149,5 @@ await adapter.CreateOrders( } ]); } - - protected async Task ClosePositions() - { - var adapter = View.Adapters["Sim"]; - - foreach (var position in adapter.Account.Positions.ToList()) - { - var side = OrderSideEnum.Buy; - - if (position.Value.Side is OrderSideEnum.Buy) - { - side = OrderSideEnum.Sell; - } - - var order = new OrderModel - { - Side = side, - Type = OrderTypeEnum.Market, - Transaction = new() - { - Volume = position.Value.Transaction.Volume, - Instrument = position.Value.Transaction.Instrument - } - }; - - await adapter.CreateOrders(order); - } - } - - public double GetPriceChange(double? currentPrice, double? percentChange) - { - return (currentPrice * percentChange).Value; - } } } diff --git a/Terminal/Pages/Shares/Pairs.razor.cs b/Terminal/Pages/Shares/Pairs.razor.cs index 01abcd26aa..5e40aabef4 100644 --- a/Terminal/Pages/Shares/Pairs.razor.cs +++ b/Terminal/Pages/Shares/Pairs.razor.cs @@ -12,6 +12,7 @@ using Terminal.Core.Enums; using Terminal.Core.Indicators; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Shares { @@ -109,7 +110,7 @@ protected async Task OnData(PointModel point) switch (true) { - case true when gain > expenses: await ClosePositions(); break; + case true when gain > expenses: await TradeService.ClosePositions(adapter); break; case true when gain < -expenses: OpenPositions(buy.Value.Transaction.Instrument, sell.Value.Transaction.Instrument); break; } } @@ -162,33 +163,5 @@ protected void OpenPositions(InstrumentModel assetBuy, InstrumentModel assetSell adapter.CreateOrders(orderBuy, orderSell); } - - protected async Task ClosePositions() - { - var adapter = View.Adapters["Sim"]; - - foreach (var position in adapter.Account.Positions.ToList()) - { - var side = OrderSideEnum.Buy; - - if (position.Value.Side is OrderSideEnum.Buy) - { - side = OrderSideEnum.Sell; - } - - var order = new OrderModel - { - Side = side, - Type = OrderTypeEnum.Market, - Transaction = new() - { - Volume = position.Value.Transaction.Volume, - Instrument = position.Value.Transaction.Instrument - } - }; - - await adapter.CreateOrders(order); - } - } } } diff --git a/Terminal/Pages/Shares/RangeBar.razor.cs b/Terminal/Pages/Shares/RangeBar.razor.cs index 2785166d9f..4f776dcff7 100644 --- a/Terminal/Pages/Shares/RangeBar.razor.cs +++ b/Terminal/Pages/Shares/RangeBar.razor.cs @@ -1,12 +1,9 @@ -using Canvas.Core.Models; using Canvas.Core.Shapes; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Configuration; using Simulation; -using SkiaSharp; using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Terminal.Components; @@ -15,6 +12,7 @@ using Terminal.Core.Extensions; using Terminal.Core.Indicators; using Terminal.Core.Models; +using Terminal.Services; namespace Terminal.Pages.Shares { @@ -130,7 +128,7 @@ protected async Task OnData(PointModel point) if (position is not null && Equals(side, position.Side) is false) { - await ClosePositions(); + await TradeService.ClosePositions(adapter); } var order = new OrderModel @@ -153,29 +151,5 @@ protected async Task OnData(PointModel point) View.OrdersView.UpdateItems(account.Orders.Values); View.PositionsView.UpdateItems(account.Positions.Values); } - - /// - /// Close all positions - /// - protected async Task ClosePositions() - { - var adapter = View.Adapters["Sim"]; - - foreach (var position in adapter.Account.Positions.Values.ToList()) - { - var order = new OrderModel - { - Side = position.Side is OrderSideEnum.Buy ? OrderSideEnum.Sell : OrderSideEnum.Buy, - Type = OrderTypeEnum.Market, - Transaction = new() - { - Volume = position.Transaction.Volume, - Instrument = position.Transaction.Instrument - } - }; - - await adapter.CreateOrders(order); - } - } } } diff --git a/Terminal/Services/TradeService.cs b/Terminal/Services/TradeService.cs new file mode 100644 index 0000000000..482b6f919f --- /dev/null +++ b/Terminal/Services/TradeService.cs @@ -0,0 +1,488 @@ +using Estimator.Services; +using MudBlazor; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Terminal.Core.Domains; +using Terminal.Core.Enums; +using Terminal.Core.Models; +using Terminal.Models; + +namespace Terminal.Services +{ + public class TradeService + { + /// + /// Get position delta + /// + /// + /// + public static double GetDelta(OrderModel o) + { + var volume = o.Transaction?.Volume; + var leverage = o.Transaction?.Instrument?.Leverage; + var delta = o.Transaction?.Instrument?.Derivative?.Variable?.Delta; + var side = o.Side is OrderSideEnum.Buy ? 1.0 : -1.0; + + return ((delta ?? volume) * leverage * side) ?? 0; + } + + /// + /// Estimated PnL for shares or options + /// + /// + /// + /// + public static double GetEstimate(double price, DateTime date, OptionInputModel inputModel) + { + var direction = inputModel.Position is OrderSideEnum.Buy ? 1.0 : -1.0; + + if (inputModel.Side is not OptionSideEnum.Put && inputModel.Side is not OptionSideEnum.Call) + { + return (price - inputModel.Price) * inputModel.Amount * direction; + } + + var optionSide = Enum.GetName(inputModel.Side.GetType(), inputModel.Side); + var days = Math.Max((inputModel.Date - date).Value.TotalDays / 250.0, double.Epsilon); + var estimate = OptionService.Premium(optionSide, price, inputModel.Strike, days, 0.25, 0.05, 0); + + return (estimate - inputModel.Premium) * inputModel.Amount * direction * 100; + } + + /// + /// Get option chain + /// + /// + /// + /// + public static async Task> GetOptions(IGateway adapter, PointModel point, int days = 1) + { + var account = adapter.Account; + var screener = new OptionScreenerModel + { + Name = point.Instrument.Name, + MinDate = point.Time, + MaxDate = point.Time.Value.AddDays(days), + Point = point + }; + + var options = await adapter.GetOptions(screener, []); + var nextOptions = options + .Data + .OrderBy(o => o.Derivative.Expiration) + .ThenBy(o => o.Derivative.Strike) + .ThenBy(o => o.Derivative.Side) + .ToList(); + + return nextOptions; + } + + /// + /// Close positions + /// + /// + /// + public static async Task ClosePositions(IGateway adapter, InstrumentEnum? instrumentType = null) + { + foreach (var position in adapter.Account.Positions.Values.ToList()) + { + var order = new OrderModel + { + Side = position.Side is OrderSideEnum.Buy ? OrderSideEnum.Sell : OrderSideEnum.Buy, + Type = OrderTypeEnum.Market, + Transaction = new() + { + Volume = position.Transaction.Volume, + Instrument = position.Transaction.Instrument + } + }; + + var insEmpty = instrumentType is null; + var insShares = instrumentType is InstrumentEnum.Shares && position.Transaction.Instrument.Derivative is null; + var insOptions = instrumentType is InstrumentEnum.Options && position.Transaction.Instrument.Derivative is not null; + + if (insEmpty || insShares || insOptions) + { + await adapter.CreateOrders(order); + } + } + } + + /// + /// Create short straddle strategy + /// + /// + /// + /// + public static IList GetCondor(IGateway adapter, PointModel point, IList options) + { + var range = point.Last * 0.01; + var shortPut = options + .Where(o => o.Derivative.Side is OptionSideEnum.Put) + .Where(o => o.Derivative.Strike <= point.Last) + .LastOrDefault(); + + var longPut = options + .Where(o => o.Derivative.Side is OptionSideEnum.Put) + .Where(o => o.Derivative.Strike < shortPut.Derivative.Strike - range) + .LastOrDefault(); + + var shortCall = options + .Where(o => o.Derivative.Side is OptionSideEnum.Call) + .Where(o => o.Derivative.Strike >= point.Last) + .FirstOrDefault(); + + var longCall = options + .Where(o => o.Derivative.Side is OptionSideEnum.Call) + .Where(o => o.Derivative.Strike > shortCall.Derivative.Strike + range) + .FirstOrDefault(); + + var order = new OrderModel + { + Type = OrderTypeEnum.Market, + Instruction = InstructionEnum.Group, + Orders = + [ + new OrderModel + { + Side = OrderSideEnum.Buy, + Instruction = InstructionEnum.Side, + Transaction = new() { Volume = 1, Instrument = longPut } + }, + new OrderModel + { + Side = OrderSideEnum.Buy, + Instruction = InstructionEnum.Side, + Transaction = new() { Volume = 1, Instrument = longCall } + }, + new OrderModel + { + Side = OrderSideEnum.Sell, + Instruction = InstructionEnum.Side, + Transaction = new() { Volume = 1, Instrument = shortPut } + }, + new OrderModel + { + Side = OrderSideEnum.Sell, + Instruction = InstructionEnum.Side, + Transaction = new() { Volume = 1, Instrument = shortCall } + } + ] + }; + + return [order]; + } + + /// + /// Create short straddle strategy + /// + /// + /// + /// + /// + public static IList GetShortStraddle(IGateway adapter, PointModel point, IList options) + { + var shortPut = options + .Where(o => o.Derivative.Side is OptionSideEnum.Put) + .Where(o => o.Derivative.Strike >= point.Last) + .FirstOrDefault(); + + var shortCall = options + .Where(o => o.Derivative.Side is OptionSideEnum.Call) + .Where(o => o.Derivative.Strike >= point.Last) + .FirstOrDefault(); + + var order = new OrderModel + { + Type = OrderTypeEnum.Market, + Instruction = InstructionEnum.Group, + Orders = + [ + new OrderModel + { + Side = OrderSideEnum.Sell, + Instruction = InstructionEnum.Side, + Price = shortPut.Point.Bid, + Transaction = new() { Volume = 1, Instrument = shortPut } + }, + new OrderModel + { + Side = OrderSideEnum.Sell, + Instruction = InstructionEnum.Side, + Price = shortCall.Point.Bid, + Transaction = new() { Volume = 1, Instrument = shortCall } + } + ] + }; + + return [order]; + } + + /// + /// Hedge each delta change with shares + /// + /// + /// + /// + public static IList GetShareHedge(IGateway adapter, PointModel point) + { + var account = adapter.Account; + var basisDelta = Math.Round(account + .Positions + .Values + .Where(o => o.Transaction.Instrument.Derivative is null) + .Sum(GetDelta), MidpointRounding.ToZero); + + var optionDelta = Math.Round(account + .Positions + .Values + .Where(o => o.Transaction.Instrument.Derivative is not null) + .Sum(GetDelta), MidpointRounding.ToZero); + + var delta = optionDelta + basisDelta; + + if (Math.Abs(delta) > 0) + { + var order = new OrderModel + { + Type = OrderTypeEnum.Market, + Side = delta < 0 ? OrderSideEnum.Buy : OrderSideEnum.Sell, + Transaction = new() { Volume = Math.Abs(delta), Instrument = point.Instrument } + }; + + return [order]; + } + + return []; + } + + /// + /// Open share position in the direction of option delta + /// + /// + /// + public static IList GetShareDirection(IGateway adapter, PointModel point) + { + var account = adapter.Account; + var basisDelta = account + .Positions + .Values + .Where(o => o.Transaction.Instrument.Derivative is null) + .Sum(GetDelta); + + var optionDelta = account + .Positions + .Values + .Where(o => o.Transaction.Instrument.Derivative is not null) + .Sum(GetDelta); + + var isOversold = basisDelta < 0 && optionDelta > 0; + var isOverbought = basisDelta > 0 && optionDelta < 0; + + if (basisDelta is 0 || isOversold || isOverbought) + { + var order = new OrderModel + { + Type = OrderTypeEnum.Market, + Side = optionDelta > 0 ? OrderSideEnum.Buy : OrderSideEnum.Sell, + Transaction = new() { Volume = 100, Instrument = point.Instrument } + }; + + return [order]; + } + + return []; + } + + /// + /// Create credit spread strategy + /// + /// + /// + /// + /// + /// + public static IList GetCreditSpread(IGateway adapter, PointModel point, OptionSideEnum side, IList options) + { + var account = adapter.Account; + var sideOptions = options.Where(o => Equals(o.Derivative.Side, side)); + var order = new OrderModel + { + Type = OrderTypeEnum.Market, + Orders = + [ + new OrderModel + { + Side = OrderSideEnum.Sell, + Instruction = InstructionEnum.Side, + Transaction = new TransactionModel { Volume = 1 } + }, + new OrderModel + { + Side = OrderSideEnum.Buy, + Instruction = InstructionEnum.Side, + Transaction = new TransactionModel { Volume = 1 } + } + ] + }; + + switch (side) + { + case OptionSideEnum.Put: + + var put = order.Orders[0].Transaction.Instrument = sideOptions + .Where(o => o.Derivative.Strike <= point.Last - point.Last * 0.001) + .LastOrDefault(); + + order.Orders[1].Transaction.Instrument = sideOptions + .Where(o => o.Derivative.Strike <= point.Last - point.Last * 0.005) + .LastOrDefault(); + + break; + + case OptionSideEnum.Call: + + var call = order.Orders[0].Transaction.Instrument = sideOptions + .Where(o => o.Derivative.Strike >= point.Last + point.Last * 0.001) + .FirstOrDefault(); + + order.Orders[1].Transaction.Instrument = sideOptions + .Where(o => o.Derivative.Strike >= point.Last + point.Last * 0.005) + .FirstOrDefault(); + + break; + } + + return [order]; + } + + /// + /// Create debit spread strategy + /// + /// + /// + /// + /// + /// + public static IList GetDebigSpread(IGateway adapter, PointModel point, OptionSideEnum side, IList options) + { + var account = adapter.Account; + var sideOptions = options.Where(o => Equals(o.Derivative.Side, side)); + var order = new OrderModel + { + Type = OrderTypeEnum.Market, + Orders = + [ + new OrderModel + { + Side = OrderSideEnum.Buy, + Instruction = InstructionEnum.Side, + Transaction = new TransactionModel { Volume = 1 } + }, + new OrderModel + { + Side = OrderSideEnum.Sell, + Instruction = InstructionEnum.Side, + Transaction = new TransactionModel { Volume = 1 } + } + ] + }; + + switch (side) + { + case OptionSideEnum.Put: + + var put = order.Orders[0].Transaction.Instrument = sideOptions + .Where(o => o.Derivative.Strike >= point.Last + point.Last * 0.001) + .FirstOrDefault(); + + order.Orders[1].Transaction.Instrument = sideOptions + .Where(o => o.Derivative.Strike <= point.Last - point.Last * 0.005) + .LastOrDefault(); + + break; + + case OptionSideEnum.Call: + + var call = order.Orders[0].Transaction.Instrument = sideOptions + .Where(o => o.Derivative.Strike <= point.Last - point.Last * 0.001) + .LastOrDefault(); + + order.Orders[1].Transaction.Instrument = sideOptions + .Where(o => o.Derivative.Strike >= point.Last + point.Last * 0.005) + .FirstOrDefault(); + + break; + } + + return [order]; + } + + /// + /// Create PMCC strategy + /// + /// + /// + /// + /// + /// + public static IList GetPmCover(IGateway adapter, PointModel point, OptionSideEnum side, IList options) + { + var account = adapter.Account; + var sideOptions = options.Where(o => Equals(o.Derivative.Side, side)); + var minDate = options.First().Derivative.Expiration; + var maxDate = options.Last().Derivative.Expiration; + var longOptions = sideOptions.Where(o => o.Derivative.Expiration >= maxDate); + var shortOptions = sideOptions.Where(o => o.Derivative.Expiration <= minDate); + var order = new OrderModel + { + Type = OrderTypeEnum.Market, + Orders = + [ + new OrderModel + { + Side = OrderSideEnum.Buy, + Instruction = InstructionEnum.Side, + Transaction = new TransactionModel { Volume = 2 } + }, + new OrderModel + { + Side = OrderSideEnum.Sell, + Instruction = InstructionEnum.Side, + Transaction = new TransactionModel { Volume = 1 } + } + ] + }; + + switch (side) + { + case OptionSideEnum.Put: + + var put = order.Orders[0].Transaction.Instrument = longOptions + .Where(o => o.Derivative.Strike > point.Last) + .FirstOrDefault(); + + order.Orders[1].Transaction.Instrument = shortOptions + .Where(o => o.Derivative.Strike < point.Last) + .LastOrDefault(); + + break; + + case OptionSideEnum.Call: + + var call = order.Orders[0].Transaction.Instrument = longOptions + .Where(o => o.Derivative.Strike < point.Last) + .LastOrDefault(); + + order.Orders[1].Transaction.Instrument = shortOptions + .Where(o => o.Derivative.Strike > point.Last) + .FirstOrDefault(); + + break; + } + + return [order]; + } + } +} diff --git a/Terminal/_Imports.razor b/Terminal/_Imports.razor index c21bc6bc4a..7431041824 100644 --- a/Terminal/_Imports.razor +++ b/Terminal/_Imports.razor @@ -12,5 +12,6 @@ @using Terminal @using Terminal.Themes @using Terminal.Records +@using Terminal.Services @using Terminal.Templates @using Terminal.Components