Connect to GF Servers

GF API Documentation

Let's create an instance of the GF API client and connect it to the GAIN Capital Futures servers.

Create GFClient

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.

Create GFClient instance
GF.Api.IGFClient gfClient = GF.Api.Impl.GFApi.CreateClient();

All GF API functionality is available from the IGFClient object graph.

Start GFClient Runner

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.

Advance
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.

Start GFClient runner
var runner = new GF.Api.Threading.GFClientRunner(gfClient);
runner.Start();

Alternatively, you can write your own runner to call Advance.

Caution note 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 note 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 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();
});
Connect to GF Servers

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:

UUID

A unique string assigned by our Customer Service to identify the connecting application. To get one, contact us at [email protected].

For the purpose of these examples, let's use a fake UUID: 9e61a8bc-0a31-4542-ad85-33ebab0e4e86.

Host

The URL of the GF Servers. There are three environments:

  • api.gainfutures.com

    The API environment. The first stage for testing your application. Uses simulated money and internally simulated orders.

  • sim.gainfutures.com

    The SIM environment. Just like API environment, uses simulated money and internally simulated orders. Access to this environment requires passing Conformance testing.

  • prod.gainfutures.com

    The PROD environment. Uses real money and real orders. Access to this environment requires passing Conformance testing and funded futures account.

Port

GF API Order server is accessible on port 9210.

GF API Price server is accessible on port 9211.

The Aggregate server is considered to be on port 9210.

ForceLogin
Each server permits one login per user-UUID pair. Enable this setting to kick any conflicting pre-existing login.
Note 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.

Connect
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()));
Full example

Full Example
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();
    }
}
See Also

Reference

IServerConnectionApi.OnLoginComplete
IServerConnectionApi.OnLoginFailed