diff --git a/controllers_v2/finstitutions/__init__.py b/controllers_v2/finstitutions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/controllers_v2/finstitutions/trading/__init__.py b/controllers_v2/finstitutions/trading/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/controllers_v2/finstitutions/trading/all_trading.py b/controllers_v2/finstitutions/trading/all_trading.py new file mode 100644 index 0000000..10fad96 --- /dev/null +++ b/controllers_v2/finstitutions/trading/all_trading.py @@ -0,0 +1,171 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Saturday, 21st Dec., 2024 + + OBJECTIVE: + + To handle all trading related behaviour from one place. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# My async utils: +from utils_v2.database.async_mongo_v2 import AsyncMongo +from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache + +# Controllers: +from controllers_v2.core.auth_token import CoreAuthTokenController + +# Models: +from models.core.auth_token import CoreAuthTokenModel + +# To work with datatypes: +from typing import List, Any + +# To make HTTP requests: +import httpx + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class TradingController(CoreAuthTokenController): + + # ┏┓ + # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓ + # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛ + + def __init__( + self, + cache: AsyncRedisCache = None, + http_client: httpx.AsyncClient = None, + alert_url: str = None, + base_filter: dict = None, + debug: bool = True, + debug_prefix: str = "Trading (C) | ", + debug_only_errors: bool = True + ): + + """ + This is the foundational controller for all SMS services. This is built on top of the core message controller, + and, in turn, all individual SMS client controllers must be built on top of this. + :param cache: The object to use for caching results from database calls. + :param http_client: The HTTP client + :param base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE + FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY. + :param debug: Whether, or not, you would like to print debugging messages: + :param debug_prefix: The prefix to print with the debugging messages. + :param debug_only_errors: Whether you would like to print only error messages or all messages. + :return: None. + """ + + # Prepare the combined base filter: + this_filter = {} + for k, v in (base_filter or {}).items(): this_filter[k] = v + this_filter["serviceType"] = "stockTrading" + + # Invoke the parent's constructor: + CoreAuthTokenController.__init__( + self, + cache = cache, + alert_url = alert_url, + http_client = http_client, + base_filter = this_filter, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # ┏┓ ┓ + # ┣┫┓┏╋┣┓ + # ┛┗┗┻┗┛┗ + + # async def login_url( + # self, + # mongo_data_conn: AsyncMongo, + # auth_token: CoreAuthTokenModel + # ) -> SMSSendOneResult: + # + # """ + # To send one SMS message through the third-party client. + # :param mongo_data_conn: The database connection to use to perform this task. + # :param auth_token: The auth token that will be used to send this message. + # :param client: The third-party SMS client to use to send this message. + # :param message: The actual message that needs to be sent. + # :param tags: Any tags to attach with this SMS for filtering when querying in the listing service. + # :return: The structured result of sending one message. + # """ + # + # pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/controllers_v2/finstitutions/trading/base.py b/controllers_v2/finstitutions/trading/base.py new file mode 100644 index 0000000..6689e1e --- /dev/null +++ b/controllers_v2/finstitutions/trading/base.py @@ -0,0 +1,210 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 19th Dec., 2024 + + OBJECTIVE: + + To handle all SMS related behaviour from one place. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# My async utils: +from utils_v2.database.async_mongo_v2 import AsyncMongo +from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache + +# Controllers: +from controllers_v2.core.message import CoreMessageController + +# Models: +from models.core.auth_token import CoreAuthTokenModel +from models.api.sms.send import ( + NimbusSMSIndiaMessage, + SavvyBulkSMSKenyaMessage, + SMSSendOneResult, + SMSSendManyResults +) + +# SMS clients: +from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS +from utils_v2.sms.kenya.savvy_bulk_sms.controllers.async_savvy_bulk_sms import AsyncSavvyBulkSMS + +# To work with datatypes: +from typing import List, Any + +# To make HTTP requests: +import httpx + +# To make abstract classes: +from abc import ABC, abstractmethod + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class SMSController(CoreMessageController, ABC): + + # ┏┓ + # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓ + # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛ + + def __init__( + self, + cache: AsyncRedisCache = None, + http_client: httpx.AsyncClient = None, + alert_url: str = None, + base_filter: dict = None, + debug: bool = True, + debug_prefix: str = "SMS (C) | ", + debug_only_errors: bool = True + ): + + """ + This is the foundational controller for all SMS services. This is built on top of the core message controller, + and, in turn, all individual SMS client controllers must be built on top of this. + :param cache: The object to use for caching results from database calls. + :param http_client: The HTTP client + :param base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE + FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY. + :param debug: Whether, or not, you would like to print debugging messages: + :param debug_prefix: The prefix to print with the debugging messages. + :param debug_only_errors: Whether you would like to print only error messages or all messages. + :return: None. + """ + + # Prepare the combined base filter: + sms_filter = {} + for k, v in (base_filter or {}).items(): sms_filter[k] = v + sms_filter["serviceType"] = "sms" + + # Invoke the parent's constructor: + CoreMessageController.__init__( + self, + cache = cache, + alert_url = alert_url, + http_client = http_client, + base_filter = sms_filter, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # ┏┓┳┳┓┏┓ ┏┓ ┓• + # ┗┓┃┃┃┗┓ ┗┓┏┓┏┓┏┫┓┏┓┏┓ + # ┗┛┛ ┗┗┛ ┗┛┗ ┛┗┗┻┗┛┗┗┫ + # ┛ + + async def send_one_sms( + self, + mongo_data_conn: AsyncMongo, + auth_token: CoreAuthTokenModel, + client: AsyncNimbusSMS | AsyncSavvyBulkSMS, + message: NimbusSMSIndiaMessage, + tags: List[Any] + ) -> SMSSendOneResult: + + """ + To send one SMS message through the third-party client. + :param mongo_data_conn: The database connection to use to perform this task. + :param auth_token: The auth token that will be used to send this message. + :param client: The third-party SMS client to use to send this message. + :param message: The actual message that needs to be sent. + :param tags: Any tags to attach with this SMS for filtering when querying in the listing service. + :return: The structured result of sending one message. + """ + + pass + + @abstractmethod + async def send_many_sms( + self, + mongo_data_conn: AsyncMongo, + auth_token: CoreAuthTokenModel, + messages: List[NimbusSMSIndiaMessage | SavvyBulkSMSKenyaMessage], + tags: List[Any] + ) -> SMSSendManyResults: + + """ + To send multiple SMS messages through the third-party client. + individual message, and then aggregates the results. + :param mongo_data_conn: The database connection to use to perform this task. + :param auth_token: The auth token that will be used to send this message. + :param messages: The list of messages to send out. + :param tags: Any tags to attach with these SMS for filtering when querying in the listing service. The same tags + will be applied to all messages. Do not call this method if you need to have different tags for all of them. + :return: The structured result of sending many SMS messages. + """ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/controllers_v2/finstitutions/trading/zerodha_kite.py b/controllers_v2/finstitutions/trading/zerodha_kite.py new file mode 100644 index 0000000..c0ecb8d --- /dev/null +++ b/controllers_v2/finstitutions/trading/zerodha_kite.py @@ -0,0 +1,145 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Saturday, 21st Dec., 2024 + + OBJECTIVE: + + To handle all trading related behaviour from one place. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# My async utils: +from utils_v2.database.async_mongo_v2 import AsyncMongo +from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache + +# Controllers: +from controllers_v2.finstitutions.trading.base import TradingController + +# Models: +from models.core.auth_token import CoreAuthTokenModel + +# To work with datatypes: +from typing import List, Any + +# To make HTTP requests: +import httpx + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class AllTradingController(TradingController): + + # ┏┓ + # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓ + # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛ + + def __init__( + self, + cache: AsyncRedisCache = None, + http_client: httpx.AsyncClient = None, + alert_url: str = None, + debug: bool = True, + debug_prefix: str = "All SMS (C) | ", + debug_only_errors: bool = True + ): + """ + This is the foundational controller for all trading services. Use this for any smaller common tasks where you + may not know the exact client beforehand. + :param cache: The object to use for caching results from database calls. + :param http_client: The HTTP client + :param debug: Whether, or not, you would like to print debugging messages: + :param debug_prefix: The prefix to print with the debugging messages. + :param debug_only_errors: Whether you would like to print only error messages or all messages. + :return: None. + """ + + # Invoke the parent's constructor: + super().__init__( + cache = cache, + alert_url = alert_url, + http_client = http_client, + base_filter = None, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # ┏┓ ┓ + # ┣┫┓┏╋┣┓ + # ┛┗┗┻┗┛┗ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/playground/socketio/tick_simulator.py b/playground/socketio/tick_simulator.py new file mode 100644 index 0000000..67d39f7 --- /dev/null +++ b/playground/socketio/tick_simulator.py @@ -0,0 +1,202 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Create: Saturday, 18th May, 2022 + Update: Thursday, 22nd Aug. 2024 + + OBJECTIVE: + + To provide an easy way to work with '.json' data and files. + + REFERENCES: + + 1) https://www.w3schools.com/python/python_json.asp + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# System-level activities: +import io + +# To work with the JSON standard: +import json + +# To work with files: +from utils_v2.system import files + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +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 + + +# --------------------------------------------------------------------------------------------------------------------- + + +def to_string( + python_data, + indent = 4, + default = None, + separators = None, + no_space = False +): + + """ + 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. + """ + + if no_space: + json_data = json.dumps( + python_data, + default = default, + separators = (',', ':') + ) + + else: + json_data = json.dumps( + python_data, + indent = indent, + default = default, + separators = separators + ) + + return json_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 + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/utils_v2/trading/__init__.py b/utils_v2/trading/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils_v2/trading/zerodha_kite/__init__.py b/utils_v2/trading/zerodha_kite/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils_v2/trading/zerodha_kite/models/__init__.py b/utils_v2/trading/zerodha_kite/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils_v2/trading/zerodha_kite/models/auth_tokens.py b/utils_v2/trading/zerodha_kite/models/auth_tokens.py new file mode 100644 index 0000000..03229fa --- /dev/null +++ b/utils_v2/trading/zerodha_kite/models/auth_tokens.py @@ -0,0 +1,241 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Saturday, 21st Dec., 2024. + + OBJECTIVE: + + To provide a data model for describing the tokens to be used for Google's APIs. + + 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, model_validator, AwareDatetime +from typing import Optional, Literal, Union, Dict, List, Any + +# Related to Google: +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials + +# My utils: +from utils_v2.string import json +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# To work with date and time: +import datetime +import dateparser + +# To make API calls: +import httpx + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +class ZerodhaKiteAuthTokens(BaseModel): + + userType: str | None = Field( + description = "the type of the user", + default = None, + alias = "user_type", + examples = ["individual/ind_with_nom"] + ) + + email: str | None = Field( + description = "the email id of the user", + default = None, + alias = "email" + ) + + name: str = Field( + description = "the name of the user", + alias = "user_name" + ) + + displayName: str | None = Field( + description = "the short display name of the user", + default = None, + alias = "user_shortname" + ) + + displayPictureUrl: str | None = Field( + description = "the display picture of the user", + default = None, + alias = "avatar_url" + ) + + exchanges: List[str] = Field( + description = "the list of exchanges this user can trade on", + alias = "exchanges" + ) + + products: List[str] = Field( + description = "the list of products (offered by the broker) this user can avail", + alias = "products" + ) + + orderTypes: List[str] = Field( + description = "the list of order types this user can place", + alias = "order_types" + ) + + accessToken: str = Field( + description = "the main access token to be used in actual requests", + alias = "access_token" + ) + + refreshToken: str | None = Field( + description = "token to be used to refresh the access token; may not be provided", + default = None, + alias = "refresh_token" + ) + + publicToken: str | None = Field( + description = "undocumented on their official documentation", + default = None, + alias = "public_token" + ) + + loginTs: AwareDatetime = Field( + description = "the time (utc) at which this user logged in", + alias = "login_time" + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "ignore" + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + @field_validator("loginTs", mode = "before") + def parse_dates(cls, value): + if not isinstance(value, datetime.datetime): + parsed = date_time.parse_date_time( + value, + date_formats = ["%Y-%m-%d %H:%M:%S"], + timezone = date_time.TIMEZONE_IST + ) + value = parsed if isinstance(parsed, datetime.datetime) else dateparser.parse(value) + if isinstance(value, datetime.datetime): value = date_time.to_timezone(value, date_time.TIMEZONE_UTC) + return value + + # ┏┓ • + # ┃┃┏┓┏┓┏┓┏┓┏┓╋┓┏┓┏ + # ┣┛┛ ┗┛┣┛┗ ┛ ┗┗┗ ┛ + # ┛ + + pass + + # ┏┓ ┏┓ + # ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏ + # ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + sample_token = { + "user_type": "individual/ind_with_nom", + "email": "khushalpradipsoonderji@gmail.com", + "user_name": "Khushal Pradip Soonderji", + "user_shortname": "Khushal", + "broker": "ZERODHA", + "exchanges": [ + "NSE", + "BSE", + "NFO", + "MF" + ], + "products": [ + "CNC", + "NRML", + "MIS", + "BO", + "CO" + ], + "order_types": [ + "MARKET", + "LIMIT", + "SL", + "SL-M" + ], + "avatar_url": None, + "user_id": "ABC123", + "api_key": "same-as-api-key-input-to-this-func", + "access_token": "use-this-for-subsequent-activities-like-data-feeds", + "public_token": "???", + "refresh_token": "", + "enctoken": "???", + "login_time": "2024-12-20 13:23:20", + "meta": { + "demat_consent": "consent" + } + } + + model = ZerodhaKiteAuthTokens(**sample_token) + print(json.to_string(model.model_dump(), default = str))