(20241224) Live Feed From Kafka (to test).

This commit is contained in:
2024-12-24 14:06:05 +05:30
parent 7c7b10f27c
commit ae6a959a21
18 changed files with 1018 additions and 538 deletions
@@ -6,22 +6,23 @@
DATE:
monday, 23rd Dec., 2024
Tuesday, 24th Aug. 2024
OBJECTIVE:
To provide a SocketIO app for socket-base communication with the front-end.
To provide an easy way to work with '.json' data and files.
REFERENCES:
01. YouTube: https://www.youtube.com/watch?v=H1eLJMC5oTg&t=3s
1) https://www.w3schools.com/python/python_json.asp
DOWNLOADS:
N/A
"""
import datetime
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
@@ -36,26 +37,12 @@ sys.path.append("..")
# System-level activities:
import io
import os
# my utils:
from utils_v2.string import json
# To work with the JSON standard:
import json
# To work with SocketIO:
import socketio
import eventlet
# For asynchronous activities:
import asyncio
# for debugging:
from icecream import IceCreamDebugger
# To work with date and time:
import time
# To work with Zerodha's Kite platform:
from kiteconnect import KiteConnect, KiteTicker
# To work with files:
from utils_v2.system import files
# *****************************************************************************************************************
@@ -65,23 +52,7 @@ from kiteconnect import KiteConnect, KiteTicker
# *****************************************************************************************************************
INSTRUMENT_MAP = {
256265: "NIFTY 50",
260617: "NIFTY 100",
259849: "NIFTY IT",
341249: "HDFCBANK",
738561: "RELIANCE",
408065: "INFY",
2953217: "TCS",
356865: "HINDUNILVR",
1270529: "ICICIBANK",
492033: "KOTAKBANK",
110630919: "GOLD25JAN75800CE",
110050823: "SILVER25FEB76000CE",
10670594: "NIFTY24DEC23650PE",
17167874: "BANKNIFTY24DEC45000PE",
}
INSTRUMENT_TOKENS = list(INSTRUMENT_MAP.keys())
# --- Nothing Yet
# *****************************************************************************************************************
@@ -91,12 +62,7 @@ INSTRUMENT_TOKENS = list(INSTRUMENT_MAP.keys())
# *****************************************************************************************************************
# The SocketIo server:
sio = socketio.Server(cors_allowed_origins = "*")
app = socketio.WSGIApp(sio)
# Debugging:
printer = IceCreamDebugger(prefix = "SocketIO | ", includeContext = True)
# --- Nothing Yet
# *****************************************************************************************************************
@@ -106,83 +72,121 @@ printer = IceCreamDebugger(prefix = "SocketIO | ", includeContext = True)
# *****************************************************************************************************************
@sio.event
def connect(sid, environ):
printer(sid)
def from_string(json_data):
"""
Decodes a JSON string to a pythonic variable like a dict.
:param json_data: The JSON string to decode.
:return: The decoded pythonic variable.
"""
python_data = json.loads(json_data)
return python_data
# ---------------------------------------------------------------------------------------------------------------------
@sio.event
def disconnect(sid):
printer(sid)
# ---------------------------------------------------------------------------------------------------------------------
def on_ticks(ws, ticks):
try:
# print(json.to_string(ticks[0], default=str))
printer(len(ticks))
now = datetime.datetime.now()
for t in ticks:
t["last_trade_time"] = t.get("last_trade_time", now).strftime("%Y-%m-%d %H:%M:%S")
t["exchange_timestamp"] = t.get("exchange_timestamp", now).strftime("%Y-%m-%d %H:%M:%S")
sio.emit("ticks", ticks)
sio.emit("ticks", {"name": "Bhopli"})
sio.emit("debug", {"name": "Debugger Bhopli"})
except Exception as exception:
printer(exception)
# ---------------------------------------------------------------------------------------------------------------------
def on_connect(ws, response):
ws.subscribe(INSTRUMENT_TOKENS)
ws.set_mode(ws.MODE_FULL, INSTRUMENT_TOKENS)
printer("Subscribed to token(s) in 'Full' mode", len(INSTRUMENT_TOKENS))
# ---------------------------------------------------------------------------------------------------------------------
def start_live_feed_input(
api_key: str,
access_token: str,
def to_string(
python_data,
indent = 4,
default = None,
separators = None,
no_space = False
):
kite_ws = KiteTicker(
api_key = api_key,
access_token = access_token
)
"""
Converts the given pythonic data to a JSON string.
:param python_data: The input data like a dict.
:param indent: The tab-width for pretty presentation.
:param default: The function to use on something that cannot be directly parsed into a JSON string.
:param separators: Custom separators to use.
:param no_space: If you want a dense JSON string that saves memory by not using spaces or tabs or line-breaks. Not
good for human readability, very good for saving memory. WARNING: THIS OVERRIDES EVERY OTHER PARAMETER EXCEPT
'default'.
:return: The JSON string representation of the input pythonic data.
"""
# Assign the callbacks:
kite_ws.on_ticks = on_ticks
# kite_ws.on_close = on_close
# kite_ws.on_error = on_error
kite_ws.on_connect = on_connect
# kite_ws.on_reconnect = on_reconnect
# kite_ws.on_noreconnect = on_noreconnect
if no_space:
json_data = json.dumps(
python_data,
default = default,
separators = (',', ':')
)
# If you choose to go threaded, you will need to work purely with callbacks.
# You will need to have an infinite loop in the main thread.
kite_ws.connect(threaded = True)
else:
json_data = json.dumps(
python_data,
indent = indent,
default = default,
separators = separators
)
return json_data
# ---------------------------------------------------------------------------------------------------------------------
@sio.event
def subscribe(sid, data):
printer(data)
sio.emit("echo", data)
def from_file(file):
"""
Reads a JSON file and returns it as a pythonic variable like a dict.
:param file: The path to the file on the disk or a file held in RAM as a BytesIO object.
:return: The decoded pythonic variable.
"""
if isinstance(file, io.BytesIO):
file.seek(0)
json_data = file.getvalue()
else: json_data = files.read_file(file)
python_data = from_string(json_data)
return python_data
# ---------------------------------------------------------------------------------------------------------------------
def to_file(
file,
python_data,
indent = 4,
default = None,
separators = None,
no_space = False
):
"""
:param file: Either a path to a file on disk, or a buffer in RAM in the form of a BytesIO object.
:param python_data: The pythonic data to be converted to the JSON string.
:param indent: The tab-width for pretty presentation.
:param default: The function to use on something that cannot be directly parsed into a JSON string.
:param separators: Custom separators to use.
:param no_space: If you want a dense JSON string that saves memory by not using spaces or tabs or line-breaks. Not
good for human readability, very good for saving memory. WARNING: THIS OVERRIDES EVERY OTHER PARAMETER EXCEPT
'default'.
:return: True/False if a path was given, else the same BytesIO object with the written JSON data.
"""
json_data = to_string(
python_data,
indent = indent,
default = default,
separators = separators,
no_space = no_space
)
if isinstance(file, io.BytesIO):
file.write(json_data.encode("utf-8"))
file.seek(0)
return file
else:
try:
files.write_file(file, json_data, mode = "w")
return True
except: return False
# *****************************************************************************************************************
@@ -194,12 +198,4 @@ def subscribe(sid, data):
if __name__ == "__main__":
# Connect to Zerodha:
creds = json.from_file(r"../creds/zerodha/api.json")
start_live_feed_input(
api_key = creds["apiKey"],
access_token = creds["accessToken"]
)
eventlet.wsgi.server(eventlet.listen(("0.0.0.0", 5214)), app)
pass
+25 -9
View File
@@ -156,6 +156,8 @@ class CoreAuthTokenController(CoreBaseModel):
mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel,
token_notes: dict,
display_name: str = None,
display_picture: str = None,
session_token: str = None,
) -> ObjectId:
@@ -166,6 +168,8 @@ class CoreAuthTokenController(CoreBaseModel):
:param mongo_data_conn: The database connection (MongoDB) to use to perform the action.
:param auth_token: An instance of the core auth-token model that holds data in the database.
:param token_notes: Any notes to feed into MariaDB with the token identifier.
:param display_name: The name of the user to user as their display name.
:param display_picture: The URL at which you will find a display picture of the user.
:param session_token: The session token of the user who requested this service.
:return: An ObjectId to later store the granted tokens.
"""
@@ -230,8 +234,8 @@ class CoreAuthTokenController(CoreBaseModel):
auth_token.client, # ............................................ 'p_provider'
auth_token.status, # ............................................ 'p_current_status'
"Auth Requested", # ............................................. 'p_last_action'
None, # ......................................................... 'p_display_name'
None, # ......................................................... 'p_display_picture'
display_name, # ................................................. 'p_display_name'
display_picture, # .............................................. 'p_display_picture'
mongo_json["key"], # ............................................ 'p_token_id'
json.to_string(python_data = token_notes, no_space = True), # ... 'p_notes'
auth_token.user.userId # ........................................ 'p_created_by'
@@ -249,6 +253,8 @@ class CoreAuthTokenController(CoreBaseModel):
token_key: ObjectId | str,
auth_token: CoreAuthTokenModel,
token_notes: dict,
display_name: str = None,
display_picture: str = None,
session_token: str = None
) -> bool:
@@ -261,6 +267,8 @@ class CoreAuthTokenController(CoreBaseModel):
:param token_key: The identifier granted by the 'generate_token_key' method.
:param auth_token: The actual auth/token data to be saved to the database.
:param token_notes: Any notes to feed into MariaDB with the token identifier.
:param display_name: The name of the user to user as their display name.
:param display_picture: The URL at which you will find a display picture of the user.
:param session_token: The session token of the user who requested this service.
:return: True if saved, False if failed.
"""
@@ -320,8 +328,8 @@ class CoreAuthTokenController(CoreBaseModel):
mongo_json["client"], # ......................................... 'p_provider'
auth_token.status, # ............................................ 'p_current_status'
"Auth Granted", # ............................................... 'p_last_action'
auth_token.token.get("displayName"), # .......................... 'p_display_name'
auth_token.token.get("displayPictureUrl"), # .................... 'p_display_picture'
display_name, # ................................................. 'p_display_name'
display_picture, # .............................................. 'p_display_picture'
token_key, # .................................................... 'p_token_id'
json.to_string(python_data = token_notes, no_space = True), # ... 'p_notes'
auth_token.user.userId # ........................................ 'p_created_by'
@@ -339,6 +347,8 @@ class CoreAuthTokenController(CoreBaseModel):
mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel,
token_notes: dict,
display_name: str = None,
display_picture: str = None,
session_token: str = None
) -> bool:
@@ -349,6 +359,8 @@ class CoreAuthTokenController(CoreBaseModel):
:param mongo_data_conn: The database connection (MongoDB) to use to perform the action.
:param auth_token: The actual auth/token data to be saved to the database.
:param token_notes: Any notes to feed into MariaDB with the token identifier.
:param display_name: The name of the user to user as their display name.
:param display_picture: The URL at which you will find a display picture of the user.
:param session_token: The session token of the user who requested this service.
:return: True if saved, False if failed.
"""
@@ -358,11 +370,13 @@ class CoreAuthTokenController(CoreBaseModel):
# Get a token id (and receive its key):
token_key = await self.generate_token_key(
sql_conn = sql_conn,
mongo_data_conn = mongo_data_conn,
auth_token = auth_token,
token_notes = token_notes,
session_token = session_token
sql_conn = sql_conn,
mongo_data_conn = mongo_data_conn,
auth_token = auth_token,
token_notes = token_notes,
display_name = display_name,
display_picture = display_picture,
session_token = session_token
)
# Immediately save the details against that token id:
@@ -372,6 +386,8 @@ class CoreAuthTokenController(CoreBaseModel):
token_key = token_key,
auth_token = auth_token,
token_notes = token_notes,
display_name = display_name,
display_picture = display_picture,
session_token = session_token
)
@@ -234,7 +234,9 @@ class ZerodhaKiteTradingController(TradingController):
token_notes = {
"apiKey": auth_token.auth["apiKey"],
"authUrl": auth_url
}
},
display_name = zerodha_auth_token.userId,
display_picture = zerodha_auth_token.displayPictureUrl
)
# Done here:
@@ -278,6 +280,8 @@ class ZerodhaKiteTradingController(TradingController):
symbols_subset = kite.instruments(exchange = exchange)
symbols_subset = [TradingSymbol.from_zerodha_kite(s) for s in symbols_subset]
symbol_list.data += symbols_subset
print("LEN(EXC):", len(symbols_subset))
print("LEN(SYM):", len(symbol_list.data))
symbol_list.success = True
symbol_list.message = "Symbol list retrieved successfully."
-1
View File
@@ -3,7 +3,6 @@
# Kill all the scripts:
echo "Killing the script."
pkill -9 -f "$(pwd)/api/main.py"
pkill -9 -f "$(pwd)/playground/socketio/tick_simulator.py"
# Get the name of the current directory,
# and kill the monitors of the above scripts:
@@ -43,6 +43,9 @@ from typing import Optional, Literal, Union, List
from utils_v2.string import regex
from utils_v2.date_time import date_time
# Other models:
from models.finstitutions.trading.symbols import TradingSymbol
# To work with date and time:
import datetime
@@ -75,145 +78,6 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
# *****************************************************************************************************************
class TradingSymbol(BaseModel):
exchange: Literal["NSE", "NFO", "BSE", "BFO", "MCX", "CDS", "BCD"] = Field(
description = "the exchange on which this symbol is traded",
frozen = True
)
exchangeToken: str | int = Field(
description = "the code by which the exchange identifies this instrument",
frozen = True
)
broker: Literal["zerodhaKite", "iciciBreeze"] = Field(
description = "the broker that gave you the details of this instrument",
frozen = True
)
brokerToken: str | int = Field(
description = "the code by which the broker identifies this instrument",
frozen = True
)
name: str = Field(
description = "the name of the co./asset",
frozen = True
)
symbol: str = Field(
description = "tha trading symbol pf the co./asset",
frozen = True
)
tickSize: float = Field(
description = "the minimum step size in the change of price of the instrument",
frozen = True
)
ltp: float = Field(
description = "the last price of this instrument at the time of requesting the symbol list",
frozen = True
)
segment: str = Field(
description = "the segment which this asset represents",
frozen = True
)
type: str = Field(
description = "the type of the instrument in the segment",
frozen = True
)
lotSize: int = Field(
description = "the minimum tradeable qty of this instrument",
frozen = True
)
strike: int | float | None = Field(
description = "the strike price of the instrument if it is a derivative",
default = None,
frozen = True
)
expiryTs: AwareDatetime | None = Field(
description = "the expiry (utc) of this instrument if it is a derivative",
default = None,
frozen = True
)
expiryTz: str = Field(
description = "the timezone in which the expiry timestamp my be interpreted; should be compatible with pytz",
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ┏┓ ┏┓
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
@staticmethod
def from_zerodha_kite(instrument: dict):
return TradingSymbol(
exchange = instrument["exchange"],
exchangeToken = instrument["exchange_token"],
broker = "zerodhaKite",
brokerToken = instrument["instrument_token"],
name = instrument["name"],
symbol = instrument["tradingsymbol"],
tickSize = instrument["tick_size"],
ltp = instrument["last_price"],
segment = instrument["segment"],
type = instrument["instrument_type"],
lotSize = instrument["lot_size"],
strike = instrument["strike"],
expiryTs = date_time.to_timezone(
datetime_object = datetime.datetime.combine(
instrument["expiry"],
datetime.time(hour = 0, minute = 0, second = 0)
),
timezone = date_time.TIMEZONE_UTC
) if instrument["expiry"] else None,
expiryTz = "Asia/Kolkata"
)
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@field_validator("expiryTs", mode = "before")
def parse_date_time(cls, value):
if not value: value = None
if isinstance(value, str):
value = value.strip()
value = date_time.parse_date_time(
input_value = value,
timezone = date_time.TIMEZONE_UTC
)
if isinstance(value, datetime.datetime):
value = date_time.to_timezone(
value,
timezone = date_time.TIMEZONE_UTC
)
return value
# ---------------------------------------------------------------------------------------------------------------------
class TradingSymbolListBrokerResponse(BaseModel):
success: bool = Field(
View File
+222
View File
@@ -0,0 +1,222 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Monday, 23rd Dec., 2024.
OBJECTIVE:
To provide a structure to represent trading symbols.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# For making data behaviour_models:
from pydantic import BaseModel, Field, field_validator, PastDatetime, model_validator, AwareDatetime
from typing import Optional, Literal, Union, List
# My utils:
from utils_v2.string import regex
from utils_v2.date_time import date_time
# To work with date and time:
import datetime
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
class TradingSymbol(BaseModel):
exchange: Literal["NSE", "NFO", "BSE", "BFO", "MCX", "CDS", "BCD"] = Field(
description = "the exchange on which this symbol is traded",
frozen = True
)
exchangeToken: str | int = Field(
description = "the code by which the exchange identifies this instrument",
frozen = True
)
broker: Literal["zerodhaKite", "iciciBreeze"] = Field(
description = "the broker that gave you the details of this instrument",
frozen = True
)
brokerToken: str | int = Field(
description = "the code by which the broker identifies this instrument",
frozen = True
)
name: str = Field(
description = "the name of the co./asset",
frozen = True
)
symbol: str = Field(
description = "tha trading symbol pf the co./asset",
frozen = True
)
tickSize: float = Field(
description = "the minimum step size in the change of price of the instrument",
frozen = True
)
ltp: float = Field(
description = "the last price of this instrument at the time of requesting the symbol list",
frozen = True
)
segment: str = Field(
description = "the segment which this asset represents",
frozen = True
)
type: str = Field(
description = "the type of the instrument in the segment",
frozen = True
)
lotSize: int = Field(
description = "the minimum tradeable qty of this instrument",
frozen = True
)
strike: int | float | None = Field(
description = "the strike price of the instrument if it is a derivative",
default = None,
frozen = True
)
expiryTs: AwareDatetime | None = Field(
description = "the expiry (utc) of this instrument if it is a derivative",
default = None,
frozen = True
)
expiryTz: str = Field(
description = "the timezone in which the expiry timestamp my be interpreted; should be compatible with pytz",
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ┏┓ ┏┓
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
@staticmethod
def from_zerodha_kite(instrument: dict):
return TradingSymbol(
exchange = instrument["exchange"],
exchangeToken = instrument["exchange_token"],
broker = "zerodhaKite",
brokerToken = instrument["instrument_token"],
name = instrument["name"],
symbol = instrument["tradingsymbol"],
tickSize = instrument["tick_size"],
ltp = instrument["last_price"],
segment = instrument["segment"],
type = instrument["instrument_type"],
lotSize = instrument["lot_size"],
strike = instrument["strike"],
expiryTs = date_time.to_timezone(
datetime_object = datetime.datetime.combine(
instrument["expiry"],
datetime.time(hour = 0, minute = 0, second = 0)
),
timezone = date_time.TIMEZONE_UTC
) if instrument["expiry"] else None,
expiryTz = "Asia/Kolkata"
)
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@field_validator("expiryTs", mode = "before")
def parse_date_time(cls, value):
if not value: value = None
if isinstance(value, str):
value = value.strip()
value = date_time.parse_date_time(
input_value = value,
timezone = date_time.TIMEZONE_UTC
)
if isinstance(value, datetime.datetime):
value = date_time.to_timezone(
value,
timezone = date_time.TIMEZONE_UTC
)
return value
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+476
View File
@@ -0,0 +1,476 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Tuesday, 24th Dec., 2024.
OBJECTIVE:
To provide a structure to represent trading tick updates.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# For making data behaviour_models:
from pydantic import BaseModel, Field, field_validator, PastDatetime, model_validator, AwareDatetime, computed_field
from typing import Optional, Literal, Union, List
# My utils:
from utils_v2.string import regex
from utils_v2.date_time import date_time
# To work with date and time:
import datetime
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
class OneMarketDepth(BaseModel):
price: float = Field(
description = "a price at which trader(s) are willing to trade this instrument",
frozen = True
)
qty: int = Field(
description = "the no. of shares available at the above price",
frozen = True
)
orders: int = Field(
description = "how many orders have contributed to the above quantity"
)
@computed_field
def liquidity(self) -> float:
return self.price * self.qty
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class MarketDepth(BaseModel):
buy: List[OneMarketDepth] = Field(
description = "the buying side market depth",
frozen = True
)
sell: List[OneMarketDepth] = Field(
description = "the selling side market depth",
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class TradingTick(BaseModel):
symbol: str = Field(
description = "the symbol of the instrument"
)
exchange: Literal["NSE", "NFO", "BSE", "BFO", "MCX", "CDS", "BCD"] = Field(
description = "the exchange on which this instrument is traded",
frozen = True
)
exchangeToken: str | int = Field(
description = "the code by which the exchange identifies this instrument",
frozen = True
)
broker: Literal["zerodhaKite", "iciciBreeze"] = Field(
description = "the broker that gave you the details of this instrument",
frozen = True
)
brokerToken: str | int = Field(
description = "the code by which the broker identifies this instrument",
frozen = True
)
tradeable: bool = Field(
description = "whether, or not, this instrument is tradeable",
frozen = True
)
segment: str = Field(
description = "the segment which this asset represents",
frozen = True
)
type: str = Field(
description = "the type of the instrument in the segment",
frozen = True
)
strike: int | float | None = Field(
description = "the strike price of the instrument if it is a derivative",
default = None,
frozen = True
)
expiryTs: AwareDatetime | None = Field(
description = "the expiry (utc) of this instrument if it is a derivative",
default = None,
frozen = True
)
expiryTz: str | None = Field(
description = "the timezone in which the expiry timestamp my be interpreted; should be compatible with pytz",
frozen = True,
examples = ["UTC", "Asia/Kolkata"]
)
ltp: float = Field(
description = "the last price of this instrument at the time of requesting the symbol list",
frozen = True
)
chg: float = Field(
description = "the absolute change since the previous close",
frozen=True
)
pChg: float = Field(
description = "the percentage change since last close",
frozen = True
)
o: float = Field(
description = "this session's open price",
frozen = True
)
h: float = Field(
description = "this session's high price",
frozen = True
)
l: float = Field(
description = "this session's low price",
frozen = True
)
c: float = Field(
description = "this session's close price",
frozen = True
)
totVol: int = Field(
description = "the total volume of this instrument that has been traded in this session",
frozen = True
)
vwap: float | None = Field(
description = "the volume weighted average price in this session",
frozen = True
)
totBuyQty: int = Field(
description = "the total open buy qty. on the exchange for this symbol",
frozen = True
)
totSellQty: int = Field(
description = "the total open sell qty. on the exchange for this symbol",
frozen = True
)
oi: int | None = Field(
description = "the total open interest of this instrument (if derivative)",
frozen = True
)
oiDayHigh: int | None = Field(
description = "this session's highest open interest of this instrument (if derivative)",
frozen = True
)
oiDayLow: int | None = Field(
description="this session's lowest open interest of this instrument (if derivative)",
frozen=True
)
tradeTs: AwareDatetime | None = Field(
description = "the last trade time (utc) of this instrument",
default = None,
frozen = True
)
tradeTz: str = Field(
description = "the timezone in which the last trade time should be interpreted; should be compatible with pytz",
frozen = True,
examples = ["UTC", "Asia/Kolkata"]
)
exchgTs: AwareDatetime | None = Field(
description = "the time (utc) at which this update was received from the exchange",
default = None,
frozen = True
)
exchgTz: str = Field(
description = "the timezone in which the exchange's time should be interpreted; should be compatible with pytz",
frozen = True,
examples = ["UTC", "Asia/Kolkata"]
)
depth: MarketDepth = Field(
description = "the market depth data for this instrument at the time of this update"
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ┏┓ ┏┓
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
@staticmethod
def from_zerodha_kite(
ticks: dict | List[dict],
lookup: dict
) -> list:
# Ensure that we are working with a list:
if not isinstance(ticks, list): ticks = [ticks]
# Iterate through the ticks and fit them into the model:
modelled_ticks = []
for tick in ticks:
broker_token = tick["instrument_token"]
tick_lookup = lookup[broker_token]
change = tick["change"]
modelled_ticks.append(
TradingTick(
symbol = tick_lookup["symbol"],
exchange = tick_lookup["exchange"],
exchangeToken = tick_lookup["exchangeToken"],
broker = "zerodhaKite",
brokerToken = broker_token,
tradeable = tick["tradeable"],
segment = tick_lookup["segment"],
type = tick_lookup["type"],
expiryTs = tick_lookup["expiryTs"],
expiryTz = tick_lookup["expiryTz"],
ltp = ,
chg = change,
pChg =
)
)
# Done here:
return modelled_ticks
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@field_validator(
"expiryTs",
"tradeTs", "exchgTs",
mode = "before"
)
def parse_date_time(cls, value):
# If the input is null,
# we can't do anything:
if not value: value = None
# When the input is a string, we try to parse it:
elif isinstance(value, str):
value = value.strip()
value = date_time.parse_date_time(
input_value = value,
timezone = date_time.TIMEZONE_UTC
)
# When the input is a datetime obj.,
# we only work on the timezone:
elif isinstance(value, datetime.datetime):
value = date_time.to_timezone(
value,
timezone = date_time.TIMEZONE_UTC
)
# Done here:
return value
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
zerodha_tick = {
"tradable": True,
"mode": "full",
"instrument_token": 408065,
"last_price": 1928.25,
"last_traded_quantity": 157,
"average_traded_price": 1942.5,
"volume_traded": 5596931,
"total_buy_quantity": 217126,
"total_sell_quantity": 380092,
"ohlc": {
"open": 1975.15,
"high": 1979.95,
"low": 1911.25,
"close": 1946.2
},
"change": -0.9223101428424646,
"last_trade_time": "2024-12-20 14:46:14",
"oi": 0,
"oi_day_high": 0,
"oi_day_low": 0,
"exchange_timestamp": "2024-12-20 14:46:15",
"depth": {
"buy": [
{
"quantity": 3,
"price": 1928.15,
"orders": 2
},
{
"quantity": 6,
"price": 1927.95,
"orders": 2
},
{
"quantity": 66,
"price": 1927.7,
"orders": 2
},
{
"quantity": 25,
"price": 1927.65,
"orders": 1
},
{
"quantity": 194,
"price": 1927.6,
"orders": 6
}
],
"sell": [
{
"quantity": 134,
"price": 1928.25,
"orders": 5
},
{
"quantity": 3,
"price": 1928.3,
"orders": 1
},
{
"quantity": 560,
"price": 1928.35,
"orders": 2
},
{
"quantity": 64,
"price": 1928.4,
"orders": 2
},
{
"quantity": 400,
"price": 1928.45,
"orders": 1
}
]
}
}
zerodha_lookup = {
408065: {
"symbol": "MYSTOCK",
"exchange": "NSE",
"exchangeToken": 12345678,
"segment": "NFO-OPT",
"type": "CE"
}
}
my_ticks = TradingTick.from_zerodha_kite(
ticks = zerodha_tick,
instrument_lookup = zerodha_lookup
)
print(my_ticks[0])
+112
View File
@@ -0,0 +1,112 @@
import random
import time
import socketio
import asyncio
import datetime
import requests
# Create a Socket.IO server instance
sio = socketio.AsyncServer(cors_allowed_origins = "*")
# Create an aiohttp web application
from aiohttp import web
app = web.Application()
# Attach the Socket.IO server to the aiohttp application
sio.attach(app)
from utils_v2.queue.async_kafka import ProducerKafka, ConsumerKafka, get_ssl_context
import os
# Define the test params:
TOPIC = "kft_file_upload"
BOOTSTRAP_SERVERS = "del.ditscentre.in:9092"
SSL_CONTEXT = get_ssl_context(
ca_file = "../../creds/kafka/cert_authority.pem",
cert_file = "../../creds/kafka/fullchain.pem",
key_file = "../../creds/kafka/privkey.pem"
)
my_consumer = ConsumerKafka(
topic = TOPIC,
bootstrap_servers = BOOTSTRAP_SERVERS,
security_protocol = "SSL",
ssl_context = SSL_CONTEXT
)
# Event: Client connects
@sio.event
async def connect(sid, environ):
print(f"Client {sid} connected")
requests.post(
url = r"https://api.thecaoffice.com/converse/tech/alert/chat/backend",
json = {
"type": "info",
"chatClient": "telegram",
"chatId": "-4206946032",
# "chatId": "1275560043",
"message": f"*SocketIO Connected!*\n👍 SID: {sid}"
}
)
# Event: Client disconnects
@sio.event
async def disconnect(sid):
print(f"Client {sid} disconnected")
requests.post(
url = r"https://api.thecaoffice.com/converse/tech/alert/chat/backend",
json = {
"type": "info",
"chatClient": "telegram",
"chatId": "-4206946032",
# "chatId": "1275560043",
"message": f"*SocketIO Disconnected!*\n❌ SID: {sid}"
}
)
@sio.event
async def message(sid, data):
print("MESSAGE:", data)
# Function to generate random data
async def broadcast_one_tick(tick):
await sio.emit("ticks", tick)
# Function to broadcast data every second asynchronously
async def broadcast_ticks():
while True:
messages = await my_consumer.consume(count = 100, timeout = 1.0)
print(f"Received {len(messages)} tick(s)")
tasks = [broadcast_one_tick(m["value"]) for m in messages]
if tasks: results = await asyncio.gather(*tasks)
# Start broadcasting random data using asyncio
async def start_broadcast():
await broadcast_ticks()
# Main function to run the aiohttp server and the broadcasting
async def main():
# Start broadcasting random data in the background
asyncio.create_task(start_broadcast())
# Run the web server
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, '0.0.0.0', 5214)
print("Server running on http://0.0.0.0:5214")
await site.start()
# Keep the server running
while True:
await asyncio.sleep(3600) # Keep the server alive for 1 hour or adjust as needed
# Run the main asyncio event loop
if __name__ == '__main__':
asyncio.run(main())
+44 -26
View File
@@ -134,7 +134,7 @@ SYMBOL_TO_PRICE_MAP = {
# *****************************************************************************************************************
# --- Nothing Yet
tg_update = False
# *****************************************************************************************************************
@@ -144,21 +144,39 @@ SYMBOL_TO_PRICE_MAP = {
# *****************************************************************************************************************
@sio.event
async def before_connect(sid, environ):
print(f"Checking connection attempt from {sid}.")
# # Simulate a failed connection based on some conditions (for example, invalid IP or header)
# user_agent = environ.get('HTTP_USER_AGENT', '')
# if 'BadUserAgent' in user_agent:
# print(f"Rejected connection from {sid} due to invalid User-Agent.")
# return False # This will reject the connection attempt
return True # Allow connection
# ---------------------------------------------------------------------------------------------------------------------
@sio.event
async def connect(sid, environ):
print(f"Client {sid} connected")
async with httpx.AsyncClient() as client:
try: await client.post(
url = r"https://api.thecaoffice.com/converse/tech/alert/chat/backend",
json = {
"type": "info",
"chatClient": "telegram",
"chatId": "-4206946032",
# "chatId": "1275560043",
"message": f"*SocketIO Connected!*\n👍 SID: {sid}"
}
)
except: pass
if tg_update:
async with httpx.AsyncClient() as client:
try: await client.post(
url = r"https://api.thecaoffice.com/converse/tech/alert/chat/backend",
json = {
"type": "info",
"chatClient": "telegram",
"chatId": "-4206946032",
# "chatId": "1275560043",
"message": f"*SocketIO Connected!*\n👍 SID: {sid}"
}
)
except: pass
# ---------------------------------------------------------------------------------------------------------------------
@@ -167,18 +185,19 @@ async def connect(sid, environ):
@sio.event
async def disconnect(sid):
print(f"Client {sid} disconnected")
async with httpx.AsyncClient() as client:
try: await client.post(
url = r"https://api.thecaoffice.com/converse/tech/alert/chat/backend",
json = {
"type": "info",
"chatClient": "telegram",
"chatId": "-4206946032",
# "chatId": "1275560043",
"message": f"*SocketIO Disconnected!*\n❌ SID: {sid}"
}
)
except: pass
if tg_update:
async with httpx.AsyncClient() as client:
try: await client.post(
url = r"https://api.thecaoffice.com/converse/tech/alert/chat/backend",
json = {
"type": "info",
"chatClient": "telegram",
"chatId": "-4206946032",
# "chatId": "1275560043",
"message": f"*SocketIO Disconnected!*\n❌ SID: {sid}"
}
)
except: pass
def round_tick(price):
@@ -339,7 +358,6 @@ if __name__ == "__main__":
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, "0.0.0.0", 5214)
print("Server running on http://0.0.0.0:5000")
await site.start()
# Keep the server running
-1
View File
@@ -3,7 +3,6 @@
# Use this to run the microservice without any docker setup.
source .venv/bin/activate
python3 "$(pwd)/api/main.py" --host "0.0.0.0" --port 5106 --workers 4 --script-id "kps_cnv_KBC3MoaU" &
python3 "$(pwd)/playground/socketio/tick_simulator.py" &
deactivate
# All done:
@@ -85,9 +85,16 @@ import httpx
class ZerodhaKiteAuthTokens(BaseModel):
userId: str = Field(
description = "the id of the user, typically in 'ABC123' format",
frozen = True,
alias = "user_id"
)
userType: str | None = Field(
description = "the type of the user",
default = None,
frozen = True,
alias = "user_type",
examples = ["individual/ind_with_nom"]
)
@@ -95,60 +102,71 @@ class ZerodhaKiteAuthTokens(BaseModel):
email: str | None = Field(
description = "the email id of the user",
default = None,
frozen = True,
alias = "email"
)
name: str = Field(
description = "the name of the user",
frozen = True,
alias = "user_name"
)
displayName: str | None = Field(
description = "the short display name of the user",
default = None,
frozen = True,
alias = "user_shortname"
)
displayPictureUrl: str | None = Field(
description = "the display picture of the user",
default = None,
frozen = True,
alias = "avatar_url"
)
exchanges: List[str] = Field(
description = "the list of exchanges this user can trade on",
frozen = True,
alias = "exchanges"
)
products: List[str] = Field(
description = "the list of products (offered by the broker) this user can avail",
frozen = True,
alias = "products"
)
orderTypes: List[str] = Field(
description = "the list of order types this user can place",
frozen = True,
alias = "order_types"
)
accessToken: str = Field(
description = "the main access token to be used in actual requests",
frozen = True,
alias = "access_token"
)
refreshToken: str | None = Field(
description = "token to be used to refresh the access token; may not be provided",
default = None,
frozen = True,
alias = "refresh_token"
)
publicToken: str | None = Field(
description = "undocumented on their official documentation",
default = None,
frozen = True,
alias = "public_token"
)
loginTs: AwareDatetime = Field(
description = "the time (utc) at which this user logged in",
frozen = True,
alias = "login_time"
)
@@ -1,93 +0,0 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
monday, 23rd Dec., 2024
OBJECTIVE:
To provide a SocketIO app for socket-base communication with the front-end.
REFERENCES:
01. YouTube: https://www.youtube.com/watch?v=H1eLJMC5oTg&t=3s
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# System-level activities:
import io
import os
# my utils:
from utils_v2.string import json
# For asynchronous activities:
import asyncio
# for debugging:
from icecream import IceCreamDebugger
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# Debugging:
printer = IceCreamDebugger(prefix = "SocketIO | ", includeContext = True)
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
async def subscribe(sid, data):
printer(data)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
-151
View File
@@ -1,151 +0,0 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
monday, 23rd Dec., 2024
OBJECTIVE:
To provide a SocketIO app for socket-base communication with the front-end.
REFERENCES:
01. YouTube: https://www.youtube.com/watch?v=H1eLJMC5oTg&t=3s
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# System-level activities:
import io
import os
# my utils:
from utils_v2.string import json
# To work with SocketIO:
import socketio
from aiohttp import web
# For asynchronous activities:
import asyncio
# for debugging:
from icecream import IceCreamDebugger
# To work with date and time:
import time
import datetime
# To work with Zerodha's Kite platform:
from kiteconnect import KiteConnect, KiteTicker
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
INSTRUMENT_MAP = {
256265: "NIFTY 50",
260617: "NIFTY 100",
259849: "NIFTY IT",
341249: "HDFCBANK",
738561: "RELIANCE",
408065: "INFY",
2953217: "TCS",
356865: "HINDUNILVR",
1270529: "ICICIBANK",
492033: "KOTAKBANK",
110630919: "GOLD25JAN75800CE",
110050823: "SILVER25FEB76000CE",
10670594: "NIFTY24DEC23650PE",
17167874: "BANKNIFTY24DEC45000PE",
}
INSTRUMENT_TOKENS = list(INSTRUMENT_MAP.keys())
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# The SocketIo server:
sio = socketio.AsyncServer(cors_allowed_origins = "*")
app = web.Application()
sio.attach(app)
# Debugging:
printer = IceCreamDebugger(prefix = "SocketIO | ", includeContext = True)
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
@sio.event
async def connect(sid, environ):
printer(sid)
# ---------------------------------------------------------------------------------------------------------------------
@sio.event
async def disconnect(sid):
printer(sid)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
# Import the needed events:
from wsocket.finstitutions.trading import live_feed
async def main():
# register all the events:
sio.on("subscribe", live_feed.subscribe)
# Run the web server:
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, "0.0.0.0", 5214)
await site.start()
# Keep the server running:
while True: await asyncio.sleep(3_600)
asyncio.run(main())