From 14a69735991ef53943520ba8b5cc71ebe2276115 Mon Sep 17 00:00:00 2001 From: moshferatu Date: Fri, 22 Sep 2023 08:36:08 -0700 Subject: [PATCH] Add script to download historical tick data from Sierra Chart's DTC server --- download_historical_tick_data.py | 120 +++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 download_historical_tick_data.py diff --git a/download_historical_tick_data.py b/download_historical_tick_data.py new file mode 100644 index 0000000..fe085e4 --- /dev/null +++ b/download_historical_tick_data.py @@ -0,0 +1,120 @@ +############################################################################################################ +# Not using this as it turns out you can't download historical tick data from CME Group exchanges via DTC. # +# Reference: https://www.sierrachart.com/index.php?page=doc/DTCServer.php#Restrictions # +############################################################################################################ + +import socket +import json + +from dotenv import load_dotenv +from os import getenv + +load_dotenv() + +# DTC Protocol Constants +ENCODING_REQUEST = 6 +HISTORICAL_PRICE_DATA_REQUEST = 800 +INTERVAL_TICK = 0 +LOGON_REQUEST = 1 +LOGON_RESPONSE = 2 +LOGON_SUCCESS = 1 + +NULL_TERMINATOR = b'\x00' + +class EncodingRequest: + def __init__(self): + self.Type = ENCODING_REQUEST + self.ProtocolVersion = 8 # Set desired DTC Protocol version. + self.Encoding = 2 # Set desired encoding (JSON). + self.ProtocolType = 'DTC' + + def to_json(self): + return json.dumps(self.__dict__) + +class LogonRequest: + def __init__(self, username, password): + self.Type = LOGON_REQUEST + self.ProtocolVersion = 8 # Set desired DTC Protocol version. + self.Username = username + self.Password = password + self.HeartbeatIntervalInSeconds = 30 + self.ClientName = 'Python DTC Client' + + def to_json(self): + return json.dumps(self.__dict__) + +def authenticate_with_sierra_chart(client, username, password): + print('Sending Encoding Request') + encoding_request = EncodingRequest() + encoded_request = encoding_request.to_json().encode() + NULL_TERMINATOR + client.sendall(encoded_request) + + encoding_response = client.recv(4096).decode().rstrip(NULL_TERMINATOR) + print('Encoding Response: ', encoding_response) + + print('Sending Logon Request') + logon_request = LogonRequest(username, password) + encoded_request = logon_request.to_json().encode('ascii') + NULL_TERMINATOR + client.sendall(encoded_request) + + response = client.recv(4096).decode().rstrip(NULL_TERMINATOR) + print('Logon Response: ', response) + data = json.loads(response) + return data['Type'] == LOGON_RESPONSE and data.get('Result', -1) == LOGON_SUCCESS + +class HistoricalPriceDataRequest: + def __init__(self, symbol, exchange, start_date, end_date): + self.Type = HISTORICAL_PRICE_DATA_REQUEST + self.RequestID = 1 + self.Symbol = symbol + self.Exchange = exchange + self.RecordInterval = INTERVAL_TICK + self.StartDateTime = start_date + self.EndDateTime = end_date + self.MaxDaysToReturn = 0 + self.UseZLibCompression = 1 + self.RequestDividendAdjustedStockData = 0 + + def to_json(self): + return json.dumps(self.__dict__) + +def listen_for_messages(client): + messages = [] + while True: + response = client.recv(4096).decode().rstrip(NULL_TERMINATOR) + if not response: + break + data = json.loads(response) + messages.append(data) + if data['Type'] != 701: # "Waiting" message. + break + return messages + +def request_data_from_sierra_chart(host, port, symbol, exchange, start_date, end_date): + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((host, port)) + + if not authenticate_with_sierra_chart(client, getenv('SIERRA_CHART_USERNAME'), getenv('SIERRA_CHART_PASSWORD')): + print('Authentication failed.') + client.close() + return + + request = HistoricalPriceDataRequest(symbol, exchange, start_date, end_date) + encoded_request = request.to_json().encode() + NULL_TERMINATOR + client.sendall(encoded_request) + + messages = listen_for_messages(client) + for message in messages: + print(message) # Store this data or process it as needed. + + client.close() + +if __name__ == '__main__': + HOST = '127.0.0.1' + PORT = 11098 # Default DTC Protocol port for historical data. + SYMBOL = 'MYMZ22-CBOT' + EXCHANGE = 'CBOT' + START_DATE = 20221001000000 # YYYYMMDDHHMMSS + END_DATE = 20221001235959 + + request_data_from_sierra_chart(HOST, PORT, SYMBOL, EXCHANGE, START_DATE, END_DATE)