Login Page - Create Account

Support Board


Date/Time: Sat, 18 May 2024 13:23:22 +0000



[Programming Help] - [SC Relay Server] Documentation Needed

View Count: 129

[2024-05-02 15:55:43]
MichaelPPTF - Posts: 68
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: 87
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: 87
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: 68
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: 68
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: 68
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:

Login

Login Page - Create Account