API Support Forum
OEC API > API Support > GFAPI Socket Error after 2 minutes
Author Topic: GFAPI Socket Error after 2 minutes
(14 messages, Page 1 of 1)
Moderators: VPfau
TLau471
Posts: 70
Joined:


Posted: Mar 05, 2020 @ 04:18 AM             Msg. 1 of 14
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


Posted: Mar 05, 2020 @ 08:14 AM             Msg. 2 of 14
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:


Posted: Mar 05, 2020 @ 08:58 AM             Msg. 3 of 14
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


Posted: Mar 05, 2020 @ 09:46 AM             Msg. 4 of 14
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:


Posted: Mar 05, 2020 @ 11:31 AM             Msg. 5 of 14
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


Posted: Mar 05, 2020 @ 12:29 PM             Msg. 6 of 14
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


Posted: Mar 05, 2020 @ 12:35 PM             Msg. 7 of 14
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


Posted: Mar 05, 2020 @ 12:54 PM             Msg. 8 of 14
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:


Posted: Mar 06, 2020 @ 03:35 AM             Msg. 9 of 14
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:


Posted: Mar 06, 2020 @ 03:38 AM             Msg. 10 of 14
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


Posted: Mar 06, 2020 @ 08:59 AM             Msg. 11 of 14
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:


Posted: Mar 06, 2020 @ 09:12 AM             Msg. 12 of 14
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


Posted: Mar 06, 2020 @ 10:06 AM             Msg. 13 of 14
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)
client.Threading.BeginInvoke(Sub() Connect(client))
End Sub
TLau471
Posts: 70
Joined:


Posted: Mar 09, 2020 @ 05:40 AM             Msg. 14 of 14
Ok, BeginInvoke helped. Thank you.
TLau