Support Board
Date/Time: Sun, 24 Nov 2024 21:20:24 +0000
[Programming Help] - [SC Relay Server] Documentation Needed
View Count: 264
[2024-05-02 15:55:43] |
MichaelPPTF - Posts: 69 |
Hello, I am trying to write a client to use the SC Relay Server functionality to stream data relayed from SC and do some calculations. But I am stuck at the very first step, which is to get ENCODING_REQUEST. Q1: I am trying to understand the procedure used by SC. For example, SC documentation stated that if the client (my script) and the server (SC itself) use the same encoding, I can bypass this step. But how do I know which encoding scheme is SC using? Do I have to send an ENCODING_REQUEST to find out or there's some documentation that I didn't find out about? Q2: As for LOGON_REQUEST, what do I use in the username/password fields? Do I use the username/password for login to SC or something else? Here's how I am packing my requests with Python struct library: format_string = '<HHI32s32s128sIII32s32s32sI'
logon_request = struct.pack( format_string, 0, # Size will be calculated after packing message_type, protocol_version, username, password, general_text_data, integer_1, integer_2, heartbeat_interval, trade_account, hardware_identifier, client_name, market_data_transmission_interval ) That's all I have for now. Without the two steps above I can't even get SC to acknowledge my client as a valid one. Here's what I got from SC: 2024-05-02 08:38:19.356 | DTC client #5. 127.0.0.1 | Received unknown message type from client. Type = 0
|
[2024-05-02 16:43:43] |
d9e5c763 - Posts: 108 |
You can refer to my dtc.py module as a reference. #!/usr/bin/env python3 import numpy import sys import struct from enum import IntEnum UINT_MAX = (1 << 32) - 1 FLT_MAX = numpy.finfo(numpy.float32).max DBL_MAX = numpy.finfo(numpy.float64).max CURRENT_VERSION = 8 class DTCMessageType(IntEnum): LOGON_REQUEST = 1 LOGON_RESPONSE = 2 HEARTBEAT = 3 ENCODING_REQUEST = 6 ENCODING_RESPONSE = 7 MARKET_DATA_REQUEST = 101 MARKET_DATA_SNAPSHOT = 104 MARKET_DATA_UPDATE_SESSION_HIGH = 114 MARKET_DATA_UPDATE_SESSION_LOW = 115 MARKET_DATA_UPDATE_OPEN_INTEREST = 124 MARKET_DATA_UPDATE_BID_ASK_FLOAT_WITH_MICROSECONDS = 144 MARKET_DATA_UPDATE_TRADE_WITH_UNBUNDLED_INDICATOR_2 = 146 MARKET_DEPTH_REQUEST = 102 MARKET_DEPTH_SNAPSHOT_LEVEL = 122 MARKET_DEPTH_UPDATE_LEVEL = 106 MARKET_DATA_FEED_SYMBOL_STATUS = 116 CANCEL_ORDER = 203 SUBMIT_NEW_SINGLE_ORDER = 208 OPEN_ORDERS_REQUEST = 300 ORDER_UPDATE = 301 OPEN_ORDERS_REJECT = 302 HISTORICAL_ORDER_FILLS_REQUEST = 303 HISTORICAL_ORDER_FILL_RESPONSE = 304 CURRENT_POSITIONS_REQUEST = 305 POSITION_UPDATE = 306 TRADE_ACCOUNT_RESPONSE = 401 TRADE_ACCOUNTS_REQUEST = 400 SECURITY_DEFINITION_RESPONSE = 507 ACCOUNT_BALANCE_UPDATE = 600 ACCOUNT_BALANCE_REQUEST = 601 HISTORICAL_ACCOUNT_BALANCES_REQUEST = 603 HISTORICAL_ACCOUNT_BALANCE_RESPONSE = 606 HISTORICAL_PRICE_DATA_REQUEST= 800 HISTORICAL_PRICE_DATA_RESPONSE_HEADER = 801 HISTORICAL_PRICE_DATA_RECORD_RESPONSE = 803 class EncodingEnum(IntEnum): BINARY_ENCODING = 0 BINARY_WITH_VARIABLE_LENGTH_STRINGS = 1 JSON_ENCODING = 2 JSON_COMPACT_ENCODING = 3 PROTOCOL_BUFFERS = 4 class LogonStatusEnum(IntEnum): LOGON_STATUS_UNSET = 0 LOGON_SUCCESS = 1 LOGON_ERROR = 2 LOGON_ERROR_NO_RECONNECT = 3 LOGON_RECONNECT_NEW_ADDRESS = 4 class RequestActionEnum(IntEnum): SUBSCRIBE = 1 UNSUBSCRIBE = 2 SNAPSHOT = 3 SNAPSHOT_WITH_INTERVAL_UPDATES = 4 class OrderStatusEnum(IntEnum): ORDER_STATUS_UNSPECIFIED = 0 ORDER_STATUS_ORDER_SENT = 1 ORDER_STATUS_PENDING_OPEN = 2 ORDER_STATUS_PENDING_CHILD = 3 ORDER_STATUS_OPEN = 4 ORDER_STATUS_PENDING_CANCEL_REPLACE = 5 ORDER_STATUS_PENDING_CANCEL = 6 ORDER_STATUS_FILLED = 7 ORDER_STATUS_CANCELED = 8 ORDER_STATUS_REJECTED = 9 ORDER_STATUS_PARTIALLY_FILLED = 10 class OrderUpdateReasonEnum(IntEnum): ORDER_UPDATE_REASON_UNSET = 0 OPEN_ORDERS_REQUEST_RESPONSE = 1 NEW_ORDER_ACCEPTED = 2 GENERAL_ORDER_UPDATE = 3 ORDER_FILLED = 4 ORDER_FILLED_PARTIALLY = 5 ORDER_CANCELED = 6 ORDER_CANCEL_REPLACE_COMPLETE = 7 NEW_ORDER_REJECTED = 8 ORDER_CANCEL_REJECTED = 9 ORDER_CANCEL_REPLACE_REJECTED = 10 class AtBidOrAskEnum8(IntEnum): BID_ASK_UNSET_8 = 0 AT_BID_8 = 1 AT_ASK_8 = 2 class AtBidOrAskEnum(IntEnum): BID_ASK_UNSET = 0 AT_BID = 1 AT_ASK = 2 class MarketDepthUpdateTypeEnum(IntEnum): MARKET_DEPTH_UNSET = 0 MARKET_DEPTH_INSERT_UPDATE_LEVEL = 1 MARKET_DEPTH_DELETE_LEVEL = 2 class OrderTypeEnum(IntEnum): ORDER_TYPE_UNSET = 0 ORDER_TYPE_MARKET = 1 ORDER_TYPE_LIMIT = 2 ORDER_TYPE_STOP = 3 ORDER_TYPE_STOP_LIMIT = 4 ORDER_TYPE_MARKET_IF_TOUCHED = 5 ORDER_TYPE_LIMIT_IF_TOUCHED = 6 ORDER_TYPE_MARKET_LIMIT = 7 class TimeInForceEnum(IntEnum): TIF_UNSET = 0 TIF_DAY = 1 TIF_GOOD_TILL_CANCELED = 2 TIF_GOOD_TILL_DATE_TIME = 3 TIF_IMMEDIATE_OR_CANCEL = 4 TIF_ALL_OR_NONE = 5 TIF_FILL_OR_KILL = 6 class BuySellEnum(IntEnum): BUY_SELL_UNSET = 0 BUY = 1 SELL = 2 class OpenCloseTradeEnum(IntEnum): TRADE_UNSET = 0 TRADE_OPEN = 1 TRADE_CLOSE = 2 class MarketDataFeedStatusEnum(IntEnum): MARKET_DATA_FEED_STATUS_UNSET = 0 MARKET_DATA_FEED_UNAVAILABLE = 1 MARKET_DATA_FEED_AVAILABLE = 2 class PriceDisplayFormatEnum(IntEnum): PRICE_DISPLAY_FORMAT_DECIMAL_0 = 0 PRICE_DISPLAY_FORMAT_DECIMAL_1 = 1 PRICE_DISPLAY_FORMAT_DECIMAL_2 = 2 PRICE_DISPLAY_FORMAT_DECIMAL_3 = 3 PRICE_DISPLAY_FORMAT_DECIMAL_4 = 4 PRICE_DISPLAY_FORMAT_DECIMAL_5 = 5 PRICE_DISPLAY_FORMAT_DECIMAL_6 = 6 PRICE_DISPLAY_FORMAT_DECIMAL_7 = 7 PRICE_DISPLAY_FORMAT_DECIMAL_8 = 8 PRICE_DISPLAY_FORMAT_DECIMAL_9 = 9 PRICE_DISPLAY_FORMAT_UNSET = -1 class SecurityTypeEnum(IntEnum): SECURITY_TYPE_UNSET = 0 SECURITY_TYPE_FUTURE = 1 SECURITY_TYPE_STOCK = 2 SECURITY_TYPE_FOREX = 3 SECURITY_TYPE_INDEX = 4 class PutCallEnum(IntEnum): PC_UNSET = 0 PC_CALL = 1 PC_PUT = 2 class HistoricalDataIntervalEnum(IntEnum): INTERVAL_TICK = 0 INTERVAL_1_SECOND = 1 INTERVAL_2_SECONDS = 2 INTERVAL_4_SECONDS = 4 INTERVAL_5_SECONDS = 5 INTERVAL_10_SECONDS = 10 INTERVAL_30_SECONDS = 30 INTERVAL_1_MINUTE = 60 INTERVAL_1_DAY = 86400 async def SendDTCMessage(Writer, DTCMessage): try: Writer.write(DTCMessage) await Writer.drain() except ConnectionResetError: return except Exception as e: print(f"SendDTCMessage: {e.__class__.__name__}") async def LogonResponse(Writer, **Data): DefaultValues = { "ProtocolVersion": CURRENT_VERSION, # ProtocolVersion, (int32_t), 4 bytes, i "Result": LogonStatusEnum.LOGON_STATUS_UNSET, # Result, (LogonStatusEnum int32_t), 4 bytes, i "ResultText": b"", # ResultText, (char), 96 bytes "ReconnectAddress": b"", # ReconnectAddress, (char), 64 bytes "Integer1": 0, # Integer1, (int32_t), 4 bytes, i "ServerName": b"", # ServerName, (char), 60 bytes "MarketDepthUpdatesBestBidAndAsk": 0, # MarketDepthUpdatesBestBidAndAsk, (uint8_t), 1 byte, B "TradingIsSupported": 0, # TradingIsSupported, (uint8_t), 1 byte, B "OCOOrdersSupported": 0, # OCOOrdersSupported, (uint8_t), 1 byte, B "OrderCancelReplaceSupported": 0, # OrderCancelReplaceSupported, (uint8_t), 1 byte, B "SymbolExchangeDelimiter": b"", # SymbolExchangeDelimiter, (char), 4 byte "SecurityDefinitionsSupported": 0, # SecurityDefinitionsSupported, (uint8_t), 1 byte, B "HistoricalPriceDataSupported": 0, # HistoricalPriceDataSupported, (uint8_t), 1 byte, B "ResubscribeWhenMarketDataFeedAvailable": 0, # ResubscribeWhenMarketDataFeedAvailable, (uint8_t), 1 byte, B "MarketDepthIsSupported": 0, # MarketDepthIsSupported, (uint8_t), 1 byte, B "OneHistoricalPriceDataRequestPerConnection": 0, # OneHistoricalPriceDataRequestPerConnection, (uint8_t), 1 byte, B "BracketOrdersSupported": 0, # BracketOrdersSupported, (uint8_t), 1 byte, B "UseIntegerPriceOrderMessages": 0, # UseIntegerPriceOrderMessages, (uint8_t), 1 byte, B "UsesMultiplePositionsPerSymbolAndTradeAccount": 0, # UsesMultiplePositionsPerSymbolAndTradeAccount, (uint8_t), 1 byte, B "MarketDataSupported": 0 # MarketDataSupported (uint8_t), 1 byte, B } DefaultValues.update(Data) FormatString = "<HHii96s64si60sBBBB4sBBBBBBBBB" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.LOGON_RESPONSE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def Heartbeat(Writer, **Data): DefaultValues = { "NumDroppedMessages": 0, # NumDroppedMessages, (uint32_t), 4 bytes, I "CurrentDateTimeWithSeconds": 0 # CurrentDateTimeWithSeconds, (t_DateTime int64_t), 8 bytes, q } DefaultValues.update(Data) FormatString = "<HHIq" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.HEARTBEAT, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDataSnapshot(Writer, **Data): DefaultValues = { "SymbolID": 0, # SymbolID, (uint32_t), 4 bytes, I "SessionSettlementPrice": DBL_MAX, # SessionSettlementPrice, (double), 8 bytes, d "SessionOpenPrice": DBL_MAX, # SessionOpenPrice, (double), 8 bytes, d "SessionHighPrice": DBL_MAX, # SessionHighPrice, (double), 8 bytes, d "SessionLowPrice": DBL_MAX, # SessionLowPrice, (double), 8 bytes, d "SessionVolume": DBL_MAX, # SessionVolume, (double), 8 bytes, d "SessionNumTrades": UINT_MAX, # SessionNumTrades, (uint32_t), 4 bytes, I "OpenInterest": UINT_MAX, # OpenInterest, (uint32_t), 4 bytes, I "BidPrice": DBL_MAX, # BidPrice, (double), 8 bytes, d "AskPrice": DBL_MAX, # AskPrice, (double), 8 bytes, d "AskQuantity": DBL_MAX, # AskQuantity, (double), 8 bytes, d "BidQuantity": DBL_MAX, # BidQuantity, (double), 8 bytes, d "LastTradePrice": DBL_MAX, # LastTradePrice, (double), 8 bytes, d "LastTradeVolume": DBL_MAX, # LastTradeVolume, (double), 8 bytes, d "LastTradeDateTimeWithMilliseconds": 0.0, # LastTradeDateTimeWithMilliseconds, (t_DateTimeWithMilliseconds double), 8 bytes, d "BidAskDateTimeWithMilliseconds": 0.0, # BidAskDateTimeWithMilliseconds, (t_DateTimeWithMilliseconds double), 8 bytes, d "SessionSettlementDateTimeWithSeconds": 0, # SessionSettlementDateTimeWithSeconds, (t_DateTime4Byte uint32_t), 4 bytes, I "TradingSessionDateTimeWithSeconds": 0 # TradingSessionDateTimeWithSeconds, (t_DateTime4Byte uint32_t), 4 bytes, I } DefaultValues.update(Data) FormatString = "<HHIdddddIIddddddddII" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DATA_SNAPSHOT, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDataFeedSymbolStatus(Writer, **Data): DefaultValues = { "Status": MarketDataFeedStatusEnum.MARKET_DATA_FEED_STATUS_UNSET, # Status, (MarketDataFeedStatusEnum int32_t), 4 bytes, i "SymbolID": 0 # SymbolID, (uint32_t), 4 bytes, I } DefaultValues.update(Data) FormatString = "<HHiI" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DATA_FEED_SYMBOL_STATUS, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDataUpdateSessionHigh(Writer, **Data): DefaultValues = { "SymbolID": 0, # SymbolID, (uint32_t), 4 bytes, I "Price": 0.0 # Price, (double), 8 bytes, d } DefaultValues.update(Data) FormatString = "<HHIf" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DATA_UPDATE_SESSION_HIGH, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDataUpdateSessionLow(Writer, **Data): DefaultValues = { "SymbolID": 0, # SymbolID, (uint32_t), 4 bytes, I "Price": 0.0 # Price, (double), 8 bytes, d } DefaultValues.update(Data) FormatString = "<HHIf" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DATA_UPDATE_SESSION_LOW, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDataUpdateOpenInterest(Writer, **Data): DefaultValues = { "SymbolID": 0, # SymbolID, (uint32_t), 4 bytes, I "OpenInterest": 0 # OpenInterest, (uint32_t), 4 bytes, I } DefaultValues.update(Data) FormatString = "<HHII" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DATA_UPDATE_OPEN_INTEREST, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDataUpdateBidAskFloatWithMicroseconds(Writer, **Data): DefaultValues = { "SymbolID": 0, # SymbolID, (uint32_t), 4 bytes, I "BidPrice": FLT_MAX, # BidPrice, (float), 4 bytes, f "BidQuantity": 0.0, # BidQuantity, (float), 4 bytes, f "AskPrice": FLT_MAX, # AskPrice, (float), 4 bytes, f "AskQuantity": 0.0, # AskQuantity, (float), 4 bytes, f "DateTime": 0 # DateTime, (t_DateTimeWithMicrosecondsInt int64_t), 8 bytes, q } DefaultValues.update(Data) FormatString = "<HHIffffq" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DATA_UPDATE_BID_ASK_FLOAT_WITH_MICROSECONDS, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDataUpdateTradeWithUnbundledIndicator2(Writer, **Data): DefaultValues = { "SymbolID": 0, # SymbolID, (uint32_t), 4 bytes, I "Price": 0.0, # Price, (float), 4 bytes, f "Volume": 0, # Volume, (uint32_t), 4 bytes, I "DateTime": 0, # DateTime, (t_DateTimeWithMicrosecondsInt int64_t), 8 bytes, q "Side": AtBidOrAskEnum8.BID_ASK_UNSET_8 # AtBidOrAsk, (AtBidOrAskEnum8 uint8_t), 2 byte, B } DefaultValues.update(Data) FormatString = "<HHIfIqB" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DATA_UPDATE_TRADE_WITH_UNBUNDLED_INDICATOR_2, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDepthSnapshotLevel(Writer, **Data): DefaultValues = { "SymbolID": 0, # SymbolID, (uint32_t), 4 bytes, i "Side": AtBidOrAskEnum.BID_ASK_UNSET, # AtBidOrAsk, (AtBidOrAskEnum int16_t), 8 bytes, q "Price": 0.0, # Price, (double), 8 bytes, d "Quantity": 0.0, # Quantity, (double), 8 bytes, d "Level": 0, # Level (uint16_t), 4 bytes, H "IsFirstMessageInBatch": 0, # IsFirstMessageInBatch, (FinalUpdateInBatchEnum uint8_t), 2 byte, B "IsLastMessageInBatch": 0, # IsLastMessageInBatch, (uint8_t), 1 byte, B "DateTimeWithMilliseconds": 0.0, # DateTimeWithMilliseconds, (t_DateTimeWithMilliseconds double), 8 bytes, d "NumOrders": 0 # NumOrders, (uint32_t), 4 bytes, I } DefaultValues.update(Data) FormatString = "<HHiqddHBB4xdI" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DEPTH_SNAPSHOT_LEVEL, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def MarketDepthUpdateLevel(Writer, **Data): DefaultValues = { "SymbolID": 0, # SymbolID, (uint32_t), 4 bytes, i "Side": AtBidOrAskEnum.BID_ASK_UNSET, # AtBidOrAsk, (AtBidOrAskEnum int16_t), 8 bytes, q "Price": 0.0, # Price, (double), 8 bytes, d "Quantity": 0.0, # Quantity, (double), 8 bytes, d "UpdateType": MarketDepthUpdateTypeEnum.MARKET_DEPTH_UNSET, # UpdateType, (MarketDepthUpdateTypeEnum uint8_t), 2 byte, B "DateTimeWithMilliseconds": 0.0, # DateTimeWithMilliseconds, (t_DateTimeWithMilliseconds double), 8 bytes, d "NumOrders": 0 # NumOrders, (uint32_t), 4 bytes, I } DefaultValues.update(Data) FormatString = "<HHiqddB7xdI" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.MARKET_DEPTH_UPDATE_LEVEL, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def OrderUpdate(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "TotalNumberMessages": 1, # TotalNumberMessages, (int32_t), 4 bytes, i "MessageNumber": 1, # MessageNumber, (int32_t), 4 bytes, i "Symbol": b"", # Symbol, (char), 64 bytes "Exchange": b"", # Exchange, (char), 16 bytes "PreviousServerOrderID": b"", # PreviousServerOrderID, (char), 32 bytes "ServerOrderID": b"", # ServerOrderID, (char), 32 bytes "ClientOrderID": b"", # ClientOrderID, (char), 32 bytes "ExchangeOrderID": b"", # ExchangeOrderID, (char), 32 bytes "OrderStatus": OrderStatusEnum.ORDER_STATUS_UNSPECIFIED, # OrderStatus, (OrderStatusEnum int32_t), 4 bytes, i "OrderUpdateReason": OrderUpdateReasonEnum.ORDER_UPDATE_REASON_UNSET, # OrderUpdateReason, (OrderUpdateReasonEnum int32_t), 4 bytes, i "OrderType": OrderTypeEnum.ORDER_TYPE_UNSET, # OrderType, (OrderTypeEnum int32_t), 4 bytes, i "BuySell": BuySellEnum.BUY_SELL_UNSET, # BuySell, (BuySellEnum int32_t), 4 bytes, i "Price1": DBL_MAX, # Price1, (double), 8 bytes, d "Price2": DBL_MAX, # Price2, (double), 8 bytes, d "TimeInForce": TimeInForceEnum.TIF_UNSET, # TimeInForce, (TimeInForceEnum int32_t), 4 bytes, i "GoodTillDateTimeWithSeconds": 0, # GoodTillDateTimeWithSeconds, (t_DateTime int64_t), 8 bytes, q "OrderQuantity": DBL_MAX, # OrderQuantity, (double), 8 bytes, d "FilledQuantity": DBL_MAX, # FilledQuantity, (double), 8 bytes, d "RemainingQuantity": DBL_MAX, # RemainingQuantity, (double), 8 bytes, d "AverageFillPrice": DBL_MAX, # AverageFillPrice, (double), 8 bytes, d "LastFillPrice": DBL_MAX, # LastFillPrice, (double), 8 bytes, d "LastFillDateTimeWithSeconds": 0, # LastFillDateTimeWithSeconds, (t_DateTime int64_t), 8 bytes, q "LastFillQuantity": DBL_MAX, # LastFillQuantity, (double), 8 bytes, d "LastFillExecutionID": b"", # LastFillExecutionID, (char), 64 bytes "TradeAccount": b"", # TradeAccount, (char), 32 bytes "InfoText": b"", # InfoText, (char), 96 bytes "NoOrders": 0, # NoOrders, (uint8_t), 1 byte, B "ParentServerOrderID": b"", # ParentServerOrderID, (char), 32 bytes "OCOLinkedOrderServerOrderID": b"", # OCOLinkedOrderServerOrderID, (char), 32 bytes "OpenOrClose": OpenCloseTradeEnum.TRADE_UNSET, # OpenOrClose, (OpenCloseTradeEnum int32_t), 4 bytes, i "PreviousClientOrderID": b"", # PreviousClientOrderID, (char), 32 bytes "FreeFormText": b"", # FreeFormText, (char), 48 bytes "OrderReceivedDateTimeWithSeconds": 0, # OrderReceivedDateTimeWithSeconds, (t_DateTime int64_t), 8 bytes, q "LatestTransactionDateTimeWithMilliseconds": 0.0 # LatestTransactionDateTimeWithMilliseconds, (t_DateTimeWithMilliseconds double), 8 bytes, d } DefaultValues.update(Data) FormatString = "<HHiii64s16s32s32s32s32siiiiddi4xqdddddqd64s32s96sB32s32s3xi32s48sqd" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.ORDER_UPDATE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def OpenOrdersReject(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "RejectText": b"", # RejectText, (char), 96 bytes } DefaultValues.update(Data) FormatString = "<HHi96s" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.OPEN_ORDERS_REJECT, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def HistoricalOrderFillResponse(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "TotalNumberMessages": 1, # TotalNumberMessages, (int32_t), 4 bytes, i "MessageNumber": 1, # MessageNumber, (int32_t), 4 bytes, i "Symbol": b"", # Symbol, (char), 64 bytes "Exchange": b"", # Exchange, (char), 16 bytes "ServerOrderID": b"", # ServerOrderID, (char), 32 bytes "BuySell": BuySellEnum.BUY_SELL_UNSET, # BuySell, (BuySellEnum int32_t), 4 bytes, i "Price": 0.0, # Price, (double), 8 bytes, d "DateTimeWithSeconds": 0, # DateTimeWithSeconds, (t_DateTime int64_t), 8 bytes, q "Quantity": 0.0, # Quantity, (double), 8 bytes, d "UniqueExecutionID": b"", # UniqueExecutionID, (char), 64 bytes "TradeAccount": b"", # TradeAccount, (char), 32 bytes "OpenClose": OpenCloseTradeEnum.TRADE_UNSET, # OpenClose, (BuySellEnum int32_t), 4 bytes, i "NoOrderFills": 0 # NoOrderFills, (uint8_t), 1 byte, B } DefaultValues.update(Data) FormatString = "<HHiii64s16s32si4xdqd64s32siB" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.HISTORICAL_ORDER_FILL_RESPONSE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def PositionUpdate(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "TotalNumberMessages": 1, # TotalNumberMessages, (int32_t), 4 bytes, i "MessageNumber": 1, # MessageNumber, (int32_t), 4 bytes, i "Symbol": b"", # Symbol, (char), 64 bytes "Exchange": b"", # Exchange, (char), 16 bytes "Quantity": 0.0, # Quantity, (double), 8 bytes, d "AveragePrice": 0.0, # AveragePrice, (double), 8 bytes, d "PositionIdentifier": b"", # PositionIdentifier, (char), 32 bytes "TradeAccount": b"", # TradeAccount, (char), 32 bytes "NoPositions": 0, # NoPositions, (uint8_t), 1 byte, B "Unsolicited": 0, # Unsolicited, (uint8_t), 1 byte, B "MarginRequirement": 0.0, # MarginRequirement, (double), 8 bytes, d "EntryDateTimeWithSeconds": 0, # EntryDateTimeWithSeconds, (t_DateTime4Byte uint32_t), 4 bytes, I "OpenProfitLoss": 0.0, # OpenProfitLoss, (double), 8 bytes, d "HighPriceDuringPosition": 0.0, # HighPriceDuringPosition, (double), 8 bytes, d "LowPriceDuringPosition": 0.0 # LowPriceDuringPosition, (double), 8 bytes, d } DefaultValues.update(Data) FormatString = "<HHiii64s16sdd32s32sBB6xdI4xddd" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.POSITION_UPDATE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def TradeAccountResponse(Writer, **Data): DefaultValues = { "TotalNumberMessages": 1, # TotalNumberMessages, (int32_t), 4 bytes, i "MessageNumber": 1, # MessageNumber, (int32_t), 4 bytes, i "TradeAccount": b"", # TradeAccount, (char), 32 bytes "RequestID": 0 # RequestID, (int32_t), 4 bytes, i } DefaultValues.update(Data) FormatString = "<HHii32si" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.TRADE_ACCOUNT_RESPONSE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def SecurityDefinitionResponse(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "Symbol": b"", # Symbol, (char), 64 bytes "Exchange": b"", # Exchange, (char), 16 bytes "SecurityType": SecurityTypeEnum.SECURITY_TYPE_UNSET, # SecurityType, (SecurityTypeEnum int32_t), 4 bytes, i "Description": b"", # Description, (char), 64 bytes "MinPriceIncrement": 0.0, # MinPriceIncrement, (float), 4 bytes, f "PriceDisplayFormat": PriceDisplayFormatEnum.PRICE_DISPLAY_FORMAT_UNSET, # PriceDisplayFormat, (PriceDisplayFormatEnum int32_t), 4 bytes, i "CurrencyValuePerIncrement": 0.0, # CurrencyValuePerIncrement, (float), 4 bytes, f "IsFinalMessage": 0, # IsFinalMessage, (uint8_t), 1 byte, B "FloatToIntPriceMultiplier": 1.0, # FloatToIntPriceMultiplier, (float), 4 bytes, f "IntToFloatPriceDivisor": 1.0, # IntegerToFloatPriceDivisor, (float), 4 bytes, f "UnderlyingSymbol": b"", # UnderlyingSymbol, (char), 32 bytes "UpdatesBidAskOnly": 0, # UpdatesBidAskOnly, (uint8_t), 1 byte, B "StrikePrice": 0.0, # StrikePrice, (float), 4 bytes, f "PutOrCall": PutCallEnum.PC_UNSET, # PutOrCall, (PutCallEnum int32_t), 4 bytes, i "ShortInterest": 0, # ShortInterest, (uint32_t), 4 bytes, I "SecurityExpirationDate": 0, # SecurityExpirationDate, (t_DateTime4Byte uint32_t), 4 bytes, I "BuyRolloverInterest": 0.0, # BuyRolloverInterest, (float), 4 bytes, f "SellRolloverInterest": 0.0, # SellRolloverInterest, (float), 4 bytes, f "EarningsPerShare": 0.0, # EarningsPerShare, (float), 4 bytes, f "SharesOutstanding": 0, # SharesOutstanding, (uint32_t), 4 bytes, I "IntToFloatQuantityDivisor": 0.0, # IntToFloatQuantityDivisor, (float), 4 bytes, f "HasMarketDepthData": 1, # HasMarketDepthData, (uint8_t), 1 byte, B "DisplayPriceMultiplier": 1.0, # DisplayPriceMultiplier, (float), 4 bytes, f "ExchangeSymbol": b"", # ExchangeSymbol, (char), 64 bytes "RolloverDate": 0, # RolloverDate, (t_DateTime4Byte uint32_t), 4 bytes, I "InitialMarginRequirement": 0.0, # InitialMarginRequirement, (float), 4 bytes, f "MaintenanceMarginRequirement": 0.0, # MaintenanceMarginRequirement, (float), 4 bytes, f "Currency": b"", # Currency, (char), 8 bytes "ContractSize": 0.0, # ContractSize, (float), 4 bytes, f "OpenInterest": 0, # OpenInterest, (uint32_t), 4 bytes, I "IsDelayed": 0 # IsDelayed, (uint8_t), 1 byte, B } DefaultValues.update(Data) FormatString = "<HHi64s16si64sfifB3xff32sB3xfiIIfffIfB3xf64sIff8sfIB" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.SECURITY_DEFINITION_RESPONSE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def AccountBalanceUpdate(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "CashBalance": 0.0, # CashBalance, (double), 8 bytes, d "BalanceAvailableForNewPositions": 0.0, # BalanceAvailableForNewPositions, (double), 8 bytes, d "AccountCurrency": b"", # AccountCurrency, (char), 8 bytes "TradeAccount": b"", # TradeAccount, (char), 32 bytes "SecuritiesValue": 0.0, # SecuritiesValue, (double), 8 bytes, d "MarginRequirement": 0.0, # MarginRequirement, (double), 8 bytes, d "TotalNumberMessages": 1, # TotalNumberMessages, (int32_t), 4 bytes, i "MessageNumber": 1, # MessageNumber, (int32_t), 4 bytes, i "NoAccountBalances": 0, # NoAccountBalances, (uint8_t), 1 byte, B "Unsolicited": 0, # Unsolicited, (uint8_t), 1 byte, B "OpenPositionsProfitLoss": 0.0, # OpenPositionsProfitLoss, (double), 8 bytes, d "DailyProfitLoss": 0.0, # DailyProfitLoss, (double), 8 bytes, d "InfoText": b"" # InfoText, (char), 96 bytes } DefaultValues.update(Data) FormatString = "<HHidd8s32sddiiBBdd96s" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.ACCOUNT_BALANCE_UPDATE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def HistoricalAccountBalanceResponse(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "DateTimeWithMilliseconds": 0.0, # DateTimeWithMilliseconds, (t_DateTimeWithMilliseconds double), 8 bytes, d "CashBalance": 0.0, # CashBalance, (double), 8 bytes, d "AccountCurrency": b"", # AccountCurrency, (char), 8 bytes "TradeAccount": b"", # TradeAccount, (char), 32 bytes "IsFinalResponse": 0, # IsFinalResponse, (uint8_t), 1 byte, B "NoAccountBalances": 0, # NoAccountBalances, (uint8_t), 1 byte, B "InfoText": b"", # InfoText, (char), 96 bytes "TransactionId": b"" # TransactionId, (char), 96 bytes } DefaultValues.update(Data) FormatString = "<HHidd8s32sBB96s96s" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.HISTORICAL_ACCOUNT_BALANCE_RESPONSE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def HistoricalPriceDataResponseHeader(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "RecordInterval": HistoricalDataIntervalEnum.INTERVAL_TICK, # RecordInterval, (HistoricalDataIntervalEnum int32_t), 4 bytes, i "UseZLibCompression": 0, # UseZLibCompression, (uint8_t), 1 byte, B "NoRecordsToReturn": 0 # NoRecordsToReturn, (uint8_t), 1 byte, B } DefaultValues.update(Data) FormatString = "<HHiiBB" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.HISTORICAL_PRICE_DATA_RESPONSE_HEADER, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) async def HistoricalPriceDataRecordResponse(Writer, **Data): DefaultValues = { "RequestID": 0, # RequestID, (int32_t), 4 bytes, i "StartDateTime": 0, # StartDateTime, (t_DateTimeWithMicrosecondsInt int64_t), 8 bytes, q "OpenPrice": 0.0, # OpenPrice, (double), 8 bytes, d "HighPrice": 0.0, # HighPrice, (double), 8 bytes, d "LowPrice": 0.0, # LowPrice, (double), 8 bytes, d "LastPrice": 0.0, # LastPrice, (double), 8 bytes, d "Volume": 0.0, # Volume, (double), 8 bytes, d "Union": 0, # Union, (OpenInterest or NumTrades), (uint32_t), 4 bytes, I "BidVolume": 0.0, # BidVolume, (double), 8 bytes, d "AskVolume": 0.0, # AskVolume, (double), 8 bytes, d "IsFinalRecord": 0 # IsFinalRecord, (uint8_t), 1 byte, B } if "OpenInterest" in Data: Data["Union"] = Data["OpenInterest"] del Data["OpenInterest"] elif "NumTrades" in Data: Data["Union"] = Data["NumTrades"] del Data["NumTrades"] DefaultValues.update(Data) FormatString = "<HHiq5dI4xddB" DTCMessage = struct.pack( FormatString, struct.calcsize(FormatString), DTCMessageType.HISTORICAL_PRICE_DATA_RECORD_RESPONSE, *DefaultValues.values() ) await SendDTCMessage(Writer, DTCMessage) |
[2024-05-02 16:56:40] |
d9e5c763 - Posts: 108 |
and the code related to handling LOGON_REQUEST: (MessageSize,) = struct.unpack("<H", await Reader.readexactly(struct.calcsize("<H"))) MessageData = await Reader.readexactly(MessageSize - struct.calcsize("<H")) Type, = StructUnpack("<H", MessageData, MessageSize) if Type == dtc.DTCMessageType.ENCODING_REQUEST: Buffer = b"" _, ProtocolVersion, Encoding, ProtocolType = StructUnpack("<Hii4s", MessageData, MessageSize) ProtocolType = ProtocolType.decode().strip("\x00") if VerboseMode: print(f"EncodingRequest[Binary][{Port}] - ProtocolVersion: {ProtocolVersion}") print(f"EncodingRequest[Binary][{Port}] - Encoding: {Encoding}") print(f"EncodingRequest[Binary][{Port}] - ProtocolType: {ProtocolType}") MessageSize = struct.calcsize("<HHii4s") Type = dtc.DTCMessageType.ENCODING_RESPONSE ProtocolVersion = dtc.CURRENT_VERSION Encoding = dtc.EncodingEnum.BINARY_ENCODING ProtocolType = "DTC".encode() EncodingResponse = struct.pack("<HHii4s", MessageSize, Type, ProtocolVersion, Encoding, ProtocolType) Writer.write(EncodingResponse) await Writer.drain() else: Writer.close() return while True: IndexOnly = False Data = await Reader.read(1024) if not Data: break Buffer += Data while len(Buffer) >= 2: MessageSize, = struct.unpack("<H", Buffer[:2]) if len(Buffer) < MessageSize: break MessageData = Buffer[2:MessageSize] Buffer = Buffer[MessageSize:] Type, = struct.unpack("<H", MessageData[:2]) if Type == dtc.DTCMessageType.LOGON_REQUEST: _, ProtocolVersion, Username, Password, GeneralTextData, \ Integer1, Integer2, HeartbeatIntervalInSeconds, TradeAccount, \ HardwareIdentifier, ClientName, MarketDataTransmissionInterval = StructUnpack("<Hi32s32s64siii4x32s64s32si", MessageData, MessageSize) # Type, (uint16_t), 2 bytes, H # ProtocolVersion, (int32_t), 4 bytes, i # Username, (char), 32 bytes # Password, (char), 32 bytes # GeneralTextData, (char), 64 bytes # Integer1, (int32_t), 4 bytes, i # Integer2, (int32_t), 4 bytes, i # HeartbeatIntervalInSeconds, (int32_t), 4 bytes, i # TradeAccount, (char), 32 bytes # HardwareIdentifier, (char), 64 bytes # ClientName, (char), 32 bytes # MarketDataTransmissionInterval, (int32_t), 4 bytes, i Username = Username.decode().strip("\x00") Password = Password.decode().strip("\x00") GeneralTextData = GeneralTextData.decode().strip("\x00") TradeAccount = TradeAccount.decode().strip("\x00") HardwareIdentifier = HardwareIdentifier.decode().strip("\x00") ClientName = ClientName.decode().strip("\x00") if VerboseMode: print(f"LogonRequest[Binary][{Port}] - ProtocolVersion: {ProtocolVersion}") print(f"LogonRequest[Binary][{Port}] - Username: {Username}") print(f"LogonRequest[Binary][{Port}] - Password: {Password}") print(f"LogonRequest[Binary][{Port}] - GeneralTextData: {GeneralTextData}") print(f"LogonRequest[Binary][{Port}] - Integer1: {Integer1}") print(f"LogonRequest[Binary][{Port}] - Integer2: {Integer2}") print(f"LogonRequest[Binary][{Port}] - HeartbeatIntervalInSeconds: {HeartbeatIntervalInSeconds}") print(f"LogonRequest[Binary][{Port}] - TradeAccount: {TradeAccount}") print(f"LogonRequest[Binary][{Port}] - HardwareIdentifier: {HardwareIdentifier}") print(f"LogonRequest[Binary][{Port}] - ClientName: {ClientName}") print(f"LogonRequest[Binary][{Port}] - MarketDataTransmissionInterval: {MarketDataTransmissionInterval}") |
[2024-05-02 17:52:55] |
MichaelPPTF - Posts: 69 |
and the code related to handling LOGON_REQUEST:
Thank you for the code. So, I guess my next question will be: In order to facilitate data streaming, what needs to happen first and in what order? Update: I've gotten this far so SC will Acknowledge my client. import socket
import json import threading import time def create_logon_request(): logon_request = { "Type": 1, # Assuming Type 1 is for LOGON_REQUEST "ProtocolVersion": 8, "HeartbeatIntervalInSeconds": 10, } return json.dumps(logon_request).encode('utf-8') + b'\x00' def create_heartbeat(): heartbeat = { "Type": 3, # Assuming Type 3 is for HEARTBEAT } return json.dumps(heartbeat).encode('utf-8') + b'\x00' def send_heartbeat(sock): while True: try: sock.send(create_heartbeat()) print("Heartbeat sent.") except socket.error as e: print("Socket error:", e) break time.sleep(10) # Send heartbeat every 10 seconds def receive_response(sock): response = b"" try: while True: part = sock.recv(1024) if part: response += part if response.endswith(b'\x00'): print("Received response:", response.decode('utf-8').strip('\x00')) response = b"" # Reset the buffer after processing else: break except socket.error as e: print("Socket error:", e) def main(): host = '127.0.0.1' port = 11099 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) print("Connected to Sierra Chart Relay Server") # Send logon request s.send(create_logon_request()) print("Logon request sent.") # Start the heartbeat thread heartbeat_thread = threading.Thread(target=send_heartbeat, args=(s,)) heartbeat_thread.start() # Handle responses receive_response(s) # Wait for the heartbeat thread to finish (if ever) heartbeat_thread.join() if __name__ == "__main__": main() This is what SC gave me back: Received response: { "Type":3, "NumDroppedMessages":0, "CurrentDateTime":1714676026, "SecondsSinceLastReceivedHeartbeat":9, "NumberOfOutstandingSentBuffers":0, "PendingTransmissionDelayInMilliseconds":0, "CurrentSendBufferSizeInBytes":0, "SendingDateTimeMicroseconds":1714676026493516, "DataCompressionRatio":0, "TotalUncompressedBytes":3016, "TotalCompressionTime":0, "NumberOfCompressions":0, "SourceIPAddress":2130706433, "MaximumSendBufferSizeInBytes":0, "MaximumSendBufferSizeInBytesDateTime":0 } How do I start to get data from it? The doc said I can't send a MARKET_DATA_REQUEST to SC, so how would I get what is being relayed? For example, I have a real time quote board with AAPL and MSFT on it. How can I stream the data from SC Relay Server? Date Time Of Last Edit: 2024-05-02 18:59:21
|
[2024-05-03 14:28:16] |
MichaelPPTF - Posts: 69 |
Update: May 3, 2024 As it turns out, this is an important field that I forgot to set in my last response: Send a LOGON_REQUEST message with the LOGON_REQUEST::Integer_1 field set to 0x2. This causes the second bit of the integer to be true. It is this message with this flag which puts the DTC Protocol Server connection into Relay Server mode.
This is the revised code: import socket import json import threading import time def create_logon_request(): logon_request = { "Type": 1, "ProtocolVersion": 8, "HeartbeatIntervalInSeconds": 10, "Integer_1": 0x2, } return json.dumps(logon_request).encode('utf-8') + b'\x00' def create_heartbeat(): heartbeat = { "Type": 3, } return json.dumps(heartbeat).encode('utf-8') + b'\x00' def send_heartbeat(sock): while True: try: sock.send(create_heartbeat()) print("Heartbeat sent.") except socket.error as e: print("Socket error:", e) break time.sleep(10) # Send heartbeat every 10 seconds def receive_response(sock): response = b"" try: while True: part = sock.recv(1024) if part: response += part # Process each complete message if response.endswith(b'\x00'): # Remove the null terminator and decode the message complete_message = response[:-1].decode('utf-8') process_message(complete_message) response = b"" # Reset the buffer after processing else: break except socket.error as e: print("Socket error:", e) def process_message(message): try: msg_json = json.loads(message) # Example of handling different types of messages if msg_json['Type'] == 3: print("Heartbeat received.") elif msg_json['Type'] == 101: print("Market data received:", msg_json) else: print("Received message:", msg_json) except json.JSONDecodeError as e: print("Error decoding JSON:", e) except KeyError: print("Received unknown message type:", message) def main(): host = '127.0.0.1' port = 11099 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) print("Connected to Sierra Chart Relay Server") # Send logon request s.send(create_logon_request()) print("Logon request sent.") # Start the heartbeat thread heartbeat_thread = threading.Thread(target=send_heartbeat, args=(s,)) heartbeat_thread.start() # Handle responses receive_response(s) # Wait for the heartbeat thread to finish (if ever) heartbeat_thread.join() if __name__ == "__main__": main() ...and here's what I get back from SC via console output: Error decoding JSON: Extra data: line 1 column 281 (char 280) Error decoding JSON: Extra data: line 1 column 146 (char 145) Error decoding JSON: Extra data: line 1 column 88 (char 87) Error decoding JSON: Extra data: line 1 column 87 (char 86) Error decoding JSON: Extra data: line 1 column 88 (char 87) Error decoding JSON: Extra data: line 1 column 87 (char 86) Received message: {'Type': 112, 'SymbolID': 10, 'AtBidOrAsk': 2, 'Price': 1530, 'Volume': 1, 'DateTime': 1714745795} Error decoding JSON: Extra data: line 1 column 87 (char 86) Received message: {'Type': 112, 'SymbolID': 10, 'AtBidOrAsk': 2, 'Price': 1530, 'Volume': 1, 'DateTime': 1714745796} Error decoding JSON: Extra data: line 1 column 88 (char 87) Error decoding JSON: Extra data: line 1 column 88 (char 87) Error decoding JSON: Extra data: line 1 column 87 (char 86) Received message: {'Type': 112, 'SymbolID': 10, 'AtBidOrAsk': 1, 'Price': 1528, 'Volume': 1, 'DateTime': 1714745796} Error decoding JSON: Extra data: line 1 column 87 (char 86) Received message: {'Type': 112, 'SymbolID': 10, 'AtBidOrAsk': 1, 'Price': 1528, 'Volume': 1, 'DateTime': 1714745796} Error decoding JSON: Extra data: line 1 column 87 (char 86) Received message: {'Type': 112, 'SymbolID': 10, 'AtBidOrAsk': 1, 'Price': 1528, 'Volume': 1, 'DateTime': 1714745797} Error decoding JSON: Extra data: line 1 column 87 (char 86) Received message: {'Type': 112, 'SymbolID': 10, 'AtBidOrAsk': 1, 'Price': 1528, 'Volume': 1, 'DateTime': 1714745797} ... As you can see, there are some messages that are decoded correctly but a lot of which arent. I've setup SC to use JSON Encoding: Encoding:JSON
This setting doesn't appear to affect the outcome: Automatically use JSON Compact Encoding for Websocket Connections:No And I don't know why stock data can't be relayed: 2024-05-03 07:25:51.195 | DTC client #31. 127.0.0.1 | Not sending ESM24_FUT_CME security definition to the client. Market data not allowed to be relayed for symbol.
|
[2024-05-03 14:51:07] |
MichaelPPTF - Posts: 69 |
Another update: So, it is necessary to decode the messages correctly. Here's a fully working sample Python client that connects to a localhost SC Relay Server to output whatever is being (and allowed to be) relayed. You can use this code to try it out, it is mesmerizing to see all the console outputs. You don't need to install any extra dependencies since they are all included in standard Python installation. import socket import json import threading import time def create_logon_request(): logon_request = { "Type": 1, "ProtocolVersion": 8, "HeartbeatIntervalInSeconds": 10, "Integer_1": 0x2, } return json.dumps(logon_request).encode('utf-8') + b'\x00' def create_heartbeat(): heartbeat = { "Type": 3, } return json.dumps(heartbeat).encode('utf-8') + b'\x00' def send_heartbeat(sock): while True: try: sock.send(create_heartbeat()) print("Heartbeat sent.") except socket.error as e: print("Socket error:", e) break time.sleep(10) # Send heartbeat every 10 seconds def receive_response(sock): response = b"" try: while True: part = sock.recv(1024) if part: response += part # Check if there's more than one JSON message in the buffer while b'\x00' in response: pos = response.find(b'\x00') # Find the position of the null terminator single_message = response[:pos] # Extract one JSON message process_message(single_message.decode('utf-8')) # Decode and process this message response = response[pos + 1:] # Remove the processed message from the buffer else: break except socket.error as e: print("Socket error:", e) def process_message(message): try: msg_json = json.loads(message) if msg_json['Type'] == 3: print("Heartbeat received.") elif msg_json['Type'] == 101: print("Market data received:", msg_json) elif msg_json['Type'] == 507: symbol = json.dumps(msg_json) print (symbol) else: print("Received message:", msg_json) except json.JSONDecodeError as e: print("Error decoding JSON:", e) except KeyError: print("Received unknown message type:", message) except ValueError as e: print("Value error:", e) def main(): host = '127.0.0.1' port = 11099 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) print("Connected to Sierra Chart Relay Server") # Send logon request s.send(create_logon_request()) print("Logon request sent.") # Start the heartbeat thread heartbeat_thread = threading.Thread(target=send_heartbeat, args=(s,)) heartbeat_thread.start() # Handle responses receive_response(s) # Wait for the heartbeat thread to finish (if ever) heartbeat_thread.join() if __name__ == "__main__": main() |
To post a message in this thread, you need to log in with your Sierra Chart account: