Author |
Topic: GFAPI Socket Error after 2 minutes (14 messages, Page 1 of 1) |
||||
---|---|---|---|---|---|
Moderators: VPfau | |||||
TLau471 Posts: 70 Joined: |
Hi,
1. My app instantiates the GFAPI cilent and starts the GFClientRunner 2. After connecting and logging in, it takes 2 minutes to perform some non-GFAPI activities 3. After the 2 mins, it calls client.Contracts.Lookup.ByCriteria(), but that results in a Disconnected being fired with "Socket Error" 4. It tries to reconnect, and Disconnected fires again with "ConnectionError" 5. The cycle repeats between #3 and #4 6. If I skip the 2 minute activity, everything works fine. I need the reconnection to work. What am I doing wrong here? Thank you. TLau
|
||||
SRuscak Posts: 50 Joined: Aug 24, 2017 |
Hello.
The API maintains connection to the servers with a heartbeat. To keep the connection alive, you need to call gfClient.Threading.Advance() regularly, so if you need to do a 2 minute operation it will have to be on a different thread. There is more information about it here: https://gainfutures.com/GFAPI/html/fc0637f9-3fd0-407e-ad3a-51e79c4014ed.htm https://gainfutures.com/GFAPI/ |
||||
TLau471 Posts: 70 Joined: |
Hi,
My question is, how do I perform a re-connection successfully? That seems impossible at the moment. Side note, I already started GFClientRunner in #1, why do I still need to call gfClient.Threading.Advance()? TLau Edited by TLau471 on Mar 05, 2020 08:59 AM |
||||
SRuscak Posts: 50 Joined: Aug 24, 2017 |
To be precise, there isn't really any 're-connection'. There is only connection. Every time you connect, the API is initialized from the server in the same way and ends up in the same state. There's nothing different about connecting for the first time versus the second or nth time. So it sounds like the issue is that the connection was dropped at all.
GFClientRunner is responsible for calling Advance, so if you're using it you shouldn't have to call Advance yourself. Is your 2-minute process happening in the login event handler? API events are raised on the API thread, so you'll miss the heartbeat window if the handler doesn't return soon enough. Are you signed up for gfClient.Logging.ErrorOccurred? Just in case there's something revealing there. |
||||
TLau471 Posts: 70 Joined: |
Ok, I think the term re-connection is just semantics.
To be explicit, there are 2 problems here, A. Why does the API disconnect after 2 mins of external activities B. How should we deal with disconnections 1. Yes, the 2 mins is happening inside the OnLoginComplete handler 2. So, this is probably the reason why I'm getting the Disconnected event Now, more importantly, even after removing the 2 mins from the handler and solving problem A, I still need to deal with problem B since disruptions can and will happen. 3. After getting Disconnected event, does the GFAPI re-establish the connection by itself? 4. If it does not, what should I do about it? 5. Currently, once I call the code to connect again, it triggers more Disconnected events. 6. I'm signed up for gfClient.Logging.ErrorOccurred, but nothing popping up there 7. I have described in detail the events that are being triggered in my OP 8. Able to check the API server for logs on what I might be doing wrong? Thank you. TLau Edited by TLau471 on Mar 05, 2020 11:32 AM |
||||
SRuscak Posts: 50 Joined: Aug 24, 2017 |
3. No, the client is responsible for reconnection.
4. Just connect like you did the first time. There shouldn't be any concerns to worry about here. 5. I don't know why it would do that. 8. Not much to say here. It looks like you successfully log on, you get a symbol lookup response, and then your host terminates the connection. I notice that you are using an old version of the API. While it is supposed to be backwards compatible, you could try updating. |
||||
SRuscak Posts: 50 Joined: Aug 24, 2017 |
My bad, 4.0.3.16 is the latest released right now. I'm going to try to replicate your issue.
|
||||
SRuscak Posts: 50 Joined: Aug 24, 2017 |
It looks ok from my side. I can connect and disconnect and connect again. I can do the same symbol lookup as you did, but without an error.
Perhaps there is an error in your SymbolLookupReceived handler? Or some further issue with your threading model? |
||||
TLau471 Posts: 70 Joined: |
I managed to replicate the issue in this sample code from the docs
--- Imports System Imports System.Collections.Generic Imports System.Linq Imports GF.Api Imports GF.Api.Accounts Imports GF.Api.Connection Imports GF.Api.Contracts Imports GF.Api.Contracts.Lookup Imports GF.Api.Contracts.Lookup.Request Imports GF.Api.Messaging.Chat Imports GF.Api.Messaging.Notifications Imports GF.Api.Orders Imports GF.Api.Orders.Drafts Imports GF.Api.Orders.Drafts.Validation Imports GF.Api.Positions Imports GF.Api.Threading Imports GF.Api.Utils Imports GF.Api.Values.Contracts.Lookup Imports GF.Api.Values.Orders Namespace ConsoleSample Friend Class Program Private Shared _frontEsSymbolLookupRequestID As SymbolLookupRequestID Private Shared _statusTimer As System.Timers.Timer Public Shared Sub Main(ByVal args As String()) Console.WriteLine("Hello OEC!") Dim client As IGFClient = GF.Api.Impl.GFApi.CreateClient() Dim runner = New GFClientRunner(client) runner.Start() AddHandler client.Connection.Aggregate.LoginCompleted, AddressOf GFClient_OnLoginCompleted AddHandler client.Connection.Aggregate.LoginFailed, AddressOf GFClient_OnLoginFailed AddHandler client.Connection.Aggregate.Disconnected, AddressOf GFClient_OnDisconnected AddHandler client.Contracts.Lookup.SymbolLookupReceived, AddressOf GFClient_OnSymbolLookupReceived AddHandler client.Subscriptions.Price.PriceTick, AddressOf GFClient_OnPriceTick AddHandler client.Orders.OrderStateChanged, AddressOf GFClient_OnOrderStateChanged AddHandler client.Orders.OrderConfirmed, AddressOf GFClient_OnOrderConfirmed AddHandler client.Orders.OrderFilled, AddressOf GFClient_OnOrderFilled AddHandler client.Accounts.AvgPositionChanged, AddressOf GFClient_OnAvgPositionChanged AddHandler client.Accounts.AccountSummaryChanged, AddressOf GFClient_OnAccountSummaryChanged AddHandler client.Accounts.BalanceChanged, AddressOf GFClient_OnBalanceChanged AddHandler client.Messaging.Notifications.NotificationMessageReceived, AddressOf GFClient_OnNotificationMessageReceived AddHandler client.Messaging.Chat.ChatMessageReceived, AddressOf GFClient_OnChatMessageReceived AddHandler client.Logging.ErrorOccurred, AddressOf GFClient_OnErrorOccurred Dim username As String Dim password As String Dim uuid As String Do client.Connection.Aggregate.Connect(New ConnectionContextBuilder(). WithUserName(username). WithPassword(password). WithPort(9210).WithHost("api.gainfutures.com"). WithUUID(uuid). WithForceLogin(True).Build()) Console.WriteLine("Connecting...") _statusTimer = New System.Timers.Timer With { .Interval = TimeSpan.FromSeconds(10).TotalMilliseconds } AddHandler _statusTimer.Elapsed, Sub(__, ___) StatusTimer_Tick(client) _statusTimer.Start() Dim cki As ConsoleKeyInfo cki = Console.ReadKey() client.Connection.Aggregate.Disconnect() _statusTimer.[Stop]() Select Case cki.Key Case ConsoleKey.X Exit Do End Select Loop runner.[Stop]() End Sub Private Shared Sub GFClient_OnChatMessageReceived(ByVal client As IGFClient, ByVal e As ChatMessageEventArgs) Console.WriteLine($"User Message. {e.ChatMessage.FromUser.Name} - {e.ChatMessage.Message}") client.Messaging.Chat.SendMessage(e.ChatMessage.FromUser.ID, "Hey, I'm just a robot!") End Sub Private Shared Sub GFClient_OnNotificationMessageReceived(ByVal client As IGFClient, ByVal e As NotificationMessageEventArgs) DisplayNotificationMessage(DateTime.UtcNow, e.NotificationMessage.Channel, e.NotificationMessage.Message) End Sub Private Shared Sub DisplayNotificationHistory(ByVal client As IGFClient) For Each notification In client.Messaging.Notifications.[Get]() DisplayNotificationMessage(notification.Timestamp, notification.Channel, notification.Message) Next End Sub Private Shared Sub DisplayNotificationMessage(ByVal timestamp As DateTime, ByVal channel As NotificationChannel, ByVal message As String) Console.WriteLine($"Notification. {channel}: {message} at {timestamp.ToLocalTime()}") End Sub Private Shared Sub GFClient_OnBalanceChanged(ByVal client As IGFClient, ByVal e As BalanceChangedEventArgs) DisplayBalance("Balance Changed", e.Account, e.Currency) End Sub Private Shared Sub GFClient_OnAccountSummaryChanged(ByVal client As IGFClient, ByVal e As AccountSummaryChangedEventArgs) DisplayBalance("Account Summary Changed", e.Account, e.Currency) End Sub Private Shared Sub DisplayBalance(ByVal comment As String, ByVal account As IAccount, ByVal currency As GF.Api.Currencies.ICurrency) Dim balance As GF.Api.Balances.IBalance = account.Balances(currency) Console.WriteLine($"{comment}. {account.Spec} ({currency.Name}): P/L = {Math.Round(balance.OpenPnL + balance.RealizedPnL, 2)}. Total Net Liq: {account.TotalBalance.NetLiq}") End Sub Private Shared Sub GFClient_OnAvgPositionChanged(ByVal client As IGFClient, ByVal e As PositionChangedEventArgs) Console.WriteLine("Average Position. {0}/{1}: Net Pos: {2} @ {3}, Bought: {4}, Sold {5}, Prev Pos: {6} P/L: {7:c}", e.Account.Spec, e.ContractPosition.Contract.Symbol, e.ContractPosition.Net.Volume, e.ContractPosition.Contract.PriceToString(e.ContractPosition.Net.Price), e.ContractPosition.Long.Volume, e.ContractPosition.Short.Volume, e.ContractPosition.Prev.Volume, e.ContractPosition.OTE + e.ContractPosition.Gain) 'If e.ContractPosition.Net.Volume = 0 Then Stop End Sub Private Shared Sub GFClient_OnOrderFilled(ByVal client As IGFClient, ByVal e As OrderFilledEventArgs) Console.WriteLine("#{0} New fill: {1} @ {2} ({3}). Total filled qty: {4}, avg. price: {5}", e.Order.ID, e.Fill.Quantity, e.Fill.Contract.PriceToString(e.Fill.Price), If(e.Fill.IsActive, "active", "cancelled"), e.Order.Fills.TotalQuantity, e.Order.Contract.PriceToString(e.Order.Fills.AvgPrice)) End Sub Private Shared Sub GFClient_OnErrorOccurred(ByVal client As IGFClient, ByVal e As ErrorEventArgs) Console.WriteLine($"OnError: {e.Exception.Message}") End Sub Private Shared Sub GFClient_OnOrderConfirmed(ByVal client As IGFClient, ByVal e As OrderConfirmedEventArgs) Console.WriteLine($"#{e.OriginalOrderID} order confirmed. New order ID is {e.Order.ID}") End Sub Private Shared Sub GFClient_OnOrderStateChanged(ByVal client As IGFClient, ByVal e As OrderStateChangedEventArgs) Console.WriteLine($"#{e.Order.ID} order state changed from {e.PreviousOrderState} to {e.Order.CurrentState}") End Sub Private Shared Sub GFClient_OnLoginFailed(ByVal client As IGFClient, ByVal e As LoginFailedEventArgs) Console.WriteLine($"OnLoginFailed: {e.FailReason}") End Sub Private Shared Sub GFClient_OnDisconnected(ByVal client As IGFClient, ByVal e As DisconnectedEventArgs) Console.WriteLine($"OnDisconnected: {e.Message}") End Sub Private Shared Sub GFClient_OnLoginCompleted(ByVal client As IGFClient, ByVal e As LoginCompleteEventArgs) Console.WriteLine($"OnLoginComplete: CompleteConnected={client.Connection.Aggregate.IsConnected}") Console.WriteLine($" Accounts: {client.Accounts.[Get]().Count}, orders: {client.Orders.[Get]().Count}, base contracts: {client.Contracts.Base.[Get]().Count}") DisplayAccount(client, client.Accounts.[Get]().First()) DisplayNotificationHistory(client) _frontEsSymbolLookupRequestID = client.Contracts.Lookup.ByCriteria(New SymbolLookupRequestBuilder().WithResultCount(1).WithSymbol("ES", TextSearchMode.StartsWith).Build()) End Sub Private Shared Sub GFClient_OnSymbolLookupReceived(ByVal client As IGFClient, ByVal e As SymbolLookupEventArgs) If _frontEsSymbolLookupRequestID IsNot Nothing AndAlso e.RequestID = _frontEsSymbolLookupRequestID Then If e.Contracts.Any() Then client.Subscriptions.Price.Subscribe(e.Contracts.First().ID) End If End If End Sub Private Shared Sub GFClient_OnPriceTick(ByVal client As IGFClient, ByVal e As PriceChangedEventArgs) 'Console.WriteLine("#{0} New fill: {1} @ {2} ({3}). Total filled qty: {4}, avg. price: {5}", e.Order.ID, e.Fill.Quantity, e.Fill.Contract.PriceToString(e.Fill.Price), If(e.Fill.IsActive, "active", "cancelled"), e.Order.Fills.TotalQuantity, e.Order.Contract.PriceToString(e.Order.Fills.AvgPrice)) Console.WriteLine("{0} {1} {2}", e.Price.LastDateTime, e.Price.LastVol, e.Price.LastPrice) If Math.Abs(e.Price.LastPrice - e.Price.BidPrice) < e.Contract.TickSize Then PlaceOrder(client, e.Contract, OrderSide.Buy, e.Price.BidPrice, "By Bid") ElseIf Math.Abs(e.Price.LastPrice - e.Price.AskPrice) < e.Contract.TickSize Then PlaceOrder(client, e.Contract, OrderSide.Sell, e.Price.AskPrice, "By Ask") End If End Sub Private Shared Sub StatusTimer_Tick(ByVal client As IGFClient) client.Threading.BeginInvoke(Sub() If client.Connection.Aggregate.IsConnected Then DisplayAccount(client, client.Accounts.[Get]().First()) CheckAndCancelOrder(client) ModifyOrder(client) End If End Sub) End Sub Private Shared Sub PlaceOrder(ByVal client As IGFClient, ByVal contract As IContract, ByVal orderSide As OrderSide, ByVal limitPrice As Double, ByVal comments As String) Return If client.Orders.[Get]().Count = 0 OrElse client.Orders.[Get]().Last().IsFinalState Then Dim orderDraft = New OrderDraftBuilder().WithAccountID(client.Accounts.[Get]().First().ID).WithContractID(contract.ID).WithSide(orderSide).WithOrderType(OrderType.Limit).WithPrice(limitPrice).WithQuantity(1).WithEnd(DateTime.UtcNow.AddMinutes(1)).WithComments(comments).Build() Dim validationErrors As IReadOnlyList(Of OrderDraftValidationError) = client.Orders.Drafts.Validate(orderDraft) If validationErrors.Any() Then Console.WriteLine($"ERROR. Order draft is invalid ({orderSide} {orderDraft.Quantity} {contract.Symbol} @ {contract.PriceToString(limitPrice)}):") For Each [error] In validationErrors Console.WriteLine($" {[error].Message}") Next Else Dim order As IOrder = client.Orders.SendOrder(orderDraft) Console.WriteLine($"Order {order} was sent") End If End If End Sub Private Shared Sub ModifyOrder(ByVal client As IGFClient) If client.Orders.[Get]().Any() AndAlso Not client.Orders.[Get]().Last().IsFinalState Then Dim currentWorkingOrder As IOrder = client.Orders.[Get]().Last() If currentWorkingOrder.Commands.Last().State = CommandState.Executed Then Dim priceChange = If(currentWorkingOrder.IsBuySide, currentWorkingOrder.Contract.TickSize, -currentWorkingOrder.Contract.TickSize) Dim modifyOrderDraft As ModifyOrderDraft = New ModifyOrderDraftBuilder().FromOrder(currentWorkingOrder).WithPrice((If(currentWorkingOrder.Price, 0.0)) + priceChange).Build() Dim validationErrors As IReadOnlyList(Of OrderDraftValidationError) = client.Orders.Drafts.Validate(modifyOrderDraft) If validationErrors.Any() Then Console.WriteLine($"ERROR. Attempt to modify the order {currentWorkingOrder} failed:") For Each [error] In validationErrors Console.WriteLine($" {[error].Message}") Next Else Dim order As IOrder = client.Orders.ModifyOrder(modifyOrderDraft) Console.WriteLine($"Modify request has been sent: {order}") End If End If End If End Sub Private Shared Sub CheckAndCancelOrder(ByVal client As IGFClient) If client.Orders.[Get]().Any() AndAlso Not client.Orders.[Get]().Last().IsFinalState Then Dim currentWorkingOrder As IOrder = client.Orders.[Get]().Last() If currentWorkingOrder.Commands.Last().State = CommandState.Executed Then If client.Orders.Drafts.GetPriceCount(currentWorkingOrder.Type) > 0 Then If Math.Abs((If(currentWorkingOrder.Price, 0.0)) - currentWorkingOrder.Contract.CurrentPrice.LastPrice) >= 3 * currentWorkingOrder.Contract.TickSize Then Console.WriteLine($"Send request to cancel: {currentWorkingOrder}") client.Orders.CancelOrder(currentWorkingOrder.ID, SubmissionType.Automatic) End If End If End If End If End Sub Private Shared Sub DisplayAccount(ByVal client As IGFClient, ByVal account As IAccount) 'If account.AvgPositions.Count = 0 Then Stop Dim totalBalance As GF.Api.Balances.IBalance = account.TotalBalance Console.WriteLine($"Account: {account.Spec}") Console.WriteLine($" NetLiq: {totalBalance.NetLiq}") Console.WriteLine($" Cash: {totalBalance.Cash}") Console.WriteLine($" Open P/L: {totalBalance.OpenPnL}") Console.WriteLine($" Total P/L: {totalBalance.RealizedPnL + totalBalance.OpenPnL}") Console.WriteLine($" Initial Margin: {totalBalance.InitialMargin}") Console.WriteLine($" Net Options Value: {totalBalance.LongCallOptionsValue + totalBalance.LongPutOptionsValue + totalBalance.ShortCallOptionsValue + totalBalance.ShortPutOptionsValue}") Console.WriteLine($"Average Positions: {account.AvgPositions.Count}") Console.WriteLine($"Orders: {client.Orders.[Get]().Count}, last one: {(If(client.Orders.[Get]().Count > 0, client.Orders.[Get]().Last().ToString(), String.Empty))}") End Sub End Class End Namespace TLau
|
||||
TLau471 Posts: 70 Joined: |
My output as follows
--- Hello OEC! Connecting... Account Summary Changed. API003808 (USD): P/L = 0. Total Net Liq: 49774.9999999999 OnLoginComplete: CompleteConnected=True Accounts: 1, orders: 0, base contracts: 463 Account: API003808 NetLiq: 49774.9999999999 Cash: 49774.9999999999 Open P/L: 0 Total P/L: 0 Initial Margin: 0 Net Options Value: 0 Average Positions: 0 Orders: 0, last one: 6/3/2020 9:36:56 AM 1 2972.5 6/3/2020 9:36:56 AM 1 2972.5 6/3/2020 9:36:56 AM 1 2972.5 6/3/2020 9:36:56 AM 1 2972.5 6/3/2020 9:36:56 AM 1 2972.5 6/3/2020 9:36:57 AM 1 2972.5 6/3/2020 9:36:57 AM 1 2972.5 6/3/2020 9:36:57 AM 1 2972.5 6/3/2020 9:36:57 AM 1 2972.5 6/3/2020 9:36:57 AM 1 2972.5 6/3/2020 9:36:57 AM 1 2972.5 6/3/2020 9:36:57 AM 1 2972.5 6/3/2020 9:36:57 AM 1 2972.5 6/3/2020 9:36:59 AM 1 2972.25 6/3/2020 9:36:59 AM 1 2972.25 6/3/2020 9:36:59 AM 1 2972.25 6/3/2020 9:36:59 AM 1 2972.25 6/3/2020 9:36:59 AM 1 2972.25 6/3/2020 9:36:59 AM 1 2972.25 6/3/2020 9:36:59 AM 2 2972.25 6/3/2020 9:37:00 AM 1 2972.5 6/3/2020 9:37:00 AM 1 2972.5 6/3/2020 9:37:00 AM 1 2972.5 6/3/2020 9:37:00 AM 1 2972.5 6/3/2020 9:37:00 AM 1 2972.5 6/3/2020 9:37:00 AM 1 2972.5 6/3/2020 9:37:01 AM 1 2972.75 OnDisconnected: Disconnected by user Connecting... OnDisconnected: ConnectionError OnLoginFailed: ConnectionError TLau
|
||||
SRuscak Posts: 50 Joined: Aug 24, 2017 |
Well... I think you're just doing it too fast? I can reproduce your issue in about 1 in 3 attempts, but if I put a Thread.Sleep(10) between the disconnect and the reconnect, I can't reproduce it at all.
|
||||
TLau471 Posts: 70 Joined: |
Too fast? Sorry, but I don't think using sleep(10) is the robust approach to event driven programming.
I mean why 10? why not 1? or 100? It sounds more like a race condition. Can it be looked into? I can't tell if the server is rejecting the connection or the API being unable to connect out.. Again, what I'm asking is B, what is the recommended way for API clients to perform re-connection. Since client.Connection.Aggregate.Connect() is an asynchronous call, my first approach is to call it from inside the Disconnected event if the trader has specified to reconnect automatically. Thank you. TLau
|
||||
SRuscak Posts: 50 Joined: Aug 24, 2017 |
The example code you gave is not recommended because you are disconnecting and then reconnecting on what is nearly the next line of code. I don't see any use case for doing that. You can just stay connected instead.
In the case of wanting to reconnect after an unexpected disconnection, yes, it is recommended to wait for the client to raise the disconnection event before reconnecting. The 'race condition' will disappear if you do this. Probably your other issue here is that you are trying to call Connect from the Disconnected event handler, which is on the API thread. To quote the help file: "GF API is not a multithreaded library. Advance() will raise API events on the calling thread. Other threads MUST NOT operate on the IGFClient object graph before the Advance() method call returns." You can use client.Threading.BeginInvoke for this: Private Shared Sub GFClient_OnDisconnected(ByVal client As IGFClient, ByVal e As DisconnectedEventArgs)
|
||||
TLau471 Posts: 70 Joined: |
Ok,
BeginInvoke helped. Thank you.TLau
|
||||