preloader
post-thumb

Last Update: February 2, 2023


BY eric


author-thumb


Keywords

Utilizing FIX API as a Gateway for Algorithmic Trading with C#

Introduction

In the realm of algorithmic trading, connecting to financial markets efficiently and securely is paramount. The Financial Information eXchange (FIX) protocol stands out as a standardized messaging protocol that facilitates real-time electronic communication for trading purposes. This article explores the integration of FIX API as a gateway for algorithmic trading, leveraging the C# programming language.

Understanding FIX API

What is FIX?

FIX, short for Financial Information eXchange, is an industry-standard protocol used for the electronic exchange of securities-related information. It provides a standardized format for transmitting trading-related messages, allowing seamless communication between financial institutions and trading systems.

The Importance of FIX in Algorithmic Trading

  1. Standardization: FIX ensures a uniform language for communication across different financial institutions, minimizing discrepancies and streamlining data exchange.

  2. Low Latency: FIX is designed for high-performance and low-latency communication, making it ideal for algorithmic trading where timely execution is crucial.

  3. Wide Adoption: FIX is widely adopted in the financial industry, and many exchanges, brokers, and trading platforms support it, making it a versatile choice for algorithmic traders.

Setting Up a FIX API Connection in C#

Choosing a FIX Library

When implementing a FIX API connection in C#, selecting a reliable FIX library is crucial. Some popular choices include:

  • QuickFIX: An open-source FIX engine that supports C# and provides a robust foundation for FIX protocol implementation.

  • QuickFIX/n: Another open-source FIX engine that is a .NET port of QuickFIX, making it suitable for C# development.

Installation

Install the chosen FIX library in your C# project using Dotnet Package Manager. For example, using dotnet CLI:

dotnet add package QuickFIXn.Core

# cTrader supports FIX 4.4
dotnet add package QuickFIXn.FIX4.4
# if you need FIX 5.0, 5.0SP1 or 5.0SP2
# dotnet add package QuickFIXn.FIX5.0
# dotnet add package QuickFIXn.FIX5.0SP1
# dotnet add package QuickFIXn.FIX5.0SP2

Code Implementation

Now we will follow the instruction as described in this (article)[/blog/introduction-about-trading-gateway/] to implement three core parts: getting market data, placing orders, and managing orders. Even there are only three core parts, there are others needed to be considered, such as creating connection, conducting verification, and getting full list of symbols that are available for trading.

Basic FIX Code Structure

Below is a simplified example skeleton demonstrating how to establish a FIX API connection using QuickFIX/n:

using QuickFix;
using QuickFix.Fields;

class FixApplication : MessageCracker, IApplication
{
    public void FromAdmin(Message message, SessionID sessionID) { }

    public void ToAdmin(Message message, SessionID sessionID) { }

    public void FromApp(Message message, SessionID sessionID) { }

    public void ToApp(Message message, SessionID sessionID) { }

    public void OnCreate(SessionID sessionID) { }

    public void OnLogon(SessionID sessionID) { }

    public void OnLogout(SessionID sessionID) { }
}

class Program
{
    static void Main(string[] args)
    {
        SessionSettings settings = new SessionSettings("fix.cfg");
        IApplication application = new FixApplication();
        IMessageStoreFactory storeFactory = new FileStoreFactory(settings);
        ILogFactory logFactory = new FileLogFactory(settings);
        MessageFactory messageFactory = new DefaultMessageFactory();

        ThreadedSocketInitiator initiator = new ThreadedSocketInitiator(
            application,
            storeFactory,
            settings,
            logFactory,
            messageFactory
        );

        initiator.Start();

        // Perform algorithmic trading logic here

        initiator.Stop();
    }
}

Ensure to replace "fix.cfg" with the path to your FIX configuration file. Below is an example of a FIX configuration file:

[DEFAULT]
ConnectionType=initiator
SocketConnectHost=demo1-us.p.ctrader.com
SocketConnectPort=5201
ReconnectInterval=60
FileStorePath=store
FileLogPath=log
StartTime=00:00:00
EndTime=00:00:00
HeartBtInt=30
ResetOnLogon=Y
ResetOnLogout=Y
ResetOnDisconnect=Y
ResetSeqNumFlag=Y
UseDataDictionary=Y
AllowUnknownMsgFields=Y
DataDictionary=config/quickfix/MYFIX44.xml
ValidateFieldsOutOfOrder=N
ValidateFieldsHaveValues=N
ValidateUserDefinedFields=N
ValidateFieldsRequiredForMessage=N
ValidateFieldsChecksum=N
ValidateFieldsRepeatingGroupCount=N
ValidateFieldsUserDefined=N

[SESSION]
BeginString=FIX.4.4
SenderCompID=demo.somemarkets.1594209
TargetCompID=cServer
ReconnectInterval=30
HeartBtInt=300
AppDataDictionary=config/quickfix/FIX50SP2.xml
TransportDataDictionary=config/quickfix/FIXT11.xml
SenderSubID=QUOTE
TargetSubID=QUOTE
ResetSeqNumFlag=Y
EncryptMethod=0
ScreenLogShowIncoming=N
ScreenLogShowOutgoing=Y
ScreenLogShowEvents=Y

You will need to replace the SocketConnectHost, SocketConnectPort, SenderCompID, TargetCompID, SenderSubID, and TargetSubID with your broker's specific details.

We will fill the code with different logics in the following sections.

Logging In

Making a connection and logging in to the FIX API is the first step in establishing communication with the financial markets. The OnLogon and OnLogout methods in the FixApplication class can be used to handle the login and logout events. Due to the security reasons, QuickFix library may ignore the Credentials in the configuration file. You may need to override the ToAdmin method to add the credentials to the logon message. For example:


    public void ToAdmin(Message message, SessionID sessionID)
    {

        if (message is QuickFix.FIX44.Logon)
        {
            var logon = message as QuickFix.FIX44.Logon;
            logon.Set(new Username(username));
            logon.Set(new Password(password));
            logon.Set(new ResetSeqNumFlag(true));
        }


        log(message, "ToAdmin");
    }

Getting Security list

We need to know what we can trade on the platform.

    public void RequestSecurityList()
    {
        QuickFix.FIX44.SecurityListRequest message = new QuickFix.FIX44.SecurityListRequest(
            new SecurityReqID("1"), // change this to a unique value
            new SecurityListRequestType(SecurityListRequestType.SYMBOL) // other SecurityListRequestType.ALL_SECURITIES, SecurityListRequestType.SYMBOL, etc. however, cTrader only supports SYMBOL
        );

        Session.SendToTarget(message, sessionID);
    }

    public void OnMessage(QuickFix.FIX44.SecurityList message, SessionID sessionId)
    {
        var no_related_sym = message.Get(new NoRelatedSym());
        for (int i = 1; i <= no_related_sym.getValue(); i++)
        {
            QuickFix.FIX44.SecurityList.NoRelatedSymGroup group = new QuickFix.FIX44.SecurityList.NoRelatedSymGroup();
            try {
                message.GetGroup(i, group);
            }
            catch (Exception ex) {
                continue;
            }
            // for cTrader
            // var symbol_id = group.GetField(new Symbol()).getValue();
            // var symbol = group.GetField(new StringField(1007)).getValue();
            // symbol_ids[symbol_id] = symbol;
        }
    }

Getting Market Data

Request Market Data

To request market data, in other words, subscribe a symbol, you can use the RequestMarketData method in the FixApplication class. This method sends a market data request message to the financial markets, requesting real-time market data for a specific symbol. For example:

    public void RequestMarketData(string symbol)
    {
        QuickFix.FIX44.MarketDataRequest message = new QuickFix.FIX44.MarketDataRequest(
            new MDReqID("1"), // change this to a unique value
            new SubscriptionRequestType(SubscriptionRequestType.SNAPSHOT_PLUS_UPDATES),
            new MarketDepth(1) // 1 for top of book, 0 for full depth
        );

        message.Set(new MDUpdateType(MDUpdateType.FULL_REFRESH));
        message.Set(new AggregatedBook(true));
        message.Set(new NoMDEntryTypes(2));
        message.Set(new NoRelatedSym(1));

        QuickFix.FIX44.MarketDataRequest.NoMDEntryTypesGroup group = new QuickFix.FIX44.MarketDataRequest.NoMDEntryTypesGroup();
        group.Set(new MDEntryType(MDEntryType.BID));
        message.AddGroup(group);

        group = new QuickFix.FIX44.MarketDataRequest.NoMDEntryTypesGroup();
        group.Set(new MDEntryType(MDEntryType.OFFER));
        message.AddGroup(group);

        group = new QuickFix.FIX44.MarketDataRequest.NoRelatedSymGroup();
        /**
         * in cTrader, the symbol is a unique number, you will need to a system to map the symbol to the unique number
         * for example, 1 is EURUSD, 2 is GBPUSD, etc.
         * 
         * to subscribe EURUSD, you will need to call RequestMarketData("1");
         */
        group.Set(new Symbol(symbol));
        message.AddGroup(group);

        Session.SendToTarget(message, sessionID);
    }

Process Market Data

    public void OnMessage(QuickFix.FIX44.MarketDataIncrementalRefresh message, SessionID sessionId)
    {
        var no_md_entries = message.Get(new NoMDEntries());
        for (int i = 1; i <= no_md_entries.getValue(); i++)
        {
            QuickFix.FIX44.MarketDataIncrementalRefresh.NoMDEntriesGroup group = new QuickFix.FIX44.MarketDataIncrementalRefresh.NoMDEntriesGroup();
            try {
                message.GetGroup(i, group);
            }
            catch (Exception ex) {
                continue;
            }
            var symbol_id = group.GetField(new Symbol()).getValue();

            /**
             * For cTrader, the symbol you get is a unique number
             */
            var symbol = symbol_ids[symbol_id];

            try {
                var md_entry_type = group.GetField(new MDEntryType()).getValue();
                var md_entry_px = group.GetField(new MDEntryPx()).getValue().ToString().ToDouble();
                var md_entry_size = group.GetField(new MDEntrySize()).getValue();
                var md_update_action = group.GetField(new MDUpdateAction()).getValue();

                if (md_update_action == MDUpdateAction.NEW || md_update_action == MDUpdateAction.CHANGE)
                {
                     /**
                     * Add your logic here how you want to use the data, for example, add the market data to your data store
                     */
                    // Bid, md_entry_type == MDEntryType.BID ? md_entry_px : 0
                    // Ask, md_entry_type == MDEntryType.OFFER ? md_entry_px : 0
                }
            }
            catch (Exception ex) {
                continue;
            }
            
            break;
        }
    }

Placing Orders

Sending an Order

    public void SendOrder(string symbol, string side, double price, int quantity)
    {
        QuickFix.FIX44.NewOrderSingle message = new QuickFix.FIX44.NewOrderSingle(
            new ClOrdID("1"), // change this to a unique value
            new Side(side), // Side.BUY or Side.SELL
            new TransactTime(DateTime.Now),
            new OrdType(OrdType.LIMIT), // OrdType.LIMIT, OrdType.MARKET, or OrdType.STOP
            new OrderQty(quantity),
            new Price(price), // only required for OrdType.LIMIT
            new Symbol(symbol) // the symbol you want to trade, in cTrader, it is a unique number
        );

        // if this is a stop order
        // message.Set(new StopPx(stop_price));

        Session.SendToTarget(message, sessionID);
    }

Processing Order Confirmation

    public void OnMessage(QuickFix.FIX44.ExecutionReport message, SessionID sessionId)
    {
        var cl_ord_id = message.GetField(new ClOrdID()).getValue();
        var exec_type = message.GetField(new ExecType()).getValue();
        var order_id = message.GetField(new OrderID()).getValue();
        var symbol = message.GetField(new Symbol()).getValue();
        var side = message.GetField(new Side()).getValue();
        var price = message.GetField(new Price()).getValue().ToString().ToDouble();
        var quantity = message.GetField(new OrderQty()).getValue();
        var cum_qty = message.GetField(new CumQty()).getValue();
        var leaves_qty = message.GetField(new LeavesQty()).getValue();
        var last_px = message.GetField(new LastPx()).getValue().ToString().ToDouble();
        var last_qty = message.GetField(new LastQty()).getValue();
        var order_status = message.GetField(new OrdStatus()).getValue();

        if (order_status == OrdStatus.FILLED)
        {
            /**
             * Add your logic here how you want to use the data, for example, add the order to your data store
             */
            // get the position id
            // var position_id = message.GetField(new StringField(1007)).getValue();
            // add the position to your data store
            // position id is needed to close the position
        }
    }

Managing Orders

Cancelling an Order

    public void CancelOrder(string order_id)
    {
        QuickFix.FIX44.OrderCancelRequest message = new QuickFix.FIX44.OrderCancelRequest(
            new OrigClOrdID(order_id),
            new ClOrdID("1"), // change this to a unique value
            new Side(Side.BUY), // Side.BUY or Side.SELL
            new TransactTime(DateTime.Now),
            new Symbol(symbol) // the symbol you want to trade, in cTrader, it is a unique number
        );

        Session.SendToTarget(message, sessionID);
    }

Processing Order Cancellation Confirmation

    public void OnMessage(QuickFix.FIX44.OrderCancelRequest message, SessionID sessionId)
    {
        /**
         * Add your logic here how you want to use the data, for example, remove the order from your data store
         */
    }

Modifying an Order

    public void ModifyOrder(string order_id, double price, int quantity)
    {
        QuickFix.FIX44.OrderCancelReplaceRequest message = new QuickFix.FIX44.OrderCancelReplaceRequest(
            new OrigClOrdID(order_id),
            new ClOrdID("1"), // change this to a unique value
            new Side(Side.BUY), // Side.BUY or Side.SELL
            new TransactTime(DateTime.Now),
            new OrdType(OrdType.LIMIT), // OrdType.LIMIT or OrdType.MARKET
            new OrderQty(quantity),
            new Price(price), // only required for OrdType.LIMIT
            new Symbol(symbol) // the symbol you want to trade, in cTrader, it is a unique number
        );

        Session.SendToTarget(message, sessionID);
    }

Processing Order Modification Confirmation

    public void OnMessage(QuickFix.FIX44.OrderCancelReplaceRequest message, SessionID sessionId)
    {
        /**
         * Add your logic here how you want to use the data, for example, update the order in your data store
         */
    }

Managing Existing Positions

Requesting Positions

We may or may not have the existing order information in the trading system when we connect to an account, so we need to get the existing positions from the broker in order to decide to start a position or close an existing postion.

    public void RequestPosition()
    {
        QuickFix.FIX44.RequestForPositions message = new QuickFix.FIX44.RequestForPositions(
            new PosReqID("1"), // change this to a unique value
        );

        Session.SendToTarget(message, sessionID);
    }

    public void OnMessage(QuickFix.FIX44.PositionReport message, SessionID sessionId)
    {
        var pos_req_id = message.GetField(new PosReqID()).getValue();
        var total_num_pos_reports = message.GetField(new TotalNumPosReports()).getValue();
        var symbol = message.GetField(new Symbol()).getValue(); // it is actually a unique number in cTrader

        // we have to save the position id for closing the position
        var position_id = message.Get(new PosMaintRptID()).getValue();

        for (var i = 0; i < total_num_pos_reports; ++i) {

            QuickFix.FIX44.PositionReport.NoPositionsGroup group = new QuickFix.FIX44.PositionReport.NoPositionsGroup();
            try {
                message.GetGroup(i, group);
            }
            catch (Exception ex) {
                continue;
            }

            /**
            * Add your logic here how you want to use the data, for example, add the position to your data store
            */
            var long_qty = group.GetField(new LongQty()).getValue();
            var short_qty = group.GetField(new ShortQty()).getValue();

        }
    }

Closing a Position

When we close a position, we just need to reverse the side of the position and send the order to the broker.

    public void ClosePosition(string position_id, string symbol, string side, double price, int quantity)
    {
        var close_side = side == Side.BUY ? Side.SELL : Side.BUY;

        QuickFix.FIX44.NewOrderSingle message = new QuickFix.FIX44.NewOrderSingle(
            new OrigClOrdID(position_id),
            new ClOrdID("1"), // change this to a unique value
            new Side(close_side), // Side.BUY or Side.SELL
            new TransactTime(DateTime.Now),
            new Symbol(symbol) // the symbol you want to trade, in cTrader, it is a unique number
        );

        if (price > 0)
        {
            message.Set(new Price(price));
            message.Set(new OrdType(OrdType.LIMIT));
        }

        message.Set(new OrderQty(quantity));

        Session.SendToTarget(message, sessionID);
    }

Special Considerations for cTrader FIX API

Lots of brokers only open their FIX API to institutional clients, but retail traders still can access FIX API if their brokers provide cTrader platform for trading.

Getting FIX API Credentials

Below are the steps to get the FIX API credentials from cTrader and set up the QuickFIX configuration file.

Basically, what you need to do is replace the SocketConnectHost, SocketConnectPort, SenderCompID, TargetCompID, SenderSubID, and TargetSubID in the configuration file with the information you get from the cTrader's FIX API information.

cTrader FIX API require two channels, one for trading and one for market data. The trading channel is used for placing orders, modifying orders, cancelling orders and getting positions. The market data channel is used for getting market data and getting the list of symbols that are available for trading.

So for each channel, you need to create a separate configuration file. The major difference between the two configuration files is the SenderSubID and Port. The SenderSubID for the trading channel is TRADE, and for the market data channel is QUOTE.

Security Symbol

In cTrader, the symbol for all the request is a unique number, not an actual symbol like you normally trade such as XAUUSD, XBRUSD, AUDUSD, etc. You will need to create a system to map the symbol to the unique number. For example, 1 is EURUSD, 2 is GBPUSD, etc. after getting the list of entire tradable securities.

Building Algorithmic Trading Logic

Once the FIX API connection is established, you can integrate your algorithmic trading strategies within the provided application interface. Implement order routing, risk management, and other trading logic based on your specific requirements.

Conclusion

Incorporating FIX API as a gateway for algorithmic trading in C# provides a standardized and efficient means of communication with financial markets. Selecting an appropriate FIX library, configuring the connection, and integrating algorithmic trading logic enable traders to execute strategies with speed and reliability. This combination of FIX and C# empowers developers to create robust algorithmic trading systems that can navigate the dynamic landscape of financial markets.

Contact

For further information, I can be reach via:

Credits

Photo by Shubham Dhage on Unsplash

agico

We transform visions into reality. We specializes in crafting digital experiences that captivate, engage, and innovate. With a fusion of creativity and expertise, we bring your ideas to life, one pixel at a time. Let's build the future together.

Copyright ©  2024  TYO Lab