GF API keeps certain conventions that should make understanding and using the API easier:
All GF API functionality is accessed through the IGFClient object graph.
Most local object collections can be accessed through IGFClient object graph methods. For example:
- GFClient.Accounts.Get()
- GFClient.Orders.Get()
- GFClient.Contracts.Get()
If a class of objects cannot be accessed from IGFClient directly, it can be accessed indirectly through intermediary objects. For example:
All methods that require interaction with the servers are asynchronous.
In most cases asynchronous method results are returned by raising some event on the IGFClient object graph.
The server will be sending updates that raise events unsolicited by the API client.
If an API method accepts an interface, the API will include a default implementation with minimal logic. If it better serves your purposes, you can write your own interface implementations.
GF API has more functionality that hasn't been covered in this guide:
Allocation Blocks
Listing related users and their accounts (relevant to Trader Managers)
Trading with compound contracts
Configuring risk limits
Listing other connected applications with the same username
GF API log statements
...and more
Some of these topics are addressed in the GF API Advanced Example
using System; using System.Collections.Generic; using System.Linq; using GF.Api; using GF.Api.Accounts; using GF.Api.Connection; using GF.Api.Contracts; using GF.Api.Contracts.Lookup; using GF.Api.Contracts.Lookup.Request; using GF.Api.Messaging.Chat; using GF.Api.Messaging.Notifications; using GF.Api.Orders; using GF.Api.Orders.Drafts; using GF.Api.Orders.Drafts.Validation; using GF.Api.Positions; using GF.Api.Threading; using GF.Api.Utils; using GF.Api.Values.Contracts.Lookup; using GF.Api.Values.Orders; namespace ConsoleSample { internal class Program { private static SymbolLookupRequestID _frontEsSymbolLookupRequestID; private static System.Timers.Timer _statusTimer; private static void Main(string[] args) { Console.WriteLine("Hello OEC!"); IGFClient client = GF.Api.Impl.GFApi.CreateClient(); var runner = new GFClientRunner(client); runner.Start(); client.Connection.Aggregate.LoginCompleted += GFClient_OnLoginCompleted; client.Connection.Aggregate.LoginFailed += GFClient_OnLoginFailed; client.Connection.Aggregate.Disconnected += GFClient_OnDisconnected; client.Contracts.Lookup.SymbolLookupReceived += GFClient_OnSymbolLookupReceived; client.Subscriptions.Price.PriceTick += GFClient_OnPriceTick; client.Orders.OrderStateChanged += GFClient_OnOrderStateChanged; client.Orders.OrderConfirmed += GFClient_OnOrderConfirmed; client.Orders.OrderFilled += GFClient_OnOrderFilled; client.Accounts.AvgPositionChanged += GFClient_OnAvgPositionChanged; client.Accounts.AccountSummaryChanged += GFClient_OnAccountSummaryChanged; client.Accounts.BalanceChanged += GFClient_OnBalanceChanged; client.Messaging.Notifications.NotificationMessageReceived += GFClient_OnNotificationMessageReceived; client.Messaging.Chat.ChatMessageReceived += GFClient_OnChatMessageReceived; client.Logging.ErrorOccurred += GFClient_OnErrorOccurred; client.Connection.Aggregate.Connect( new ConnectionContextBuilder() .WithUserName("username") .WithPassword("password") .WithPort(9210) .WithHost("api.gainfutures.com") .WithUUID("9e61a8bc-0a31-4542-ad85-33ebab0e4e86") .WithForceLogin(true) .Build()); Console.WriteLine("Connecting..."); _statusTimer = new System.Timers.Timer {Interval = TimeSpan.FromSeconds(10).TotalMilliseconds}; _statusTimer.Elapsed += (_, __) => StatusTimer_Tick(client); _statusTimer.Start(); Console.WriteLine("Press any key to exit"); Console.ReadKey(); client.Connection.Aggregate.Disconnect(); _statusTimer.Stop(); runner.Stop(); } private static void GFClient_OnChatMessageReceived(IGFClient client, ChatMessageEventArgs e) { 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!"); } private static void GFClient_OnNotificationMessageReceived(IGFClient client, NotificationMessageEventArgs e) { DisplayNotificationMessage(DateTime.UtcNow, e.NotificationMessage.Channel, e.NotificationMessage.Message); } private static void DisplayNotificationHistory(IGFClient client) { foreach (var notification in client.Messaging.Notifications.Get()) DisplayNotificationMessage(notification.Timestamp, notification.Channel, notification.Message); } private static void DisplayNotificationMessage(DateTime timestamp, NotificationChannel channel, string message) { Console.WriteLine($"Notification. {channel}: {message} at {timestamp.ToLocalTime()}"); } private static void GFClient_OnBalanceChanged(IGFClient client, BalanceChangedEventArgs e) { DisplayBalance("Balance Changed", e.Account, e.Currency); } private static void GFClient_OnAccountSummaryChanged(IGFClient client, AccountSummaryChangedEventArgs e) { DisplayBalance("Account Summary Changed", e.Account, e.Currency); } private static void DisplayBalance(string comment, IAccount account, GF.Api.Currencies.ICurrency currency) { GF.Api.Balances.IBalance balance = 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:c}"); } private static void GFClient_OnAvgPositionChanged(IGFClient client, PositionChangedEventArgs e) { 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); } private static void GFClient_OnOrderFilled(IGFClient client, OrderFilledEventArgs e) { 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), e.Fill.IsActive ? "active" : "cancelled", e.Order.Fills.TotalQuantity, e.Order.Contract.PriceToString(e.Order.Fills.AvgPrice)); } private static void GFClient_OnErrorOccurred(IGFClient client, ErrorEventArgs e) { Console.WriteLine($"OnError: {e.Exception.Message}"); } private static void GFClient_OnOrderConfirmed(IGFClient client, OrderConfirmedEventArgs e) { Console.WriteLine($"#{e.OriginalOrderID} order confirmed. New order ID is {e.Order.ID}"); } private static void GFClient_OnOrderStateChanged(IGFClient client, OrderStateChangedEventArgs e) { Console.WriteLine($"#{e.Order.ID} order state changed from {e.PreviousOrderState} to {e.Order.CurrentState}"); } private static void GFClient_OnLoginFailed(IGFClient client, LoginFailedEventArgs e) { Console.WriteLine($"OnLoginFailed: {e.FailReason}"); } private static void GFClient_OnDisconnected(IGFClient client, DisconnectedEventArgs e) { Console.WriteLine($"OnDisconnected: {e.Message}"); } private static void GFClient_OnLoginCompleted(IGFClient client, LoginCompleteEventArgs e) { Console.WriteLine($"OnLoginComplete: CompleteConnected={client.Connection.Aggregate.IsConnected}"); Console.WriteLine($"\tAccounts: {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()); } private static void GFClient_OnSymbolLookupReceived(IGFClient client, SymbolLookupEventArgs e) { if (_frontEsSymbolLookupRequestID != null && e.RequestID == _frontEsSymbolLookupRequestID) { if (e.Contracts.Any()) { client.Subscriptions.Price.Subscribe(e.Contracts.First().ID); } } } private static void GFClient_OnPriceTick(IGFClient client, PriceChangedEventArgs e) { if (Math.Abs(e.Price.LastPrice - e.Price.BidPrice) < e.Contract.TickSize) PlaceOrder(client, e.Contract, OrderSide.Buy, e.Price.BidPrice, "By Bid"); else if (Math.Abs(e.Price.LastPrice - e.Price.AskPrice) < e.Contract.TickSize) PlaceOrder(client, e.Contract, OrderSide.Sell, e.Price.AskPrice, "By Ask"); } private static void StatusTimer_Tick(IGFClient client) { client.Threading.BeginInvoke(() => { if (client.Connection.Aggregate.IsConnected) { DisplayAccount(client, client.Accounts.Get().First()); CheckAndCancelOrder(client); ModifyOrder(client); } }); } private static void PlaceOrder(IGFClient client, IContract contract, OrderSide orderSide, double limitPrice, string comments) { if (client.Orders.Get().Count == 0 || client.Orders.Get().Last().IsFinalState) { var 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(); IReadOnlyList<OrderDraftValidationError> validationErrors = client.Orders.Drafts.Validate(orderDraft); if (validationErrors.Any()) { Console.WriteLine($"ERROR. Order draft is invalid ({orderSide} {orderDraft.Quantity} {contract.Symbol} @ {contract.PriceToString(limitPrice)}):"); foreach (var error in validationErrors) Console.WriteLine($"\t{error.Message}"); } else { IOrder order = client.Orders.SendOrder(orderDraft); Console.WriteLine($"Order {order} was sent"); } } } private static void ModifyOrder(IGFClient client) { if (client.Orders.Get().Any() && !client.Orders.Get().Last().IsFinalState) { IOrder currentWorkingOrder = client.Orders.Get().Last(); if (currentWorkingOrder.Commands.Last().State == CommandState.Executed) { var priceChange = currentWorkingOrder.IsBuySide ? currentWorkingOrder.Contract.TickSize : -currentWorkingOrder.Contract.TickSize; ModifyOrderDraft modifyOrderDraft = new ModifyOrderDraftBuilder() .FromOrder(currentWorkingOrder) .WithPrice((currentWorkingOrder.Price ?? 0.0) + priceChange) .Build(); IReadOnlyList<OrderDraftValidationError> validationErrors = client.Orders.Drafts.Validate(modifyOrderDraft); if (validationErrors.Any()) { Console.WriteLine($"ERROR. Attempt to modify the order {currentWorkingOrder} failed:"); foreach (var error in validationErrors) Console.WriteLine($"\t{error.Message}"); } else { IOrder order = client.Orders.ModifyOrder(modifyOrderDraft); Console.WriteLine($"Modify request has been sent: {order}"); } } } } private static void CheckAndCancelOrder(IGFClient client) { if (client.Orders.Get().Any() && !client.Orders.Get().Last().IsFinalState) { IOrder currentWorkingOrder = client.Orders.Get().Last(); if (currentWorkingOrder.Commands.Last().State == CommandState.Executed) { if (client.Orders.Drafts.GetPriceCount(currentWorkingOrder.Type) > 0) { if (Math.Abs((currentWorkingOrder.Price ?? 0.0) - currentWorkingOrder.Contract.CurrentPrice.LastPrice) >= 3 * currentWorkingOrder.Contract.TickSize) { Console.WriteLine($"Send request to cancel: {currentWorkingOrder}"); client.Orders.CancelOrder(currentWorkingOrder.ID, SubmissionType.Automatic); } } } } } private static void DisplayAccount(IGFClient client, IAccount account) { 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: {client.Orders.Get().Count}, last one: {(client.Orders.Get().Count > 0 ? client.Orders.Get().Last().ToString() : string.Empty)}"); } } }