Let's create an instance of the GF API client and connect it to the GAIN Capital Futures servers.
GFAPI is structured as an interface library, [GF.Api], and an implementation library, [GF.Api.Impl], as well as a library for common value types, [GF.Api.Values]. Typically, all calls should be made through IGFClient, and the implementation library is referenced just once during instantiation of the concrete implementation, GFClient.
GF.Api.IGFClient gfClient = GF.Api.Impl.GFApi.CreateClient();
All GF API functionality is available from the IGFClient object graph.
Next, we hook up GFClient to the thread that will make it run.
Once the client is connected, it will passively enqueue server messages, but does no message processing until the API consumer tells it to. When Advance is called, GF API will process all enqueued messages. Therefore, Advance must be called frequently to keep API state synchronized with the servers.
gfClient.Threading.Advance();
You can use GFClientRunner to manage calling Advance for you. It will create a new thread and call Advance in a loop.
var runner = new GF.Api.Threading.GFClientRunner(gfClient); runner.Start();
Alternatively, you can write your own runner to call Advance.
Caution |
---|
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. |
Caution |
---|
The GF Servers monitor the client heartbeat. The servers will disconnect the client if the client is silent for too long. GF API will manage the heartbeat for you, but Advance must be called regularly. |
Tip |
---|
IThreadingApi has some helper methods to make thread synchronization a little easier. They will execute code on the runner thread. GFClient thread invocation gfClient.Threading.Invoke(() => { if (!gfClient.Connection.Aggregate.IsConnected) return; var account = gfClient.Accounts.Get().First(); GF.Api.Balances.IBalance totalBalance = account.TotalBalance; Console.WriteLine($"Account: {account.Spec}"); Console.WriteLine($"\tNetLiq: {totalBalance.NetLiq:c}"); Console.WriteLine($"\tCash: {totalBalance.Cash:c}"); Console.WriteLine($"\tOpen P/L: {totalBalance.OpenPnL:c}"); Console.WriteLine($"\tTotal P/L: {totalBalance.RealizedPnL + totalBalance.OpenPnL:c}"); Console.WriteLine($"\tInitial Margin: {totalBalance.InitialMargin:c}"); Console.WriteLine($"\tNet Options Value: {totalBalance.LongCallOptionsValue + totalBalance.LongPutOptionsValue + totalBalance.ShortCallOptionsValue + totalBalance.ShortPutOptionsValue:c}"); Console.WriteLine($"Average Positions: {account.AvgPositions.Count}"); Console.WriteLine($"Orders: {gfClient.Orders.Get().Count}, last one: {(gfClient.Orders.Get().Count > 0 ? gfClient.Orders.Get().Last().ToString() : string.Empty)}"); Console.WriteLine(); }); |
We must choose which GF server to connect to:
- Order: The primary server, where most functionality is located. Deals with high-priority, low-volume data. e.g. Orders, Contracts, Strategies, Chat, Allocation, Currency, Users, and Traders
- Price: Deals with low-priority, high-volume data. Handles Subscriptions (Quotes, Bars, Ticks, DOM, Histogram)
- Aggregate: A virtual 'server' that represents an all-or-nothing connection to each physical server.
We'll use the Aggregate connection for these examples. This is the simplest option and is probably appropriate for most API consumers.
IServerConnectionApiConnect(ConnectionContext) takes a ConnectionContext parameter, and ConnectionContextBuilder can be used to build it.
Parameters:
Note |
---|
GF API deals with asynchronicity by raising events. For every asynchronous query or state change, there will be an event that will inform the consumer of the result or new state. |
When connection is complete (or has failed) an event will be raised: OnLoginComplete or OnLoginFailed.
gfClient.Connection.Aggregate.LoginCompleted += (client, e) => Console.WriteLine("Connection complete"); gfClient.Connection.Aggregate.LoginFailed += (client, e) => Console.WriteLine($"Connection failed: {e.FailReason}"); gfClient.Connection.Aggregate.Disconnected += (client, e) => Console.WriteLine($"Disconnected: {e.Message}"); gfClient.Logging.ErrorOccurred += (client, e) => Console.WriteLine($"{e.Exception.Message}"); gfClient.Threading.Invoke(() => gfClient.Connection.Aggregate.Connect( new GF.Api.Connection.ConnectionContextBuilder() .WithUserName("username") .WithPassword("password") .WithUUID("9e61a8bc-0a31-4542-ad85-33ebab0e4e86") .WithPort(9200) .WithHost("api.gainfutures.com") .WithForceLogin(true) .Build()));
internal class Program { private static void Main(string[] args) { GF.Api.IGFClient gfClient = GF.Api.Impl.GFApi.CreateClient(); var runner = new GF.Api.Threading.GFClientRunner(gfClient); runner.Start(); Console.WriteLine("Connecting..."); gfClient.Connection.Aggregate.LoginCompleted += (client, e) => Console.WriteLine("Connection complete"); gfClient.Connection.Aggregate.LoginFailed += (client, e) => Console.WriteLine($"Connection failed: {e.FailReason}"); gfClient.Connection.Aggregate.Disconnected += (client, e) => Console.WriteLine($"Disconnected: {e.Message}"); gfClient.Logging.ErrorOccurred += (client, e) => Console.WriteLine($"{e.Exception.Message}"); gfClient.Threading.Invoke(() => gfClient.Connection.Aggregate.Connect( new GF.Api.Connection.ConnectionContextBuilder() .WithUserName("username") .WithPassword("password") .WithUUID("9e61a8bc-0a31-4542-ad85-33ebab0e4e86") .WithPort(9200) .WithHost("api.gainfutures.com") .WithForceLogin(true) .Build())); var timer = new System.Timers.Timer {Interval = TimeSpan.FromSeconds(2).TotalMilliseconds}; timer.Elapsed += (_, __) => { // The timer callback is on a different thread than the GFClientRunner, so we must delegate to the runner thread gfClient.Threading.Invoke(() => { if (!gfClient.Connection.Aggregate.IsConnected) return; var account = gfClient.Accounts.Get().First(); GF.Api.Balances.IBalance totalBalance = account.TotalBalance; Console.WriteLine($"Account: {account.Spec}"); Console.WriteLine($"\tNetLiq: {totalBalance.NetLiq:c}"); Console.WriteLine($"\tCash: {totalBalance.Cash:c}"); Console.WriteLine($"\tOpen P/L: {totalBalance.OpenPnL:c}"); Console.WriteLine($"\tTotal P/L: {totalBalance.RealizedPnL + totalBalance.OpenPnL:c}"); Console.WriteLine($"\tInitial Margin: {totalBalance.InitialMargin:c}"); Console.WriteLine($"\tNet Options Value: {totalBalance.LongCallOptionsValue + totalBalance.LongPutOptionsValue + totalBalance.ShortCallOptionsValue + totalBalance.ShortPutOptionsValue:c}"); Console.WriteLine($"Average Positions: {account.AvgPositions.Count}"); Console.WriteLine($"Orders: {gfClient.Orders.Get().Count}, last one: {(gfClient.Orders.Get().Count > 0 ? gfClient.Orders.Get().Last().ToString() : string.Empty)}"); Console.WriteLine(); }); }; Console.WriteLine("Press any key to quit"); Console.ReadKey(); timer.Stop(); runner.Stop(); } }