diff --git a/src/Notebook.Sample/notebook.ipynb b/src/Notebook.Sample/notebook.ipynb index 03241bb..96c51c8 100644 --- a/src/Notebook.Sample/notebook.ipynb +++ b/src/Notebook.Sample/notebook.ipynb @@ -69,9 +69,7 @@ "{\n", " \"ClientId\": \"your app client id\",\n", " \"Secret\": \"your app secret\",\n", - " \"HostType\": \"your account type (demo or live)\",\n", " \"AccessToken\": \"your access token, create one by using your app playground on open API site\",\n", - " \"AccountId\": \"Your account ID as a number without quotes\"\n", "}\n", "```" ] @@ -97,7 +95,7 @@ "using System.Text.Json.Serialization;\n", "using System.IO;\n", "\n", - "public record Credentials(string ClientId, string Secret, string HostType, string AccessToken, long AccountId);\n", + "public record Credentials(string ClientId, string Secret, string AccessToken);\n", "\n", "private async Task GetCredentials()\n", "{\n", @@ -114,7 +112,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We got the API credentials, now lets create an OpenAPI.NET client, and authorize our app and account:" + "We got the API credentials, now lets create a method for getting OpenAPI.NET client, this client will first autorize our API application and then trading account:" ] }, { @@ -130,45 +128,390 @@ "using System.Reactive.Linq;\n", "using Google.Protobuf;\n", "\n", - "var host = credentials.HostType.Equals(\"demo\", StringComparison.OrdinalIgnoreCase) ? ApiInfo.DemoHost : ApiInfo.LiveHost;\n", + "private async Task GetClient(Mode mode)\n", + "{\n", + " var host = ApiInfo.GetHost(mode);\n", + "\n", + " var client = new OpenClient(host, ApiInfo.Port, TimeSpan.FromSeconds(15));\n", + "\n", + " client.Subscribe(_ => {}, exception => Console.WriteLine($\"Exception: {exception}\"));\n", + " client.OfType().Subscribe(error => Console.WriteLine($\"Exception: {error}\"));\n", + "\n", + " bool isAppAuthroized = false;\n", + "\n", + " void OnAppAuthorized(ProtoOAApplicationAuthRes response) => isAppAuthroized = true;\n", + " \n", + " client.OfType().Subscribe(OnAppAuthorized);\n", + " \n", + " await client.Connect();\n", + " \n", + " var applicationAuthReq = new ProtoOAApplicationAuthReq\n", + " {\n", + " ClientId = credentials.ClientId,\n", + " ClientSecret = credentials.Secret,\n", + " };\n", + " \n", + " await client.SendMessage(applicationAuthReq, applicationAuthReq.PayloadType);\n", "\n", - "var client = new OpenClient(host, ApiInfo.Port, TimeSpan.FromSeconds(15));\n", + " var waitStartTime = DateTime.Now;\n", + "\n", + " while (isAppAuthroized is false && DateTime.Now - waitStartTime < TimeSpan.FromSeconds(2))\n", + " {\n", + " await Task.Delay(100);\n", + " }\n", + "\n", + " if (isAppAuthroized is false) throw new InvalidOperationException(\"Account authorization failed\");\n", + "\n", + " return client;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "source": [ + "After create a method to an API client we need to get all the authorized accounts for your access token:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "outputs": [], + "source": [ + "// Please set one of the authorized trading accounts number\n", + "// We will use it for future requests\n", + "const long AccountNumber = 3397885;\n", "\n", - "client.Where(iMessage => iMessage is not ProtoHeartbeatEvent).Subscribe(message => Console.WriteLine($\"Message Received: {message}\"), exception => Console.WriteLine($\"Exception: {exception}\"));\n", + "if (AccountNumber == 0) throw new InvalidOperationException(\"Please first set the account number\");\n", "\n", - "private async void OnAppAuthorized(ProtoOAApplicationAuthRes response)\n", + "private async Task GetAccounts()\n", "{\n", - " var accountAuthReq = new ProtoOAAccountAuthReq\n", + " var request = new ProtoOAGetAccountListByAccessTokenReq()\n", " {\n", - " CtidTraderAccountId = credentials.AccountId,\n", " AccessToken = credentials.AccessToken\n", " };\n", "\n", - " client.OfType().Subscribe(OnAccountAuthRes);\n", + " ProtoOAGetAccountListByAccessTokenRes result = null;\n", + "\n", + " void OnResponse(ProtoOAGetAccountListByAccessTokenRes response) => result = response;\n", + "\n", + " using var client = await GetClient(Mode.Live);\n", "\n", - " await client.SendMessage(accountAuthReq, accountAuthReq.PayloadType);\n", + " client.OfType().Subscribe(OnResponse);\n", + "\n", + " await client.SendMessage(request, request.PayloadType);\n", + "\n", + " await Task.Delay(1000);\n", + "\n", + " if (result is null) throw new InvalidOperationException($\"Couldn't get response\");\n", + "\n", + " return result;\n", "}\n", "\n", + "var accounts = await GetAccounts();\n", + "\n", + "var account = accounts.CtidTraderAccount.First(account => account.TraderLogin == AccountNumber);\n", + "\n", + "Console.WriteLine($\"Account ID is: {account.CtidTraderAccountId}\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use an account you have to first authroize it, otherwise you will not be able to send any request related to that account, let's create method for authorized an account: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "outputs": [], + "source": [ + "private async Task AuthorizeAccount(OpenClient client, long accountId)\n", + "{\n", + " var request = new ProtoOAAccountAuthReq()\n", + " {\n", + " AccessToken = credentials.AccessToken,\n", + " CtidTraderAccountId = accountId\n", + " };\n", + "\n", + " ProtoOAAccountAuthRes result = null;\n", + "\n", + " void OnResponse(ProtoOAAccountAuthRes response) => result = response;\n", + " \n", + " client.OfType().Subscribe(OnResponse);\n", + "\n", + " await client.SendMessage(request, request.PayloadType);\n", + "\n", + " await Task.Delay(500);\n", + "\n", + " if (result is null) throw new InvalidOperationException($\"Couldn't get response\");\n", + "\n", + " return result;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "source": [ + "We will use the AuthorizeAccount whenever we created a new client after application authorization." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now our account and API application is authorized and we can start interacting with the API, let's first create a method for getting a symbol data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "outputs": [], + "source": [ + "// Be sure your trading account has a symbol with this name,\n", + "// if not change it to another symbol name\n", + "const string SymbolName = \"EURUSD\";\n", "\n", - "private void OnAccountAuthRes(ProtoOAAccountAuthRes response)\n", + "private async Task GetSymbol(string symbolName)\n", "{\n", - " Console.WriteLine(\"OnAccountAuthRes\");\n", + " var request = new ProtoOASymbolsListReq\n", + " {\n", + " CtidTraderAccountId = (long)account.CtidTraderAccountId,\n", + " };\n", + "\n", + " ProtoOALightSymbol result = null;\n", + "\n", + " void OnResponse(ProtoOASymbolsListRes response)\n", + " {\n", + " result = response.Symbol.FirstOrDefault(symbol => symbol.SymbolName.Equals(SymbolName, StringComparison.OrdinalIgnoreCase));\n", + " }\n", + "\n", + " using var client = await GetClient(account.IsLive ? Mode.Live : Mode.Demo);\n", + "\n", + " await AuthorizeAccount(client, accountId);\n", + "\n", + " client.OfType().Subscribe(OnResponse);\n", + "\n", + " await client.SendMessage(request, request.PayloadType);\n", + "\n", + " await Task.Delay(1000);\n", + "\n", + " if (result is null) throw new InvalidOperationException($\"Couldn't get/find the symbol {SymbolName}\");\n", + "\n", + " return result;\n", "}\n", "\n", - "client.OfType().Subscribe(OnAppAuthorized);\n", + "var symbol = await GetSymbol(SymbolName);\n", + "Console.WriteLine(symbol);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we will get the above symbol daily trend bars or OHLC price data data from API:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "outputs": [], + "source": [ + "private async Task GetTrendbars(long symbolId, DateTimeOffset from, DateTimeOffset to)\n", + "{\n", + " var request = new ProtoOAGetTrendbarsReq()\n", + " {\n", + " Period = ProtoOATrendbarPeriod.D1,\n", + " CtidTraderAccountId = (long)account.CtidTraderAccountId,\n", + " SymbolId = symbolId,\n", + " FromTimestamp = from.ToUnixTimeMilliseconds(),\n", + " ToTimestamp = to.ToUnixTimeMilliseconds()\n", + " };\n", + "\n", + " ProtoOAGetTrendbarsRes result = null;\n", + "\n", + " void OnResponse(ProtoOAGetTrendbarsRes response) => result = response;\n", + "\n", + " using var client = await GetClient(account.IsLive ? Mode.Live : Mode.Demo);\n", + "\n", + " await AuthorizeAccount(client, accountId);\n", "\n", - "await client.Connect();\n", + " client.OfType().Subscribe(OnResponse);\n", "\n", - "var applicationAuthReq = new ProtoOAApplicationAuthReq\n", + " await client.SendMessage(request, request.PayloadType);\n", + "\n", + " await Task.Delay(1000);\n", + "\n", + " if (result is null) throw new InvalidOperationException($\"Couldn't get response\");\n", + "\n", + " return result;\n", + "}\n", + "\n", + "// We get one year of daily bars\n", + "var trendbars = await GetTrendbars(symbol.SymbolId, DateTimeOffset.UtcNow.AddYears(-1), DateTimeOffset.UtcNow);\n", + "\n", + "Console.WriteLine(trendbars)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What about getting account open positions and orders? let's do it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "outputs": [], + "source": [ + "private async Task GetOrdersAndPositions()\n", "{\n", - " ClientId = credentials.ClientId,\n", - " ClientSecret = credentials.Secret,\n", - "};\n", + " var request = new ProtoOAReconcileReq()\n", + " {\n", + " CtidTraderAccountId = (long)account.CtidTraderAccountId,\n", + " };\n", + "\n", + " ProtoOAReconcileRes result = null;\n", + "\n", + " void OnResponse(ProtoOAReconcileRes response) => result = response;\n", "\n", - "await client.SendMessage(applicationAuthReq, applicationAuthReq.PayloadType);\n", + " using var client = await GetClient(account.IsLive ? Mode.Live : Mode.Demo);\n", + "\n", + " await AuthorizeAccount(client, accountId);\n", + "\n", + " client.OfType().Subscribe(OnResponse);\n", + "\n", + " await client.SendMessage(request, request.PayloadType);\n", + "\n", + " await Task.Delay(1000);\n", + "\n", + " if (result is null) throw new InvalidOperationException($\"Couldn't get response\");\n", + "\n", + " return result;\n", + "}\n", + "\n", + "var ordersAndPositions = await GetOrdersAndPositions();\n", + "\n", + "Console.WriteLine(ordersAndPositions)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please don't use ProtoOAReconcileReq every time you need account open orders/positions, instead use the ExecutionEvent and use ProtoOAReconcileReq only once for getting the already open orders/positions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All of the above methods return data from account, but they don't perform any trading action, let's do some trading, we will create a market order, a limit order, and a stop order:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "outputs": [], + "source": [ + "public async Task CreateNewOrder(ProtoOAOrderType orderType, long volume, ProtoOATradeSide tradeSide, double limitPrice = 0, double stopPrice = 0)\n", + "{\n", + " var request = new ProtoOANewOrderReq\n", + " {\n", + " CtidTraderAccountId = (long)account.CtidTraderAccountId,\n", + " SymbolId = symbol.SymbolId,\n", + " Volume = volume,\n", + " TradeSide = tradeSide,\n", + " OrderType = orderType\n", + " };\n", + "\n", + " if (request.OrderType == ProtoOAOrderType.Limit)\n", + " {\n", + " request.LimitPrice = limitPrice;\n", + " }\n", + "\n", + " if (request.OrderType == ProtoOAOrderType.Stop)\n", + " {\n", + " request.StopPrice = stopPrice;\n", + " }\n", + "\n", + " using var client = await GetClient(account.IsLive ? Mode.Live : Mode.Demo);\n", + "\n", + " await AuthorizeAccount(client, accountId);\n", + "\n", + " await client.SendMessage(request, request.PayloadType);\n", + "\n", + " await Task.Delay(1000);\n", + "}\n", + "\n", + "// First market order, change the volume if it doesn't match symbol min volume\n", + "// Volume is in units * 100, so 1000 units equal 100000\n", + "await CreateNewOrder(ProtoOAOrderType.Market, 100000, ProtoOATradeSide.Buy);\n", + "// Limit order, change the price based on current symbol price\n", + "await CreateNewOrder(ProtoOAOrderType.Limit, 100000, ProtoOATradeSide.Buy, limitPrice: 1.5);\n", + "// Stop order, change the price based on current symbol price\n", + "await CreateNewOrder(ProtoOAOrderType.Stop, 100000, ProtoOATradeSide.Buy, stopPrice: 1.5);\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "dotnet_interactive": { + "language": "csharp" + } + }, + "source": [ + "Please check the ProtoOANewOrderReq documentation for setting other order properites like stop loss, take profit, stop limit orders, market range orders, etc." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This was our .NET notebook sample, please check our other samples for more future rich apps like our Blazor web assembly sample.\n", "\n", - "// We will wait 10 seconds to receive the responses\n", - "await Task.Delay(10000);" + "We love to hear from you and see you contribute to OpenAPI.NET repository." ] } ],