(20250125) More work done on Tick Strategies background task.
This commit is contained in:
+150
-9
@@ -32,6 +32,9 @@
|
|||||||
|
|
||||||
# To make sibling directories accessible for imports:
|
# To make sibling directories accessible for imports:
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
sys.path.append(".")
|
sys.path.append(".")
|
||||||
sys.path.append("..")
|
sys.path.append("..")
|
||||||
|
|
||||||
@@ -79,6 +82,9 @@ import asyncio
|
|||||||
# To work with various datatypes:
|
# To work with various datatypes:
|
||||||
from typing import List, Literal
|
from typing import List, Literal
|
||||||
|
|
||||||
|
# For scheduling and cron:
|
||||||
|
from scheduler.asyncio import Scheduler
|
||||||
|
|
||||||
# For MongoDB:
|
# For MongoDB:
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
|
|
||||||
@@ -94,8 +100,8 @@ from icecream import IceCreamDebugger
|
|||||||
|
|
||||||
|
|
||||||
# Debugging:
|
# Debugging:
|
||||||
printer = IceCreamDebugger(prefix = "Strategy (0) | ", includeContext = True)
|
printer = IceCreamDebugger(prefix = "Bhandari (s0) | ", includeContext = True)
|
||||||
no_context_printer = IceCreamDebugger(prefix = "Strategy (0) | ", includeContext = False)
|
no_context_printer = IceCreamDebugger(prefix = "Bhandari (s0) | ", includeContext = False)
|
||||||
|
|
||||||
# To make API calls:
|
# To make API calls:
|
||||||
http_client = httpx.AsyncClient(
|
http_client = httpx.AsyncClient(
|
||||||
@@ -126,6 +132,7 @@ redis_cache: AsyncRedisCache | None = None
|
|||||||
|
|
||||||
# For database:
|
# For database:
|
||||||
sql_writer: AsyncMySQL | None = None
|
sql_writer: AsyncMySQL | None = None
|
||||||
|
sql_reader: AsyncMySQL | None = None
|
||||||
data_mongo: AsyncMongo | None = None
|
data_mongo: AsyncMongo | None = None
|
||||||
|
|
||||||
# For kafka:
|
# For kafka:
|
||||||
@@ -133,7 +140,8 @@ kafka_consumer: ConsumerKafka | None = None
|
|||||||
|
|
||||||
# Session-awareness and maintenance of this script's state:
|
# Session-awareness and maintenance of this script's state:
|
||||||
SCRIPT_DATA = {}
|
SCRIPT_DATA = {}
|
||||||
exclusive_lock = asyncio.Semaphore(1)
|
STRATEGY_REFERENCE = {}
|
||||||
|
strategy_reference_lock = asyncio.Semaphore(1)
|
||||||
ticks_since_telegram = 0
|
ticks_since_telegram = 0
|
||||||
|
|
||||||
# For Zerodha-Kite:
|
# For Zerodha-Kite:
|
||||||
@@ -228,6 +236,7 @@ async def init(
|
|||||||
# Declare the required global variables:
|
# Declare the required global variables:
|
||||||
global SCRIPT_DATA
|
global SCRIPT_DATA
|
||||||
global sql_writer
|
global sql_writer
|
||||||
|
global sql_reader
|
||||||
global kafka_consumer
|
global kafka_consumer
|
||||||
|
|
||||||
# Basic stuff:
|
# Basic stuff:
|
||||||
@@ -273,7 +282,19 @@ async def init(
|
|||||||
database = script_cred["mariaDb"]["write"]["database"]
|
database = script_cred["mariaDb"]["write"]["database"]
|
||||||
)
|
)
|
||||||
if not await sql_writer.connect():
|
if not await sql_writer.connect():
|
||||||
print("FATAL: MARIA-DB CONNECTION FAILED!")
|
print("FATAL: MARIA-DB WRITER CONNECTION FAILED!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
sql_reader = AsyncMySQL(
|
||||||
|
pool_size = script_cred["mariaDb"]["read"]["poolSize"],
|
||||||
|
host = script_cred["mariaDb"]["read"]["host"],
|
||||||
|
user = script_cred["mariaDb"]["read"]["user"],
|
||||||
|
password = script_cred["mariaDb"]["read"]["password"],
|
||||||
|
# database = script_cred["mariaDb"]["read"]["database"]
|
||||||
|
database = "caOffice"
|
||||||
|
)
|
||||||
|
if not await sql_reader.connect():
|
||||||
|
print("FATAL: MARIA-DB READER CONNECTION FAILED!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
printer("MariaDB connected.")
|
printer("MariaDB connected.")
|
||||||
@@ -355,7 +376,74 @@ async def send_telegram(
|
|||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
async def heartbeat(interval_seconds: float = 300):
|
async def refresh_strategy_reference() -> bool:
|
||||||
|
|
||||||
|
# Declare the required global variables:
|
||||||
|
global STRATEGY_REFERENCE
|
||||||
|
|
||||||
|
no_context_printer("Refreshing strategy ref.")
|
||||||
|
|
||||||
|
# Query the database:
|
||||||
|
proc_name = "strategy_results"
|
||||||
|
proc_args = (0, 184, 0)
|
||||||
|
db_json, db_exception = await sql_reader.call_procedure_and_get_json(
|
||||||
|
procedure_name = proc_name,
|
||||||
|
procedure_args = proc_args,
|
||||||
|
return_exception = True,
|
||||||
|
retry_count = 3,
|
||||||
|
backoff_seconds = 0.5,
|
||||||
|
backoff_multiplier = 1.1
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the database call fails:
|
||||||
|
if db_json["status"] != 1 or db_exception:
|
||||||
|
await send_telegram(
|
||||||
|
message = (
|
||||||
|
"*Strategies (0) SQL Procedure Failure*\n\n"
|
||||||
|
f"Procedure Name: `{proc_name}`\n\n"
|
||||||
|
f"Procedure Args: `{proc_args}`\n\n"
|
||||||
|
f"Message: `{db_json['message']}`\n\n"
|
||||||
|
f"Exception: `{db_exception}`\n\n"
|
||||||
|
),
|
||||||
|
message_type = "error"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Construct the reference structure:
|
||||||
|
formatted_reference = {}
|
||||||
|
for i in db_json["data"]["rs0"]:
|
||||||
|
key_items = [i["exchange"], i["segment"], i["exchange_code"]]
|
||||||
|
if i["expiry_date"]: key_items.append(str(i["expiry_date"]))
|
||||||
|
if i["right"]:
|
||||||
|
key_items.append(str(i["right"]))
|
||||||
|
key_items.append(str(i["strike"]))
|
||||||
|
formatted_reference["_".join(key_items)] = {
|
||||||
|
"stratName": i["name"], # ................ As inserted by the user.
|
||||||
|
"watchlistId": i["watch_list_id"], # ..... A unique id in case 2 users come up with the same name.
|
||||||
|
"entry": i["entry"].upper().strip(), # ... BUY/SELL.
|
||||||
|
"rate": i["entry_rate"], # ............... The price at which to enter the trade.
|
||||||
|
"t": i["target1"], # ..................... The 1st target.
|
||||||
|
"sl": i["stop_loss"] # ................... The stop loss value.
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save the new reference in the global variable:
|
||||||
|
async with strategy_reference_lock:
|
||||||
|
STRATEGY_REFERENCE = formatted_reference
|
||||||
|
|
||||||
|
# Done here:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
async def heartbeat(interval_seconds: float = 300) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Just to let the admins know that the system is up and running.
|
||||||
|
:param interval_seconds: The amount of time after which the heartbeat must be sent.
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await send_telegram(message = f"*Strategies (0) Heartbeat*\nInterval: `{interval_seconds:,} seconds`")
|
await send_telegram(message = f"*Strategies (0) Heartbeat*\nInterval: `{interval_seconds:,} seconds`")
|
||||||
@@ -365,17 +453,40 @@ async def heartbeat(interval_seconds: float = 300):
|
|||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
async def main(debug: bool = False):
|
async def main(
|
||||||
|
start_time: datetime.datetime,
|
||||||
|
end_time: datetime.datetime,
|
||||||
|
proc_interval: int,
|
||||||
|
heartbeat_interval: int = 1_800,
|
||||||
|
debug: bool = False
|
||||||
|
):
|
||||||
|
|
||||||
|
# Start configuring the scheduler:
|
||||||
|
printer("Configuring the schedule-manager.")
|
||||||
|
schedule_manager = Scheduler()
|
||||||
|
|
||||||
|
# Create all the timestamps at which the job must be done:
|
||||||
|
all_job_ts = []
|
||||||
|
offset_seconds = 0
|
||||||
|
while True:
|
||||||
|
ts = start_time + datetime.timedelta(seconds = offset_seconds)
|
||||||
|
if ts > end_time: break
|
||||||
|
all_job_ts.append(ts.time())
|
||||||
|
offset_seconds += proc_interval
|
||||||
|
|
||||||
|
# Add the jobs:
|
||||||
|
for ts in all_job_ts: schedule_manager.daily(ts, refresh_strategy_reference)
|
||||||
|
printer(len(all_job_ts))
|
||||||
|
|
||||||
|
# Run the heartbeat task and the infinite tick-reading loop:
|
||||||
tasks = [
|
tasks = [
|
||||||
ticks_from_kafka(
|
ticks_from_kafka(
|
||||||
consumer = kafka_consumer,
|
consumer = kafka_consumer,
|
||||||
fetch_count = 500,
|
fetch_count = 500,
|
||||||
fetch_timeout = 2.5
|
fetch_timeout = 2.5
|
||||||
),
|
),
|
||||||
heartbeat(interval_seconds = 1_800)
|
heartbeat(interval_seconds = heartbeat_interval)
|
||||||
]
|
]
|
||||||
|
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
|
||||||
@@ -394,12 +505,34 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
# Get the config from the command-line:
|
# Get the config from the command-line:
|
||||||
parser = argparse.ArgumentParser(description = f"SocketIO to serve live market data (and a general passthrough).")
|
parser = argparse.ArgumentParser(description = f"To implement trading strategies (0) for Mr. M. Bhandari.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s", "--script-id",
|
"-s", "--script-id",
|
||||||
type = str,
|
type = str,
|
||||||
help = "The id of this script (will affect the loaded config)."
|
help = "The id of this script (will affect the loaded config)."
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--start-time",
|
||||||
|
type = str,
|
||||||
|
help = "The 24-hr time of the day (in 'HH:MM:SS' format) from which the data can be refreshed."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--end-time",
|
||||||
|
type = str,
|
||||||
|
help = "The 24-hr time of the day (in 'HH:MM:SS' format) till which the data must be refreshed."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--proc-interval",
|
||||||
|
type = int,
|
||||||
|
help = "The no. of seconds after which you would like to refresh the data available from the stored procedure.",
|
||||||
|
default = 300
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--heartbeat-interval",
|
||||||
|
type = int,
|
||||||
|
help = "The no. of seconds after which you would like to send out a heartbeat to the admins.",
|
||||||
|
default = 1_800
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-d", "--debug",
|
"-d", "--debug",
|
||||||
action = "store_true",
|
action = "store_true",
|
||||||
@@ -415,11 +548,19 @@ if __name__ == "__main__":
|
|||||||
printer(str(args.debug))
|
printer(str(args.debug))
|
||||||
printer.disable()
|
printer.disable()
|
||||||
|
|
||||||
|
# Parse the inputs:
|
||||||
|
start_time = datetime.datetime.strptime(args.start_time, "%H:%M:%S")
|
||||||
|
end_time = datetime.datetime.strptime(args.end_time, "%H:%M:%S")
|
||||||
|
|
||||||
# Initialize and then run the script:
|
# Initialize and then run the script:
|
||||||
if await init(
|
if await init(
|
||||||
script_id = args.script_id,
|
script_id = args.script_id,
|
||||||
debug = args.debug
|
debug = args.debug
|
||||||
): await main(
|
): await main(
|
||||||
|
start_time = start_time,
|
||||||
|
end_time = end_time,
|
||||||
|
proc_interval = args.proc_interval,
|
||||||
|
heartbeat_interval = args.heartbeat_interval,
|
||||||
debug = args.debug
|
debug = args.debug
|
||||||
)
|
)
|
||||||
|
|
||||||
Reference in New Issue
Block a user