Swift/Python
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()
Replies
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
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