diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index 3a3ba5f..5314e07 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -6,7 +6,7 @@ from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Orde from v2realbot.indicators.indicators import ema from v2realbot.indicators.oscillators import rsi from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType -from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, round2five, is_open_rush, is_close_rush, eval_cond_dict, Average, crossed_down, crossed_up, crossed, is_pivot, json_serial +from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, round2five, is_open_rush, is_close_rush, is_window_open, eval_cond_dict, Average, crossed_down, crossed_up, crossed, is_pivot, json_serial from v2realbot.utils.directive_utils import get_conditions_from_configuration from v2realbot.common.model import SLHistory from datetime import datetime @@ -1108,13 +1108,17 @@ def next(data, state: StrategyState): #obecne precondition preds vstupem - platne jak pro condition based tak pro plugin def common_go_preconditions_check(signalname: str, options: dict): #ZAKLADNI KONTROLY ATRIBUTU s fallbackem na obecné - #check working windows - close_rush = safe_get(options, "close_rush",safe_get(state.vars, "open_rush",0)) - open_rush = safe_get(options, "open_rush",safe_get(state.vars, "close_rush",0)) + #check working windows (open - close, in minutes from the start of marker) + window_open = safe_get(options, "window_open",safe_get(state.vars, "window_open",0)) + window_close = safe_get(options, "window_close",safe_get(state.vars, "window_close",390)) - if is_open_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), open_rush) or is_close_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), close_rush): - state.ilog(e=f"SIGNAL {signalname} - RUSH STANDBY", msg=f"{open_rush=} {close_rush=} ") - return False + if is_window_open(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), window_open, window_close) is False: + state.ilog(e=f"SIGNAL {signalname} - WINDOW CLOSED", msg=f"{window_open=} {window_close=} ") + return False + + # if is_open_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), open_rush) or is_close_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), close_rush): + # state.ilog(e=f"SIGNAL {signalname} - WINDOW CLOSED", msg=f"{open_rush=} {close_rush=} ") + # return False #natvrdo nebo na podminku activated = safe_get(options, "activated", True) diff --git a/v2realbot/common/__pycache__/model.cpython-310.pyc b/v2realbot/common/__pycache__/model.cpython-310.pyc index 62fc75b..6329a60 100644 Binary files a/v2realbot/common/__pycache__/model.cpython-310.pyc and b/v2realbot/common/__pycache__/model.cpython-310.pyc differ diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index 36af815..dec2925 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -27,6 +27,7 @@ from alpaca.data.enums import Exchange class Intervals(BaseModel): start: str end: str + note: Optional[str] = None # Define the data model for the TestLists class TestList(BaseModel): @@ -71,6 +72,7 @@ class RunRequest(BaseModel): ilog_save: bool = False bt_from: datetime = None bt_to: datetime = None + test_batch_id: Optional[str] = None cash: int = 100000 diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index fbc923b..4ad56c4 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -6,7 +6,7 @@ from alpaca.data.requests import StockTradesRequest, StockBarsRequest from alpaca.data.enums import DataFeed from alpaca.data.timeframe import TimeFrame from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide -from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent +from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram from v2realbot.utils.ilog import delete_logs from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType @@ -19,6 +19,7 @@ from tinydb import TinyDB, Query, where from tinydb.operations import set import json from numpy import ndarray +from rich import print import pandas as pd from traceback import format_exc from datetime import timedelta, time @@ -309,8 +310,94 @@ def capsule(target: object, db: object): db.runners.remove(i) print("Runner STOPPED") + +#vrátí konkrétní sadu testlistu +def get_testlist_byID(record_id: str): + conn = pool.get_connection() + try: + cursor = conn.cursor() + cursor.execute("SELECT id, name, dates FROM test_list WHERE id = ?", (record_id,)) + row = cursor.fetchone() + finally: + pool.release_connection(conn) + + if row is None: + return -2, "not found" + else: + return 0, TestList(id=row[0], name=row[1], dates=json.loads(row[2])) + + +#volano pro batchove spousteni (BT,) +def run_batch_stratin(id: UUID, runReq: RunRequest): + if runReq.test_batch_id is None: + return (-1, "batch_id required for batch run") + + if runReq.mode != Mode.BT: + return (-1, "batch run only for backtest") + + print("request values:", runReq) + + print("getting intervals") + testlist: TestList + + res, testlist = get_testlist_byID(record_id=runReq.test_batch_id) + + if res < 0: + return (-1, f"not existing ID of testlists with {runReq.test_batch_id}") + +#spousti se vlakno s paralelnim behem a vracime ok + ridici_vlakno = Thread(target=batch_run_manager, args=(id, runReq, testlist), name=f"Batch run controll thread started.") + ridici_vlakno.start() + print(enumerate()) + + return 0, f"Batch run started" + + +#thread, ktery bude ridit paralelni spousteni +# bud ceka na dokonceni v runners nebo to bude ridit jinak a bude mit jednoho runnera? +# nejak vymyslet. +# logovani zatim jen do print +def batch_run_manager(id: UUID, runReq: RunRequest, testlist: TestList): + #zde muzu iterovat nad intervaly + #cekat az dobehne jeden interval a pak spustit druhy + #pripadne naplanovat beh - to uvidim + #domyslet kompatibilitu s budoucim automatickym "dennim" spousteni strategii + #taky budu mit nejaky konfiguracni RUN MANAGER, tak by krome rizeniho denniho runu + #mohl podporovat i BATCH RUNy. + batch_id = str(uuid4())[:8] + print("generated batch_ID", batch_id) + + print("test batch", testlist) + + print("test date", testlist.dates) + interval: Intervals + cnt_max = len(testlist.dates) + cnt = 0 + for intrvl in testlist.dates: + cnt += 1 + interval = intrvl + if interval.note is not None: + print("mame zde note") + print("Datum od", interval.start) + print("Datum do", interval.end) + print("starting") + + #předání atributů datetime.fromisoformat + runReq.bt_from = datetime.fromisoformat(interval.start) + runReq.bt_to = datetime.fromisoformat(interval.end) + runReq.note = f"Batch {batch_id} run #{cnt}/{cnt_max} Note:{interval.note}" + + #protoze jsme v ridicim vlaknu, poustime za sebou jednotlive stratiny v synchronnim modu + res, id_val = run_stratin(id=id, runReq=runReq, synchronous=True) + if res < 0: + print(f"CHyba v runu #{cnt} od:{runReq.bt_from} do {runReq.bt_to} -> {id_val}") + break + + print("Batch manager FINISHED") + + #stratin run -def run_stratin(id: UUID, runReq: RunRequest): +def run_stratin(id: UUID, runReq: RunRequest, synchronous: bool = False): if runReq.mode == Mode.BT: if runReq.bt_from is None: return (-1, "start date required for BT") @@ -405,6 +492,12 @@ def run_stratin(id: UUID, runReq: RunRequest): print(db.runners) print(i) print(enumerate()) + + #pokud spoustime v batch módu, tak čekáme na výsledek a pak pouštíme další run + if synchronous: + print(f"waiting for thread {vlakno} to finish") + vlakno.join() + return (0, id) except Exception as e: return (-2, "Exception: "+str(e)+format_exc()) diff --git a/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc b/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc index 2d13f6f..74bdcc9 100644 Binary files a/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc and b/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc differ diff --git a/v2realbot/main.py b/v2realbot/main.py index 9c51c97..d728bca 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -226,7 +226,11 @@ def _get_stratin(stratin_id) -> StrategyInstance: @app.put("/stratins/{stratin_id}/run", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) def _run_stratin(stratin_id: UUID, runReq: RunRequest): - res, id = cs.run_stratin(id=stratin_id, runReq=runReq) + print(runReq) + if runReq.test_batch_id is not None: + res, id = cs.run_batch_stratin(id=stratin_id, runReq=runReq) + else: + res, id = cs.run_stratin(id=stratin_id, runReq=runReq) if res == 0: return id elif res < 0: raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {res}:{id}") @@ -336,8 +340,8 @@ def _get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, dateti @app.post('/testlists/', dependencies=[Depends(api_key_auth)]) def create_record(testlist: TestList): # Generate a new UUID for the record - testlist.id = str(uuid4()) - + testlist.id = str(uuid4())[:8] + # Insert the record into the database conn = pool.get_connection() cursor = conn.cursor() @@ -366,17 +370,12 @@ def get_testlists(): # API endpoint to retrieve a single record by ID @app.get('/testlists/{record_id}') def get_testlist(record_id: str): - conn = pool.get_connection() - cursor = conn.cursor() - cursor.execute("SELECT id, name, dates FROM test_list WHERE id = ?", (record_id,)) - row = cursor.fetchone() - pool.release_connection(conn) - - if row is None: + res, testlist = cs.get_testlist_byID(record_id=record_id) + + if res == 0: + return testlist + elif res < 0: raise HTTPException(status_code=404, detail='Record not found') - - testlist = TestList(id=row[0], name=row[1], dates=json.loads(row[2])) - return testlist # API endpoint to update a record @app.put('/testlists/{record_id}') diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index dd60301..7555b30 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -38,7 +38,10 @@ + + +