PNL Subscription Manager

GF API Documentation

To have accurate position values, the relevant contracts need to have quote subscriptions. In legacy OEC API, the API did this automatically, but in GF API it is the client's responsibility. This allows more control over the limited number of active subscriptions.

This snippet will listen to order fills and maintain subscriptions for open positions.

PNL Subscription Manager

PNL Subscription Manager
public class PnlSubscriptionManager
{
    private readonly IGFClient _client;
    private Dictionary<ContractID, PositionInfo> _contractInfo = new Dictionary<ContractID, PositionInfo>();

    public PnlSubscriptionManager(IGFClient client)
    {
        _client = client;
    }

    public void Start()
    {
        _client.Connection.Aggregate.LoginCompleted += GFClient_OnLoginCompleted;
        _client.Orders.OrderFilled += Orders_OrderFilled;
    }

    private void GFClient_OnLoginCompleted(IGFClient client, LoginCompleteEventArgs e)
        => _contractInfo = client.Accounts.Get()
            .SelectMany(a => a.AvgPositions.Select(pair => pair.Value))
            .GroupBy(p => p.Contract.ID)
            .ToDictionary(
                g => g.Key,
                contractPositions =>
                {
                    var accountsWithPositions = new HashSet<AccountID>(
                        contractPositions
                            .Where(position => position.Net.Volume != 0)
                            .Select(position => position.Account.ID));
                    return new PositionInfo
                    {
                        AccountsWithPositions = accountsWithPositions,
                        Subscription = accountsWithPositions.Any()
                            ? client.Subscriptions.Price.Subscribe(contractPositions.Key)
                            : null
                    };
                });

    private void Orders_OrderFilled(IGFClient client, OrderFilledEventArgs e)
    {
        if (!_contractInfo.TryGetValue(e.Fill.Contract.ID, out var info))
            info = _contractInfo[e.Fill.Contract.ID] = new PositionInfo();

        var fillQty =
            (e.Order.IsBuySide ? 1 : -1)
            * (e.Fill.Type == FillType.Opposite ? -1 : 1)
            * (e.Fill.IsActive ? 1 : -1)
            * e.Fill.Quantity;

        if (e.Order.Account.AvgPositions[e.Fill.Contract].Net.Volume + fillQty != 0)
        {
            if (info.AccountsWithPositions.Add(e.Order.Account.ID) &&
                info.Subscription == null)
                info.Subscription = client.Subscriptions.Price.Subscribe(e.Fill.Contract.ID);
        }
        else
        {
            if (info.AccountsWithPositions.Remove(e.Order.Account.ID) &&
                info.Subscription != null &&
                info.AccountsWithPositions.Count == 0)
            {
                info.Subscription.Unsubscribe();
                info.Subscription = null;
            }
        }
    }

    private class PositionInfo
    {
        public HashSet<AccountID> AccountsWithPositions { get; set; } = new HashSet<AccountID>();

        public ISubscription Subscription { get; set; }
    }
}