Swift/Python

Created at 10 Jan 2025, 14:20
How’s your experience with the cTrader Platform?
Your feedback is crucial to cTrader's development. Please take a few seconds to share your opinion and help us improve your trading experience. Thanks!
AL

algobeginner

Joined 15.12.2024

Swift/Python
10 Jan 2025, 14:20


I'm trying to get Python script to request MarketData into MacOS app , does anyone have some experience how this could be done ? 

I have created config.json as instructed ,  it does work  logging me in and do receive message

 “received:  8=FIX.4.4|9=135|35=W|34=10|49=cServer|50=QUOTE|52=20250110-14:19:04.102|56=demo.ctrader.xxxxxxx|57=QUOTE|55=1|268=2|269=0|270=1.02628|269=1|270=1.0263|10=044| ” 

 

Any idea which Swift Library or conversion to use to create this process in Xcode IDE ?


 

simple EURUSD.py 

from twisted.internet import reactor

import json

from ctrader_fix import *

 

# Callback for receiving all messages

def onMessageReceived(client, responseMessage):

    print("Received: ", responseMessage.getMessage().replace("", "|"))

    messageType = responseMessage.getFieldValue(35)

    if messageType == "A":

        print("We are logged in")

        requestMarketData(client)  # Request market data after logging in

 

# Callback for client disconnection

def disconnected(client, reason):

    print("Disconnected, reason: ", reason)

 

# Callback for client connection

def connected(client):

    print("Connected")

    logonRequest = LogonRequest(config)

    client.send(logonRequest)

 

# Function to request market data for EUR/USD

def requestMarketData(client):

    marketDataRequest = MarketDataRequest(config)

    

    # Set the required fields for the market data request

    marketDataRequest.MDReqID = "EURUSD_MarketData"  # Unique request ID

    marketDataRequest.SubscriptionRequestType = "1"  # 1 = Snapshot + Updates

    marketDataRequest.MarketDepth = "1"  # Depth of market

    marketDataRequest.NoMDEntryTypes = "1"  # Number of MD entry types

    marketDataRequest.MDEntryType = "0"  # 0 = Bid, 1 = Offer

    marketDataRequest.NoRelatedSym = "1"  # Number of related symbols

    marketDataRequest.Symbol = "1"  # The symbol for which you want market data

 

    client.send(marketDataRequest)

    print("Market data request sent for EUR/USD")

 

# Load configuration

with open("config.json") as configFile:

    config = json.load(configFile)

 

client = Client(config["Host"], config["Port"], ssl=config["SSL"])

 

# Setting client callbacks

client.setConnectedCallback(connected)

client.setDisconnectedCallback(disconnected)

client.setMessageReceivedCallback(onMessageReceived)

 

# Starting the client service

client.startService()

reactor.run()

 


 


@algobeginner
Replies

algobeginner
11 Jan 2025, 21:22

Swift code in IDE 

import Cocoa

import Foundation

import Network

 

// Configuration for FIX API

struct FIXConfig {

    var Host: String

    var Port: Int

    var SSL: Bool

    var Username: String

    var Password: String

    var BeginString: String

    var SenderCompID: String

    var SenderSubID: String

    var TargetCompID: String

    var TargetSubID: String

    var HeartBeat: Int

}

 

// Get current timestamp in FIX-compatible format

func getCurrentTimestamp() -> String {

    let dateFormatter = DateFormatter()

    dateFormatter.dateFormat = "yyyyMMdd-HH:mm:ss"

    return dateFormatter.string(from: Date())

}

 

// Calculate the checksum for the message (FIX requires it for integrity)

func generateChecksum(for message: String) -> String {

    let messageBytes = [UInt8](message.utf8)

    let checksum = messageBytes.reduce(0, { $0 + Int($1) }) % 256

    return String(format: "%03d", checksum)

}

 

// Function to handle connection and sending logon message

func connectToServer(config: FIXConfig) -> NWConnection {

    let host = NWEndpoint.Host(config.Host)

    let port = NWEndpoint.Port(integerLiteral: UInt16(config.Port))

    

    let connection = NWConnection(host: host, port: port, using: .tcp)

    

    connection.stateUpdateHandler = { state in

        switch state {

        case .ready:

            print("Connection established. Sending Logon message...")

            sendLogonMessage(connection: connection, config: config)

        case .failed(let error):

            print("Connection failed with error: \(error)")

        default:

            break

        }

    }

    

    connection.start(queue: .global())

    

    return connection

}

 

// Function to send Logon Message

func sendLogonMessage(connection: NWConnection, config: FIXConfig) {

    var logonMessage = """

    8=\(config.BeginString)\u{1}35=A\u{1}49=\(config.SenderCompID)\u{1}56=\(config.TargetCompID)\u{1}34=1\u{1}52=\(getCurrentTimestamp())\u{1}108=\(config.HeartBeat)\u{1}

    """

    

    let checksum = generateChecksum(for: logonMessage)

    logonMessage.append("10=\(checksum)\u{1}")

    

    print("Sending logon message: \(logonMessage)")  // Log the message

    

    let data = Data(logonMessage.utf8)

    

    connection.send(content: data, completion: .contentProcessed({ error in

        if let error = error {

            print("Error sending logon message: \(error)")

        } else {

            print("Logon message sent successfully.")

            requestMarketData(connection: connection, config: config)

        }

    }))

    

    // Start listening for messages after sending logon

    listenForMessages(connection: connection)

}

 

// Function to send Market Data Request for EUR/USD

func requestMarketData(connection: NWConnection, config: FIXConfig) {

    let mdReqID = "EURUSD_MarketData"

    let subscriptionRequestType = "1" // Snapshot + Updates

    let marketDepth = "1" // Top of book

    let noMDEntryTypes = "1" // Number of MD Entry Types

    let mdEntryType = "0" // Bid

    let noRelatedSym = "1" // Number of related symbols

    let symbol = "1" // EUR/USD symbol

    let mdUpdateType = "0" // Full refresh

    

    var marketDataRequest = """

    8=\(config.BeginString)\u{1}35=V\u{1}49=\(config.SenderCompID)\u{1}56=\(config.TargetCompID)\u{1}34=2\u{1}52=\(getCurrentTimestamp())\u{1}262=\(mdReqID)\u{1}

    263=\(subscriptionRequestType)\u{1}264=\(marketDepth)\u{1}265=\(noMDEntryTypes)\u{1}269=\(mdEntryType)\u{1}146=\(noRelatedSym)\u{1}55=\(symbol)\u{1}

    265=\(mdUpdateType)\u{1}

    """

    

    let checksum = generateChecksum(for: marketDataRequest)

    marketDataRequest.append("10=\(checksum)\u{1}")

    

    print("Sending market data request: \(marketDataRequest)")  // Log the message

    

    let data = Data(marketDataRequest.utf8)

    

    connection.send(content: data, completion: .contentProcessed({ error in

        if let error = error {

            print("Error sending market data request: \(error)")

        } else {

            print("Market data request sent successfully.")

        }

    }))

}

 

// Listen for incoming messages and handle logon acknowledgment or market data responses

func listenForMessages(connection: NWConnection) {

    connection.receive(minimumIncompleteLength: 1, maximumLength: 1024) { (data, context, isComplete, error) in

        if let data = data {

            let receivedMessage = String(decoding: data, as: UTF8.self)

            print("Raw received message: \(receivedMessage)")  // Log the raw message

            

            // Print raw bytes

            print("Raw bytes: \(data.map { String(format: "%02x", $0) }.joined(separator: " "))")

 

            

            // Debug: Check if we're receiving an empty message

            if data.isEmpty {

                print("Received an empty message. Waiting for more data...")

            }

            

            // Handle the different message types (A = Logon, V = Market Data)

            let messageType = receivedMessage.split(separator: "\u{1}").first { $0.hasPrefix("35=") }?.split(separator: "=").last ?? ""

            

            if messageType == "A" {

                // Logon acknowledgment received

                print("Logon acknowledgment received!")

                // Now, you can send the market data request here if you haven't yet

            } else if messageType == "V" {

                // Market Data response (Type V)

                print("Received Market Data Update")

                processMarketDataUpdate(receivedMessage)

            } else {

                print("Received unhandled message: \(receivedMessage)")

            }

        }

        

        // Handle when the connection is complete or encounters an error

        if isComplete {

            connection.cancel()

            print("Connection closed.")

        } else if let error = error {

            print("Error receiving message: \(error)")

            connection.cancel()

        } else {

            // Continue listening for more messages

            listenForMessages(connection: connection)

        }

    }

}

 

 

 

// Example: Process Market Data Update

func processMarketDataUpdate(_ message: String) {

    // Print and process the market data response

    print("Processing Market Data Update: \(message)")

    

    // Example: You can extract the bid/ask data from the message.

    // You will need to adjust parsing according to your FIX message format.

    let messageParts = message.split(separator: "\u{1}") // Split by the ASCII delimiter

    for part in messageParts {

        print("Part: \(part)") // Print each part of the response message

    }

}

 

 

// macOS ViewController

class ViewController: NSViewController {

    

    let fixSessionManager = FIXSessionManager()

 

    override func viewDidLoad() {

        super.viewDidLoad()

        

        // Start FIX session after the view is loaded

        fixSessionManager.startFIXSession()

    }

}

 

config 

class FIXSessionManager {

    

    func startFIXSession() {

        let config = FIXConfig(

            Host: "demo-uk-eqx-02.p.ctrader.com",

            Port: 5201,

            SSL: false,

            Username: "1111111",

            Password: "password",

            BeginString: "FIX.4.4",

            SenderCompID: "demo.ctrader.1111111",

            SenderSubID: "QUOTE",

            TargetCompID: "cServer",

            TargetSubID: "QUOTE",

            HeartBeat: 30

        )

        

        let connection = connectToServer(config: config)

        listenForMessages(connection: connection)

    }

}
 

 

still no response from server , any idea how to get this working , I tried this PythonKit but its so buggy it won't load even after environment creation for Python and Hardened Runtime disabled  Library Validation .

 

Yes I tried, googling , AI , but no solution .

 


@algobeginner

algobeginner
12 Jan 2025, 23:23 ( Updated at: 12 Jan 2025, 23:43 )

Update - lot cleaner , need work bodylenght ( 9 )

import Foundation

import Combine

 

class FIXClientViewModel: ObservableObject {

    @Published var connectionStatus: String = "Disconnected"

    @Published var receivedMessage: String = ""

    

    private var client: FIXClient?

    private var config: Config?

    

    init() {

        loadConfig()

    }

    

    func loadConfig() {

        guard let configURL = Bundle.main.url(forResource: "config", withExtension: "json") else {

            print("Config file not found.")

            return

        }

        

        do {

            let data = try Data(contentsOf: configURL)

            let config = try JSONDecoder().decode(Config.self, from: data)

            self.config = config

            setupClient()

        } catch {

            print("Error loading config: \(error)")

        }

    }

    

    func setupClient() {

        guard let config = config else {

            return

        }

        

        client = FIXClient(host: config.Host, port: config.Port, ssl: config.SSL)

        

        client?.setConnectedCallback { [weak self] in

            self?.connectionStatus = "Connected"

        }

        

        client?.setDisconnectedCallback { [weak self] error in

            self?.connectionStatus = error != nil ? "Disconnected" : "Error"

        }

        

        client?.setMessageReceivedCallback { [weak self] message in

            self?.receivedMessage = message.getMessage()

        }

    }

    

    func connectToServer() {

        client?.startService()

    }

    

    struct MessageField {

        let tag: Int

        let value: String

        

    }

 

    func sendLogonMessage() {

        guard let config = config else {

            return

        }

        

        let timestamp = getCurrentUTCTimestamp()

 

        // Define the fields in the correct order for the Logon message (35=A)

        let messageFields: [MessageField] = [

            MessageField(tag: 8, value: config.BeginString),   // 8=FIX.4.4 (BeginString)

            MessageField(tag: 9, value: ""),                   // 9=BodyLength (will be calculated)

            MessageField(tag: 35, value: "A"),                 // 35=A (Logon message type)

            MessageField(tag: 49, value: config.SenderCompID), // 49=SenderCompID (ID of the trading party)

            MessageField(tag: 56, value: config.TargetCompID), // 56=CSERVER (ID of the target component)

            MessageField(tag: 34, value: String(1)),           // 34=1 (MsgSeqNum, sequence number)

            MessageField(tag: 52, value: timestamp),                  // 52=SendingTime (will be set dynamically)

            MessageField(tag: 57, value: "QUOTE"),             // 57=TRADE (TargetSubID)

            MessageField(tag: 50, value: "Quote"),             // 50=Quote (SenderSubID)

            MessageField(tag: 98, value: "0"),                 // 98=0 (EncryptMethod, no encryption)

            MessageField(tag: 108, value: config.HeartBeat),               // 108=30 (Heartbeat interval)

            MessageField(tag: 141, value: "Y"),                // 141=Y (ResetSeqNumFlag)

            MessageField(tag: 553, value: config.Username),    // 553=Username (user ID)

            MessageField(tag: 554, value: config.Password),    // 554=Password (user password)

            MessageField(tag: 10, value: "")                   // 10=Checksum (calculated later)

        ]

        // Create the FIX message object

        var logonMessage = FIXMessage()

 

        // Add fields to the FIX message in the defined order, excluding 9 and 10 for now

        for field in messageFields {

            logonMessage.setField(field.tag, field.value)

        }

 

        // Dynamically set the SendingTime (52) to the current UTC timestamp

        let currentTime = getCurrentUTCTimestamp()

        logonMessage.setField(52, currentTime)  // Set SendingTime (52)

 

        // Now, calculate the message body length (excluding the BodyLength itself, i.e., excluding tag 9)

        let messageString = logonMessage.getMessage()

 

        // Calculate the message length (excluding the BodyLength field itself)

        let messageLength = messageString.count + 1 // Include the Message Length field itself (9)

        let messageLengthString = String(messageLength)

 

        // Now, set the '9' field (BodyLength) in the second position

        logonMessage.setField(9, messageLengthString)  // Add BodyLength as second field

 

        // Calculate checksum (tag 10)

        let checksum = calculateChecksum(for: messageString)

 

        // Set the '10' field with the calculated checksum as the last field

        logonMessage.setField(10, checksum)  // Add checksum at the end of the message

        

        // Generate the fixed-format message string

        let fixedFormatMessage = logonMessage.getMessage()

 

        // Debugging: print the logon message in fixed format

        print("Logon Message (Fixed Format): \(fixedFormatMessage)")

 

        // Send the logon message

        client?.send(logonMessage)

    }

 

            }

    

    // Function to get the current UTC timestamp in the required format (yyyyMMdd-HH:mm:ss)

    func getCurrentUTCTimestamp() -> String {

        let dateFormatter = DateFormatter()

        dateFormatter.dateFormat = "yyyyMMdd-HH:mm:ss"

        dateFormatter.timeZone = TimeZone(abbreviation: "UTC")  // Set the timezone to UTC

        let date = Date()

        return dateFormatter.string(from: date)

        

        

    }

 

    // Example checksum calculation function (you will need to implement this based on your specific needs)

    func calculateChecksum(for message: String) -> String {

        let checksum = message.reduce(0) { $0 + Int($1.asciiValue ?? 0) }

        return String(checksum % 256)

    }

 

    

 

 

 

 

 

 

 

 


 


@algobeginner