diff --git a/test.db b/test.db new file mode 100644 index 0000000..33e5e0a Binary files /dev/null and b/test.db differ diff --git a/testy/testSqlite3.py b/testy/testSqlite3.py new file mode 100644 index 0000000..0e81d66 --- /dev/null +++ b/testy/testSqlite3.py @@ -0,0 +1,97 @@ +import sqlite3 +from v2realbot.config import DATA_DIR +from v2realbot.utils.utils import json_serial +from uuid import UUID, uuid4 +import json +from datetime import datetime +from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account + +sqlite_db_file = DATA_DIR + "/v2trading.db" +conn = sqlite3.connect(sqlite_db_file) +#standardne vraci pole tuplů, kde clen tuplu jsou sloupce +#conn.row_factory = lambda c, r: json.loads(r[0]) +#conn.row_factory = lambda c, r: r[0] +#conn.row_factory = sqlite3.Row + +#CREATE TABLE +# c = conn.cursor() +# createTable= "CREATE TABLE runner_logs (runner_id varchar(32) NOT NULL, time real NOT NULL, data json NOT NULL);" +# print(c.execute(createTable)) +# sql = ("CREATE INDEX index_runner_logs ON runner_logs (runner_id, time);") +# print(c.execute(sql)) + +#testovaci objekty +insert = dict(time=datetime.now(), side="ddd", rectype=RecordType.BAR, id=uuid4()) +insert_list = [dict(time=datetime.now().timestamp(), side="ddd", rectype=RecordType.BAR, id=uuid4()),dict(time=datetime.now().timestamp(), side="ddd", rectype=RecordType.BAR, id=uuid4()),dict(time=datetime.now().timestamp(), side="ddd", rectype=RecordType.BAR, id=uuid4()),dict(time=datetime.now().timestamp(), side="ddd", rectype=RecordType.BAR, id=uuid4())] + +def insert_log(runner_id: UUID, time: float, logdict: dict): + c = conn.cursor() + json_string = json.dumps(logdict, default=json_serial) + res = c.execute("INSERT INTO runner_logs VALUES (?,?,?)",[str(runner_id), time, json_string]) + conn.commit() + return res.rowcount + +def insert_log_multiple(runner_id: UUID, loglist: list): + c = conn.cursor() + insert_data = [] + for i in loglist: + row = (str(runner_id), i["time"], json.dumps(i, default=json_serial)) + insert_data.append(row) + c.executemany("INSERT INTO runner_logs VALUES (?,?,?)", insert_data) + conn.commit() + return c.rowcount + + # c = conn.cursor() + # json_string = json.dumps(logdict, default=json_serial) + # res = c.execute("INSERT INTO runner_logs VALUES (?,?,?)",[str(runner_id), time, json_string]) + # print(res) + # conn.commit() + # return res + +#returns list of ilog jsons +def read_log_window(runner_id: UUID, timestamp_from: float, timestamp_to: float): + conn.row_factory = lambda c, r: json.loads(r[0]) + c = conn.cursor() + res = c.execute(f"SELECT data FROM runner_logs WHERE runner_id='{str(runner_id)}' AND time >={ts_from} AND time <={ts_to}") + return res.fetchall() + +#returns number of deleted elements +def delete_logs(runner_id: UUID): + c = conn.cursor() + res = c.execute(f"DELETE from runner_logs WHERE runner_id='{str(runner_id)}';") + print(res.rowcount) + conn.commit() + return res.rowcount + +print(insert_log(str(uuid4()), datetime.now().timestamp(), insert)) +c = conn.cursor() +ts_from = 1683108821.08872 +ts_to = 1683108821.08874 +# res = c.execute(f"SELECT runner_id, time, data FROM runner_logs where time > {ts_from} and time <{ts_to}") +# result = res.fetchall() + +# res= delete_logs("7f9866ac-c742-47f4-a329-1d2b6721e781") +# print(res) + +# res = read_log_window(runner_id="33", timestamp_from=11 , timestamp_to=22) +# print(res) + +res = insert_log_multiple(uuid4(), insert_list) +print(res) + +# res = read_log_window("3340e257-d19a-4179-baf3-3b39190acde3", ts_from, ts_to) + +# print(res) + +# for r in res.fetchall(): +# print(dict(r)) + + +#print(res.description) +#print(result) + + + + + + diff --git a/testy/tinyDBselect.py b/testy/tinyDBselect.py index 5a7aae1..ff247ce 100644 --- a/testy/tinyDBselect.py +++ b/testy/tinyDBselect.py @@ -19,14 +19,58 @@ from tinydb.operations import set import json from rich import print -arch_header_file = DATA_DIR + "/arch_header.json" -arch_detail_file = DATA_DIR + "/arch_detail.json" -#db layer to store runner archive -db_arch_h = TinyDB(arch_header_file, default=json_serial) -db_arch_d = TinyDB(arch_detail_file, default=json_serial) +#vyzkouset https://github.com/MrPigss/BetterJSONStorage + + +insert = {'datum': datetime.now(), 'side': "dd", 'name': 'david','id': uuid4(), 'order': "neco"} +class RunnerLogger: + def __init__(self, runner_id: UUID) -> None: + self.runner_id = runner_id + runner_log_file = DATA_DIR + "/runner_log.json" + db_runner_log = TinyDB(runner_log_file, default=json_serial) + +def insert_log_multiple(runner_id: UUID, logList: list): + runner_table = db_runner_log.table(str(runner_id)) + res = runner_table.insert_multiple(logList) + return res + +def insert_log(runner_id: UUID, logdict: dict): + runner_table = db_runner_log.table(str(runner_id)) + res = runner_table.insert(logdict) + return res + + +def read_log_window(runner_id: UUID, timestamp_from: float, timestamp_to: float): + runner_table = db_runner_log.table(str(runner_id)) + res = runner_table.search((where('datum') >= timestamp_from) & (where('datum') <= timestamp_to)) + if len(res) == 0: + return -1, "not found" + return 0, res + +def delete_log(runner_id: UUID): + res = db_runner_log.drop_table(str(runner_id)) + if res is None: + return -1, "not found" + return 0, runner_id + +# runner_id = uuid4() +# for i in range(0,10): +# print(insert_log(runner_id, insert)) + +print(delete_log(runner_id="2459a6ff-a350-44dc-9c14-11cfae07f7e9")) + +print(read_log_window("ae9cdf8f-5cd0-4a49-8cfe-c486e21cb4fa",1,99999999999999)) + + +#2459a6ff-a350-44dc-9c14-11cfae07f7e9 +#ae9cdf8f-5cd0-4a49-8cfe-c486e21cb4fa + + +#db_runner_log.drop_tables() +print(db_runner_log.tables()) # res = db_arch_h.update(set('note', "ahoj"), where('id') == "74aa524e-3ed4-41fb-8166-f20946520344") # print(res) -res = db_arch_d.all() -print(res) \ No newline at end of file +#res = db_runner_log.all() +#print(res) diff --git a/testy/tinyFLUXtest.py b/testy/tinyFLUXtest.py new file mode 100644 index 0000000..fe911e0 --- /dev/null +++ b/testy/tinyFLUXtest.py @@ -0,0 +1,44 @@ + +from typing import Any, List +from uuid import UUID, uuid4 +import pickle +from alpaca.data.historical import StockHistoricalDataClient +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 +from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange +from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial +from datetime import datetime +from threading import Thread, current_thread, Event, enumerate +from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR +import importlib +from queue import Queue +#from tinydb import TinyDB, Query, where +#from tinydb.operations import set +import json +from rich import print +from tinyflux import Point, TinyFlux + + +runner_log_file = DATA_DIR + "/runner_flux__log.json" +#db layer to store runner archive +db_runner_log = TinyFlux(runner_log_file) + +insert_dict = {'datum': datetime.now(), 'side': "dd", 'name': 'david','id': uuid4(), 'order': "neco"} +#json.dumps(insert_dict, default=json_serial) +p1 = Point(time=datetime.now(), tags=insert_dict) + +db_runner_log.insert(p1) + +res=db_runner_log.all() +print(res) + + +# #db_runner_log.drop_table('hash') +# res = runner_table.get(where('side') == "dd") +# print(res) +# # res = db_arch_h.update(set('note', "ahoj"), where('id') == "74aa524e-3ed4-41fb-8166-f20946520344") +# # print(res) +# res = runner_table.all() +# print(res) diff --git a/testy/tinydbTest.py b/testy/tinydbTest.py index 62a62c4..fb3ef5e 100644 --- a/testy/tinydbTest.py +++ b/testy/tinydbTest.py @@ -114,6 +114,10 @@ db = TinyDB(db_file, default=json_serial) db.truncate() insert = {'datum': datetime.now(), 'side': OrderSide.BUY, 'name': 'david','id': uuid4(), 'order': orderList} + + + + #insert record db.insert(a.__dict__) diff --git a/v2realbot/ENTRY_backtest_strategyVykladaci.py b/v2realbot/ENTRY_backtest_strategyVykladaci.py index ce8098d..9dcaa02 100644 --- a/v2realbot/ENTRY_backtest_strategyVykladaci.py +++ b/v2realbot/ENTRY_backtest_strategyVykladaci.py @@ -191,6 +191,8 @@ def next(data, state: StrategyState): state.vars.blockbuy = 0 return 0 + state.ilog(e="-----") + try: ## slope vyresi rychlé sesupy - jeste je treba podchytit pomalejsi sesupy @@ -245,6 +247,10 @@ def next(data, state: StrategyState): # state.ilog(e="Slope - MA"+str(state.indicators.slopeMA[-1])) else: + #pokud plnime historii musime ji plnit od zacatku, vsehcny idenitifkatory maji spolecny time + #kvuli spravnemu zobrazovani na gui + state.indicators.slope.append(0) + state.indicators.slopeMA.append(0) state.ilog(e="Slope - not enough data", slope_lookback=slope_lookback) except Exception as e: @@ -357,7 +363,7 @@ def next(data, state: StrategyState): #HLAVNI ITERACNI LOG JESTE PRED AKCI - obsahuje aktualni hodnoty vetsiny parametru lp = state.interface.get_last_price(symbol=state.symbol) - state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)} DEF:{str(is_defensive_mode())}", last_price=lp, stratvars=state.vars) + state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)} DEF:{str(is_defensive_mode())}", last_price=lp, data=data, stratvars=state.vars) #maxSlopeMA = -0.03 #SLOPE ANGLE PROTECTIONs @@ -461,7 +467,7 @@ def init(state: StrategyState): state.indicators['slope'] = [] state.indicators['slopeMA'] = [] #static indicators - those not series based - state.statinds['angle'] = {} + state.statinds['angle'] = dict(minimum_slope=state.vars["minimum_slope"]) state.vars["ticks2reset_backup"] = state.vars.ticks2reset def main(): @@ -475,7 +481,7 @@ def main(): name = os.path.basename(__file__) se = Event() pe = Event() - s = StrategyOrderLimitVykladaci(name = name, symbol = "BAC", account=Account.ACCOUNT1, next=next, init=init, stratvars=stratvars, open_rush=10, close_rush=0, pe=pe, se=se) + s = StrategyOrderLimitVykladaci(name = name, symbol = "BAC", account=Account.ACCOUNT1, next=next, init=init, stratvars=stratvars, open_rush=10, close_rush=0, pe=pe, se=se, ilog_save=True) s.set_mode(mode = Mode.BT, debug = False, start = datetime(2023, 4, 14, 10, 42, 0, 0, tzinfo=zoneNY), diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index e3b2ecb..d49f68a 100644 Binary files a/v2realbot/__pycache__/config.cpython-310.pyc and b/v2realbot/__pycache__/config.cpython-310.pyc differ diff --git a/v2realbot/common/__pycache__/model.cpython-310.pyc b/v2realbot/common/__pycache__/model.cpython-310.pyc index 9978461..cd3cb2d 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 e6c038b..c62b13f 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -58,20 +58,21 @@ class RunRequest(BaseModel): mode: Mode note: Optional[str] = None debug: bool = False + ilog_save: bool = False bt_from: datetime = None bt_to: datetime = None cash: int = 100000 - - class RunnerView(BaseModel): id: UUID + strat_id: UUID run_started: Optional[datetime] = None run_mode: Mode run_name: Optional[str] = None run_note: Optional[str] = None run_account: Account + run_ilog_save: Optional[bool] = False run_symbol: Optional[str] = None run_trade_count: Optional[int] = 0 run_profit: Optional[float] = 0 @@ -83,12 +84,14 @@ class RunnerView(BaseModel): #Running instance - not persisted class Runner(BaseModel): id: UUID + strat_id: UUID run_started: Optional[datetime] = None run_mode: Mode run_account: Account run_symbol: Optional[str] = None run_name: Optional[str] = None run_note: Optional[str] = None + run_ilog_save: Optional[bool] = False run_trade_count: Optional[int] run_profit: Optional[float] run_positions: Optional[int] @@ -174,6 +177,8 @@ class RunArchive(BaseModel): bt_from: Optional[datetime] = None bt_to: Optional[datetime] = None stratvars: Optional[dict] = None + settings: Optional[dict] = None + ilog_save: Optional[bool] = False profit: float = 0 trade_count: int = 0 end_positions: int = 0 diff --git a/v2realbot/config.py b/v2realbot/config.py index a272a83..bdec1cb 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -2,6 +2,8 @@ from alpaca.data.enums import DataFeed from v2realbot.enums.enums import Mode, Account, FillCondition from appdirs import user_data_dir + +LOG_RUNNER_EVENTS = False #no print in console QUIET_MODE = False #how many consecutive trades with the fill price are necessary for LIMIT fill to happen in backtesting diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 92eee53..70a259d 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -6,11 +6,12 @@ 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 -from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange -from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial +from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar +from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours +from v2realbot.utils.ilog import delete_logs from datetime import datetime from threading import Thread, current_thread, Event, enumerate -from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR +from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR,BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_LOG_SURROUNDING_TRADES,BT_FILL_CONDITION_BUY_LIMIT,BT_FILL_CONDITION_SELL_LIMIT import importlib from queue import Queue from tinydb import TinyDB, Query, where @@ -117,7 +118,7 @@ def delete_stratin(id: UUID): def inject_stratvars(id: UUID, stratvars_parsed_new: AttributeDict, stratvars_parsed_old: AttributeDict): for i in db.runners: - if str(i.id) == str(id): + if str(i.strat_id) == str(id): #inject only those changed, some of them cannot be changed (for example pendingbuys) changed_keys = [] @@ -180,7 +181,7 @@ def modify_stratin_running(si: StrategyInstance, id: UUID): ##enable realtime chart - inject given queue for strategy instance ##webservice listens to this queue -async def stratin_realtime_on(id: UUID, rtqueue: Queue): +async def runner_realtime_on(id: UUID, rtqueue: Queue): for i in db.runners: if str(i.id) == str(id): i.run_instance.rtqueue = rtqueue @@ -189,7 +190,7 @@ async def stratin_realtime_on(id: UUID, rtqueue: Queue): print("ERROR NOT FOUND") return -2 -async def stratin_realtime_off(id: UUID): +async def runner_realtime_off(id: UUID): for i in db.runners: if str(i.id) == str(id): i.run_instance.rtqueue = None @@ -199,7 +200,7 @@ async def stratin_realtime_off(id: UUID): return -2 ##controller (run_stratefy, pause, stop, reload_params) -def pause_stratin(id: UUID): +def pause_runner(id: UUID): for i in db.runners: print(i.id) if str(i.id) == id: @@ -215,7 +216,7 @@ def pause_stratin(id: UUID): print("no ID found") return (-1, "not running instance found") -def stop_stratin(id: UUID = None): +def stop_runner(id: UUID = None): chng = [] for i in db.runners: #print(i['id']) @@ -236,6 +237,13 @@ def stop_stratin(id: UUID = None): return (-2, "not found" + str(id)) def is_stratin_running(id: UUID): + for i in db.runners: + if str(i.strat_id) == str(id): + if i.run_started is not None and i.run_stopped is None: + return True + return False + +def is_runner_running(id: UUID): for i in db.runners: if str(i.id) == str(id): if i.run_started is not None and i.run_stopped is None: @@ -280,14 +288,14 @@ def capsule(target: object, db: object): i.run_pause_ev = None i.run_stop_ev = None #ukladame radek do historie (pozdeji refactor) - save_history(id=i.id, st=target, runner=i, reason=reason) + save_history(id=i.strat_id, st=target, runner=i, reason=reason) #store in archive header and archive detail archive_runner(runner=i, strat=target) #mazeme runner po skonceni instance db.runners.remove(i) print("Runner STOPPED") - +#stratin run def run_stratin(id: UUID, runReq: RunRequest): if runReq.mode == Mode.BT: if runReq.bt_from is None: @@ -336,7 +344,12 @@ def run_stratin(id: UUID, runReq: RunRequest): next=next, init=init, stratvars=stratvars, - open_rush=open_rush, close_rush=close_rush, pe=pe, se=se) + open_rush=open_rush, + close_rush=close_rush, + pe=pe, + se=se, + runner_id=id, + ilog_save=runReq.ilog_save) print("instance vytvorena", instance) #set mode if runReq.mode == Mode.LIVE or runReq.mode == Mode.PAPER: @@ -359,7 +372,9 @@ def run_stratin(id: UUID, runReq: RunRequest): vlakno.start() print("Spuštěna", instance.name) ##storing the attributtes - pozor pri stopu je zase odstranit - runner = Runner(id = i.id, + #id runneru je nove id, stratin se dava dalsiho parametru + runner = Runner(id = id, + strat_id = i.id, run_started = datetime.now(zoneNY), run_pause_ev = pe, run_name = name, @@ -368,13 +383,14 @@ def run_stratin(id: UUID, runReq: RunRequest): run_stop_ev = se, run_thread = vlakno, run_account = runReq.account, + run_ilog_save = runReq.ilog_save, run_mode = runReq.mode, run_instance = instance) db.runners.append(runner) print(db.runners) print(i) print(enumerate()) - return (0, i.id) + return (0, id) except Exception as e: return (-2, "Exception: "+str(e)) return (-2, "not found") @@ -403,9 +419,17 @@ def archive_runner(runner: Runner, strat: StrategyInstance): else: bp_from = None bp_to = None - id = uuid4() - runArchive: RunArchive = RunArchive(id = id, - strat_id = runner.id, + + settings = dict(resolution=strat.state.timeframe, + rectype=strat.state.rectype, + configs=dict( + BT_FILL_CONS_TRADES_REQUIRED=BT_FILL_CONS_TRADES_REQUIRED, + BT_FILL_LOG_SURROUNDING_TRADES=BT_FILL_LOG_SURROUNDING_TRADES, + BT_FILL_CONDITION_BUY_LIMIT=BT_FILL_CONDITION_BUY_LIMIT, + BT_FILL_CONDITION_SELL_LIMIT=BT_FILL_CONDITION_SELL_LIMIT)) + + runArchive: RunArchive = RunArchive(id = runner.id, + strat_id = runner.strat_id, name=runner.run_name, note=runner.run_note, symbol=runner.run_symbol, @@ -413,9 +437,11 @@ def archive_runner(runner: Runner, strat: StrategyInstance): stopped=runner.run_stopped, mode=runner.run_mode, account=runner.run_account, + ilog_save=runner.run_ilog_save, bt_from=bp_from, bt_to = bp_to, stratvars = strat.state.vars, + settings = settings, profit=round(float(strat.state.profit),2), trade_count=len(strat.state.tradeList), end_positions=strat.state.positions, @@ -434,7 +460,7 @@ def archive_runner(runner: Runner, strat: StrategyInstance): print("is not numpy", key, value) flattened_indicators[key]= value - runArchiveDetail: RunArchiveDetail = RunArchiveDetail(id = id, + runArchiveDetail: RunArchiveDetail = RunArchiveDetail(id = runner.id, name=runner.run_name, bars=strat.state.bars, indicators=flattened_indicators, @@ -452,14 +478,15 @@ def get_all_archived_runners(): res = db_arch_h.all() return 0, res -#delete runner in archive and archive detail +#delete runner in archive and archive detail and runner logs def delete_archived_runners_byID(id: UUID): try: resh = db_arch_h.remove(where('id') == id) resd = db_arch_d.remove(where('id') == id) - if len(resh) == 0 or len(resd) == 0: - return -1, "not found "+str(resh) + " " + str(resd) - return 0, str(resh) + " " + str(resd) + reslogs = delete_logs(id) + if len(resh) == 0 or len(resd) == 0 or reslogs ==0: + return -1, "not found "+str(resh) + " " + str(resd) + " " + str(reslogs) + return 0, str(resh) + " " + str(resd) + " " + str(reslogs) except Exception as e: return -2, str(e) @@ -493,7 +520,30 @@ def get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, datetim #datetime_object_from = datetime(2023, 2, 27, 18, 51, 38, tzinfo=datetime.timezone.utc) #datetime_object_to = datetime(2023, 2, 27, 21, 51, 39, tzinfo=datetime.timezone.utc) bar_request = StockBarsRequest(symbol_or_symbols=symbol,timeframe=timeframe, start=datetime_object_from, end=datetime_object_to, feed=DataFeed.SIP) + #print("before df") bars = client.get_stock_bars(bar_request) + result = [] + for row in bars.data[symbol]: + if is_open_hours(row.timestamp): + result.append(row) + + # print("df", bars) + # print(bars.info()) + # bars = bars.droplevel(0) + # print("after drop", bars) + # print(bars.info()) + # print("before tz", bars) + # bars = bars.tz_convert('America/New_York') + # print("before time", bars) + # bars = bars.between_time("9:30","16:00") + # print("after time", bars) + # bars = bars.reset_index() + # bars = bars.to_dict(orient="records") + #print(ohlcvList) + #ohlcvList = {} + + #bars = {} + return 0, bars.data[symbol] except Exception as e: return -2, str(e) diff --git a/v2realbot/main.py b/v2realbot/main.py index 33e2221..4710279 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -13,6 +13,7 @@ from fastapi.security import APIKeyHeader import uvicorn from uuid import UUID import v2realbot.controller.services as cs +from v2realbot.utils.ilog import get_log_window from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveDetail, Bar, RunArchiveChange from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query from fastapi.responses import HTMLResponse, FileResponse @@ -95,14 +96,14 @@ async def websocket_endpoint( api_key: Annotated[str, Depends(get_api_key)], ): await websocket.accept() - if not cs.is_stratin_running(runner_id): + if not cs.is_runner_running(runner_id): #await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA, reason="Strat not running") - raise WebSocketException(code=status.WS_1003_UNSUPPORTED_DATA, reason="Stratin not running.") + raise WebSocketException(code=status.WS_1003_UNSUPPORTED_DATA, reason="Runner not running.") return else: print("stratin exists") q: Queue = Queue() - await cs.stratin_realtime_on(id=runner_id, rtqueue=q) + await cs.runner_realtime_on(id=runner_id, rtqueue=q) # tx task; reads data from queue and sends to websocket async def websocket_tx_task(ws, _q): @@ -158,7 +159,7 @@ async def websocket_endpoint( print("CLIENT DISCONNECTED for", runner_id) finally: q.put("break") - await cs.stratin_realtime_off(runner_id) + await cs.runner_realtime_off(runner_id) @app.get("/threads/", dependencies=[Depends(api_key_auth)]) def _get_all_threads(): @@ -227,16 +228,16 @@ def _run_stratin(stratin_id: UUID, runReq: RunRequest): elif res < 0: raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {res}:{id}") -@app.put("/stratins/{stratin_id}/pause", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) -def _pause_stratin(stratin_id): - res, id = cs.pause_stratin(id=stratin_id) +@app.put("/runners/{runner_id}/pause", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) +def _pause_runner(runner_id): + res, id = cs.pause_runner(id=runner_id) if res == 0: return id elif res < 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}") -@app.put("/stratins/{stratin_id}/stop", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) -def _stop_stratin(stratin_id): - res, id = cs.stop_stratin(id=stratin_id) +@app.put("/runners/{runner_id}/stop", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) +def _stop_runner(runner_id): + res, id = cs.stop_runner(id=runner_id) if res == 0: return id elif res < 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}") @@ -248,9 +249,9 @@ def _delete_stratin(stratin_id): elif res < 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}") -@app.put("/stratins/stop", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) -def stop_all_stratins(): - res, id = cs.stop_stratin() +@app.put("/runners/stop", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) +def stop_all_runners(): + res, id = cs.stop_runner() if res == 0: return id elif res < 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}") @@ -310,6 +311,15 @@ def _get_archived_runner_details_byID(runner_id) -> RunArchiveDetail: else: raise HTTPException(status_code=404, detail=f"No runner with id: {runner_id} a {set}") +#get archived runners detail by id +@app.get("/archived_runners_log/{runner_id}", dependencies=[Depends(api_key_auth)]) +def _get_archived_runner_log_byID(runner_id: UUID, timestamp_from: float, timestamp_to: float) -> list[dict]: + res = get_log_window(runner_id,timestamp_from, timestamp_to) + if len(res) > 0: + return res + else: + raise HTTPException(status_code=404, detail=f"No logs found with id: {runner_id} and between {timestamp_from} and {timestamp_to}") + #get alpaca history bars @app.get("/history_bars/", dependencies=[Depends(api_key_auth)]) def _get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, datetime_object_to: datetime, timeframe_amount: int, timeframe_unit: TimeFrameUnit) -> list[Bar]: @@ -317,7 +327,7 @@ def _get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, dateti if res == 0: return set else: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found") + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found {res} {set}") #join cekej na dokonceni vsech diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index 7fbdd62..53de61e 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -1,5 +1,5 @@ - + @@ -51,12 +51,16 @@ -
+
-

+                        
+
+

+                            

+                        
@@ -95,16 +99,18 @@ - +
+ - + + @@ -152,6 +158,7 @@ + @@ -159,20 +166,22 @@ + - + - + + - - - + + + @@ -216,7 +225,7 @@
- +
@@ -245,6 +254,7 @@ +
IdStratId Started Mode SymbolAccountAccountilog Paused Profit Trades
IdStratId NameSymbolSym Note started stopped mode account bt_frombt_tobt_toilog stratvars profittradecntend_posend_pos_avgptradepospos_avgp open
@@ -418,8 +428,16 @@
- - + + +
+
+ + +
+
+ +
+
+ +
+ config options +
+

diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index 95c8152..640e72c 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -1,6 +1,10 @@ var tradeDetails = new Map(); var toolTip = null var CHART_SHOW_TEXT = false +// var vwapSeries = null +// var volumeSeries = null +var markersLine = null +var avgBuyLine = null //TRANSFORM object returned from RESTA PI get_arch_run_detail //to series and markers required by lightweigth chart //input array object bars = { high: [1,2,3], time: [1,2,3], close: [2,2,2]...} @@ -42,10 +46,25 @@ function transform_data(data) { var avgp_markers = [] var markers = [] var markers_line = [] + var last_timestamp = 0.1 + var iterator = 0.001 data.trades.forEach((trade, index, array) => { obj = {}; a_markers = {} timestamp = Date.parse(trade.order.filled_at)/1000 + //light chart neumi vice zaznamu ve stejny cas + //protoze v BT se muze stat vice tradu v jeden cas, testujeme stejne hodnoty a pripadne pricteme jednu ms + //tradu s jednim casem muze byt za sebou vic, proto iterator + if (last_timestamp == timestamp) { + last_timestamp = timestamp + timestamp = timestamp + iterator + iterator += 0.001 + } + else { + last_timestamp = timestamp + iterator = 0.001 + } + if (trade.order.side == "buy") { //avgp lajnu vytvarime jen pokud je v tradeventu prumerna cena if (trade.pos_avg_price !== null) { @@ -103,12 +122,11 @@ function transform_data(data) { transformed["markers_line"] = markers_line transformed["avgp_markers"] = avgp_markers //get additional indicators - //TBD return transformed } //unit: Min, Hour, Day, Week, Month -//prepare data before displaying archived chart - fetch history bars if necessary +//prepares data before displaying archived chart - fetch history bars if necessary function prepare_data(archRunner, timeframe_amount, timeframe_unit, archivedRunnerDetail) { req = {} req["symbol"] = archRunner.symbol @@ -133,7 +151,7 @@ function prepare_data(archRunner, timeframe_amount, timeframe_unit, archivedRunn contentType: "application/json", data: req, success:function(data){ - console.log("one minute bars", JSON.stringify(data)) + console.log("one minute bars before", JSON.stringify(data)) data.map((el)=>{ cas = new Date(el.timestamp) el.time = cas.getTime()/1000; @@ -154,7 +172,6 @@ function prepare_data(archRunner, timeframe_amount, timeframe_unit, archivedRunn }) } - //render chart of archived runs function chart_archived_run(archRecord, data, oneMinuteBars) { if (chart !== null) { @@ -203,105 +220,270 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { //initialize chart document.getElementById("chart").style.display = "block" - var chartOptions = { width: 1300, - height: 600, - leftPriceScale: {visible: true}, - layout: { - background: { - type: 'solid', - color: '#000000', - }, - textColor: '#d1d4dc', - }, - grid: { - vertLines: { - visible: true, - color: "#434d46" - }, - horzLines: { - color: "#667069", - visible:true - }, - }, - } - chart = LightweightCharts.createChart(container1, chartOptions); - chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: { - mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true - }}) + initialize_chart() container1.append(switcherElement) - var archCandlestickSeries = null + candlestickSeries = null switch_to_interval(intervals[1]) chart.timeScale().fitContent(); function switch_to_interval(interval) { - //prip prenuti prepisujeme candlestick a markery - - if (archCandlestickSeries) { + //prip prpenuti prepisujeme candlestick a markery + if (candlestickSeries) { last_range = chart.timeScale().getVisibleRange() - chart.removeSeries(archCandlestickSeries); - archCandlestickSeries = null + chart.removeSeries(candlestickSeries); + candlestickSeries = null } else { last_range = null } - archCandlestickSeries = chart.addCandlestickSeries({ lastValueVisible: true, priceLineWidth:2, priceLineColor: "red", priceFormat: { type: 'price', precision: 2, minMove: 0.01 }}); - archCandlestickSeries.priceScale().applyOptions({ - scaleMargins: { - top: 0.1, // highest point of the series will be 10% away from the top - bottom: 0.4, // lowest point will be 40% away from the bottom - }, - }); - archCandlestickSeries.setData(AllCandleSeriesesData.get(interval)); + + if (interval == native_resolution) { + //indicators are in native resolution only + display_indicators(data); + } + else { + remove_indicators(); + } + + intitialize_candles() + candlestickSeries.setData(AllCandleSeriesesData.get(interval)); + if (last_range) { chart.timeScale().setVisibleRange(last_range); } } - var archVwapSeries = chart.addLineSeries({ - // title: "vwap", - color: '#2962FF', - lineWidth: 1, - lastValueVisible: false - }); - - var archVolumeSeries = chart.addHistogramSeries({title: "Volume", color: '#26a69a', priceFormat: {type: 'volume'}, priceScaleId: ''}); - archVolumeSeries.priceScale().applyOptions({ - // set the positioning of the volume series - scaleMargins: { - top: 0.7, // highest point of the series will be 70% away from the top - bottom: 0, - }, - }); + //TBD + //pro kazdy identifikator zobrazime button na vypnuti zapnuti + //vybereme barvu pro kazdy identifikator + //zjistime typ idenitfikatoru - zatim right vs left + function display_indicators(data) { + console.log("indikatory", JSON.stringify(data.indicators,null,2)) + //podobne v livewebsokcets.js - dat do jedne funkce + if (data.hasOwnProperty("indicators")) { + // console.log("jsme uvnitr indikatoru") + var indicators = data.indicators + //if there are indicators it means there must be at least two keys (time which is always present) + if (Object.keys(indicators).length > 1) { + for (const [key, value] of Object.entries(indicators)) { + if (key !== "time") { + //initialize indicator and store reference to array + var obj = {name: key, series: null} + //start + //console.log(key) + //get configuation of indicator to display + conf = get_ind_config(key) + + //INIT INDICATOR BASED on CONFIGURATION + + //MOVE TO UTILS ro reuse?? + if (conf && conf.display) { + + //tranform data do správného formátru + items = [] + value.forEach((element, index, array) => { + item = {} + + item["time"] = indicators.time[index] + item["value"] = element + //console.log("objekt indicatoru",item) + items.push(item) + }); + + if (conf.embed) { + obj.series = chart.addLineSeries({ + color: colors.shift(), + priceScaleId: conf.priceScaleId, + title: (conf.titlevisible?conf.name:""), + lineWidth: 1 + }); + + //toto nejak vymyslet konfiguracne, additional threshold lines + if (key == "slopeMA") { + //natvrdo nakreslime lajnu pro min angle + //TODO predelat na configuracne + const minSlopeLineOptopns = { + price: data.statinds.angle.minimum_slope, + color: '#b67de8', + lineWidth: 1, + lineStyle: 2, // LineStyle.Dotted + axisLabelVisible: true, + title: "max:", + }; + + const minSlopeLine = obj.series.createPriceLine(minSlopeLineOptopns); + } + } + //INDICATOR on new pane + else { console.log("not implemented")} + + //add options + obj.series.applyOptions({ + lastValueVisible: false, + priceLineVisible: false, + }); + //add data + obj.series.setData(items) + + // add to indList array - pole zobrazovanych indikatoru + indList.push(obj); + + + } + + + + + + + + + + + + + + + + + // if (momentumIndicatorNames.includes(key)) { + // obj.series = chart.addLineSeries({ + // priceScaleId: 'left', + // color: colors.shift(), + // title: key, + // lineWidth: 1, + // lastValueVisible: false + // }); + + // if (key == "slopeMA") { + // const minSlopeLineOptopns = { + // //vzit odnekud jinud? + // price: data.statinds.angle.minimum_slope, + // color: colors.shift(), + // lineWidth: 1, + // lineStyle: 2, // LineStyle.Dotted + // axisLabelVisible: true, + // title: "max:", + // }; + + // const minSlopeLine = obj.series.createPriceLine(minSlopeLineOptopns); + // } + // } + // //ostatni + // else { + // obj.series = chart.addLineSeries({ + // title: key, + // color: colors.shift(), + // lineWidth: 1, + // lastValueVisible: false + // }); + // } + + // obj.series.applyOptions({ + // lastValueVisible: false, + // priceLineVisible: false, + // }); + + // try { + // obj.series.setData(items) + // } + // catch (error) { + // console.log("obj.series.setData(items)", items) + // console.error(error) + // } + + // //add to indList array - pole zobrazovanych indikatoru + // indList.push(obj); + + } + } + } + } + + //display vwap and volume + initialize_vwap() + vwapSeries.setData(transformed_data["vwap"]) + + initialize_volume() + volumeSeries.setData(transformed_data["volume"]) + console.log("volume") + } + + function remove_indicators() { + //reset COLORS + colors = reset_colors + + //remove CUSTOMS indicators if exists + indList.forEach((element, index, array) => { + chart.removeSeries(element.series); + } + ); + indList = []; + //remove BASIC indicators + if (vwapSeries !== null) { + chart.removeSeries(vwapSeries) + } + if (volumeSeries !== null) { + chart.removeSeries(volumeSeries) + } + } + + //gets indicators from archived data and displays them on the chart + + console.log("avgp_buy_line",transformed_data["avgp_buy_line"]) console.log("avgp_markers",transformed_data["avgp_markers"]) if (transformed_data["avgp_buy_line"].length > 0) { - var avgBuyLine = chart.addLineSeries({ + avgBuyLine = chart.addLineSeries({ // title: "avgpbuyline", color: '#e8c76d', // color: 'transparent', lineWidth: 1, lastValueVisible: false }); + + avgBuyLine.applyOptions({ + lastValueVisible: false, + priceLineVisible: false, + }); + + + try { avgBuyLine.setData(transformed_data["avgp_buy_line"]); + } + catch (error) { + console.log("avgbuyline") + } + avgBuyLine.setMarkers(transformed_data["avgp_markers"]) } - var markersLine = chart.addLineSeries({ + markersLine = chart.addLineSeries({ // title: "avgpbuyline", // color: '#d6d1c3', color: 'transparent', lineWidth: 1, lastValueVisible: false }); - markersLine.setData(transformed_data["markers_line"]); + + + + try { + markersLine.setData(transformed_data["markers_line"]); + } + catch (error) { + console.log("markersLine") + } + + markersLine.setMarkers(transformed_data["markers"]) - + //TBD dynamicky //pokud je nazev atributu X_candles vytvorit candles @@ -333,6 +515,11 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { //chart.subscribeCrosshairMove(param => { chart.subscribeCrosshairMove(param => { + //LEGEND SECTIOIN + firstRow.style.color = 'white'; + update_chart_legend(param); + + //TOOLTIP SECTION //$('#trade-timestamp').val(param.time) if ( param.point === undefined || @@ -390,15 +577,11 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { } }); - - - - - - - chart.subscribeClick(param => { $('#trade-timestamp').val(param.time) + if (archRecord.ilog_save == "true") { + fetch_log_data(param.time, archRecord.id); + } if ( param.point === undefined || !param.time || @@ -458,9 +641,90 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { $("#statusName").text(archRecord.name) $("#statusMode").text(archRecord.mode) $("#statusAccount").text(archRecord.account) + $("#statusIlog").text("Logged:" + archRecord.ilog_save) $("#statusStratvars").text(JSON.stringify(archRecord.stratvars,null,2)) - + $("#statusSettings").text(JSON.stringify(archRecord.settings,null,2)) //TBD other dynamically created indicators +} + + +function fetch_log_data(timestamp, runner_id) { + timestamp_from = timestamp - 20 + timestamp_to = timestamp + 20 + req = {} + req["runner_id"] = runner_id; + req["timestamp_from"] = timestamp_from; + req["timestamp_to"] = timestamp_to; + $.ajax({ + url:"/archived_runners_log/"+runner_id, + beforeSend: function (xhr) { + xhr.setRequestHeader('X-API-Key', + API_KEY); }, + method:"GET", + contentType: "application/json", + data: req, + success:function(data){ + console.log("archived logs", JSON.stringify(data)) + display_log(data, timestamp) + }, + error: function(xhr, status, error) { + var err = eval("(" + xhr.responseText + ")"); + window.alert(JSON.stringify(xhr)); + console.log(JSON.stringify(xhr)); + } + }) +} + +function display_log(iterLogList, timestamp) { + //console.log("Incoming logline object") + + var lines = document.getElementById('lines') + var line = document.createElement('div') + line.classList.add("line") + const newLine = document.createTextNode("---------------") + line.appendChild(newLine) + lines.appendChild(line) + + iterLogList.forEach((logLine) => { + //console.log("logline item") + //console.log(JSON.stringify(logLine,null,2)) + + //
+ //
12233 Event
+ //
+ // Detaila mozna structured + // Lorem ipsum dolor sit amet, consectetur adipisicing elit, + // sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + // quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + //
+ //
+ + highlighted = (parseInt(logLine.time) == parseInt(timestamp)) ? "highlighted" : "" + logcnt++; + row = '
'+logLine.time + " " + logLine.event + ' - '+ (logLine.message == undefined ? "" : logLine.message) +'
' + str_row = JSON.stringify(logLine.details, null, 2) + //row_detail = '
' + str_row + '
' + + row_detail = '
' + str_row + '
' + + var lines = document.getElementById('lines') + var line = document.createElement('div') + line.classList.add("line") + line.dataset.timestamp = logLine.time + + line.insertAdjacentHTML( 'beforeend', row ); + line.insertAdjacentHTML( 'beforeend', row_detail ); + //line.appendChild(newLine) + //var pre = document.createElement("span") + //pre.classList.add("pidi") + //const stLine = document.createTextNode(str_row) + //pre.appendChild(stLine) + //line.appendChild(pre) + lines.appendChild(line) + }); + $('#messages').animate({ + scrollTop: $('#lines')[0].scrollHeight}, 2000); + } \ No newline at end of file diff --git a/v2realbot/static/js/archivetables.js b/v2realbot/static/js/archivetables.js index 308d8f5..5b70b9e 100644 --- a/v2realbot/static/js/archivetables.js +++ b/v2realbot/static/js/archivetables.js @@ -7,7 +7,6 @@ $(document).ready(function () { $('#button_delete_arch').attr('disabled','disabled'); $('#button_edit_arch').attr('disabled','disabled'); - //selectable rows in archive table $('#archiveTable tbody').on('click', 'tr', function () { if ($(this).hasClass('selected')) { @@ -36,6 +35,7 @@ $(document).ready(function () { row = archiveRecords.row('.selected').data(); window.$('#editModalArchive').modal('show'); $('#editidarchive').val(row.id); + $('#editnote').val(row.note); $('#editstratvars').val(JSON.stringify(row.stratvars,null,2)); }); @@ -54,7 +54,7 @@ $(document).ready(function () { success:function(data){ $('#button_show_arch').attr('disabled',false); $('#chartContainerInner').addClass("show"); - $("#lines").html("
"+JSON.stringify(row.stratvars,null,2)+"
") + //$("#lines").html("
"+JSON.stringify(row.stratvars,null,2)+"
") //$('#chartArchive').append(JSON.stringify(data,null,2)); console.log(JSON.stringify(data,null,2)); @@ -134,6 +134,7 @@ $("#delModalArchive").on('submit','#delFormArchive', function(event){ window.alert(JSON.stringify(xhr)); console.log(JSON.stringify(xhr)); $('#deletearchive').attr('disabled', false); + archiveRecords.ajax.reload(); } }) }); @@ -154,6 +155,7 @@ var archiveRecords = } }, columns: [{ data: 'id' }, + {data: 'strat_id'}, {data: 'name'}, {data: 'symbol'}, {data: 'note'}, @@ -163,6 +165,7 @@ var archiveRecords = {data: 'account', visible: true}, {data: 'bt_from', visible: true}, {data: 'bt_to', visible: true}, + {data: 'ilog_save', visible: true}, {data: 'stratvars', visible: true}, {data: 'profit'}, {data: 'trade_count', visible: true}, @@ -176,7 +179,7 @@ var archiveRecords = // return format_date(data) // }, // }], - order: [[5, 'desc']], + order: [[6, 'desc']], paging: true, lengthChange: false, // createdRow: function( row, data, dataIndex){ diff --git a/v2realbot/static/js/livewebsocket.js b/v2realbot/static/js/livewebsocket.js index d51f793..76505a6 100644 --- a/v2realbot/static/js/livewebsocket.js +++ b/v2realbot/static/js/livewebsocket.js @@ -1,14 +1,13 @@ const momentumIndicatorNames = ["roc", "slope", "slopeMA"] -var indList = [] var pbiList = [] var ws = null; var logcnt = 0 var positionsPriceLine = null var limitkaPriceLine = null var angleSeries = 1 -var candlestickSeries -var volumeSeries -var vwapSeries +var candlestickSeries = null +var volumeSeries = null +var vwapSeries = null //get details of runner to populate chart status //fetch necessary - it could be initiated by manually inserting runnerId @@ -92,12 +91,12 @@ function connect(event) { iterLogList = parsed_data.iter_log //console.log("Incoming logline object") - var lines = document.getElementById('lines') - var line = document.createElement('div') - line.classList.add("line") - const newLine = document.createTextNode("---------------") - line.appendChild(newLine) - lines.appendChild(line) + // var lines = document.getElementById('lines') + // var line = document.createElement('div') + // line.classList.add("line") + // const newLine = document.createTextNode("---------------") + // line.appendChild(newLine) + // lines.appendChild(line) iterLogList.forEach((logLine) => { //console.log("logline item") @@ -194,7 +193,7 @@ function connect(event) { positions = parsed_data.positions const posLine = { price: positions.avgp, - color: 'black', + color: '#918686', lineWidth: 1, lineStyle: 1, // LineStyle.Dotted axisLabelVisible: true, @@ -221,7 +220,7 @@ function connect(event) { if (klic === "angle") { //nejsou vsechny hodnoty - if (Object.keys(hodnota).length > 0) { + if (Object.keys(hodnota).length > 1) { // console.log("angle nalezen"); // console.log(JSON.stringify(hodnota)); if (angleSeries !== 1) { @@ -271,49 +270,52 @@ function connect(event) { //console.log("object new - init and add") var obj = {name: key, series: null} - //predelat configuracne - //inicializace indicatoru - //momentum - if (momentumIndicatorNames.includes(key)) { - - - obj.series = chart.addLineSeries({ - priceScaleId: 'left', - title: key, - lineWidth: 1 - }); - - //natvrdo nakreslime lajnu pro min angle - //TODO predelat na configuracne - const minSlopeLineOptopns = { - price: parsed_data.statinds.angle.minimum_slope, - color: '#b67de8', - lineWidth: 2, - lineStyle: 2, // LineStyle.Dotted - axisLabelVisible: true, - title: "max:", - }; - - const minSlopeLine = obj.series.createPriceLine(minSlopeLineOptopns); + //get configuation of indicator to display + conf = get_ind_config(key) - + //INIT INDICATOR BASED on CONFIGURATION + + //MOVE TO UTILS ro reuse?? + if (conf && conf.display) { + if (conf.embed) { + obj.series = chart.addLineSeries({ + color: colors.shift(), + priceScaleId: conf.priceScaleId, + lastValueVisible: conf.lastValueVisible, + title: (conf.titlevisible?conf.name:""), + lineWidth: 1 + }); + + //tady add data + obj.series.update({ + time: indicators.time, + value: value}); + indList.push(obj); + + //toto nejak vymyslet konfiguracne, additional threshold lines + if (key == "slopeMA") { + //natvrdo nakreslime lajnu pro min angle + //TODO predelat na configuracne + const minSlopeLineOptopns = { + price: parsed_data.statinds.angle.minimum_slope, + color: '#b67de8', + lineWidth: 1, + lineStyle: 2, // LineStyle.Dotted + axisLabelVisible: true, + title: "max:", + }; + + const minSlopeLine = obj.series.createPriceLine(minSlopeLineOptopns); + } + } + //INDICATOR on new pane + else { console.log("not implemented")} } - //ostatni - else { - obj.series = chart.addLineSeries({ - //title: key, - lineWidth: 1, - lastValueVisible: false - }); - } - obj.series.update({ - time: indicators.time, - value: value}); - indList.push(obj); } //indicator exists in an array, lets update it else { //console.log("object found - update") + //tady add data searchObject.series.update({ time: indicators.time, value: value diff --git a/v2realbot/static/js/mytables.js b/v2realbot/static/js/mytables.js index 3bd903a..ba27867 100644 --- a/v2realbot/static/js/mytables.js +++ b/v2realbot/static/js/mytables.js @@ -10,7 +10,7 @@ function get_status(id) { runnerRecords.rows().iterator('row', function ( context, index ) { var data = this.row(index).data(); //window.alert(JSON.stringify(data)) - if (data.id == id) { + if (data.strat_id == id) { //window.alert("found"); if ((data.run_mode) == "backtest") { status_detail = data.run_mode} else { status_detail = data.run_mode + " | " + data.run_account} @@ -25,12 +25,12 @@ function get_status(id) { return status } -function is_running(id) { +function is_stratin_running(id) { var running = false runnerRecords.rows().iterator('row', function ( context, index ) { var data = this.row(index).data(); //window.alert(JSON.stringify(data)) - if (data.id == id) { + if (data.strat_id == id) { running = true } //window.alert("found") } @@ -181,7 +181,7 @@ $(document).ready(function () { //button refresh - $('#button_refresh').click(function () { + $('.refresh').click(function () { runnerRecords.ajax.reload(); stratinRecords.ajax.reload(); archiveRecords.ajax.reload(); @@ -255,7 +255,7 @@ $(document).ready(function () { event.preventDefault(); $('#button_pause').attr('disabled','disabled'); $.ajax({ - url:"/stratins/"+row.id+"/pause", + url:"/runners/"+row.id+"/pause", beforeSend: function (xhr) { xhr.setRequestHeader('X-API-Key', API_KEY); }, @@ -282,7 +282,7 @@ $(document).ready(function () { event.preventDefault(); $('#button_stop').attr('disabled','disabled'); $.ajax({ - url:"/stratins/"+row.id+"/stop", + url:"/runners/"+row.id+"/stop", beforeSend: function (xhr) { xhr.setRequestHeader('X-API-Key', API_KEY); }, @@ -310,7 +310,7 @@ $(document).ready(function () { event.preventDefault(); $('#buttonall_stop').attr('disabled','disabled'); $.ajax({ - url:"/stratins/stop", + url:"/runners/stop", beforeSend: function (xhr) { xhr.setRequestHeader('X-API-Key', API_KEY); }, @@ -343,6 +343,7 @@ $(document).ready(function () { $('#mode').val(localStorage.getItem("mode")); $('#account').val(localStorage.getItem("account")); $('#debug').val(localStorage.getItem("debug")); + $('#ilog_save').val(localStorage.getItem("ilog_save")); $('#runid').val(row.id); }); @@ -465,10 +466,12 @@ var runnerRecords = // }, }, columns: [{ data: 'id' }, + {data: 'strat_id'}, {data: 'run_started'}, {data: 'run_mode'}, {data: 'run_symbol'}, {data: 'run_account'}, + {data: 'run_ilog_save'}, {data: 'run_paused'}, {data: 'run_profit'}, {data: 'run_trade_count'}, @@ -489,6 +492,7 @@ $("#runModal").on('submit','#runForm', function(event){ localStorage.setItem("mode", $('#mode').val()); localStorage.setItem("account", $('#account').val()); localStorage.setItem("debug", $('#debug').val()); + localStorage.setItem("ilog_save", $('#ilog_save').val()); event.preventDefault(); $('#run').attr('disabled','disabled'); @@ -496,9 +500,19 @@ $("#runModal").on('submit','#runForm', function(event){ //rename runid to id Object.defineProperty(formData, "id", Object.getOwnPropertyDescriptor(formData, "runid")); delete formData["runid"]; + //console.log(formData) + if ($('#ilog_save').prop('checked')) { + formData.ilog_save = true; + } + else + { + formData.ilog_save = false; + } + // $('#subscribe').prop('checked') if (formData.bt_from == "") {delete formData["bt_from"];} if (formData.bt_to == "") {delete formData["bt_to"];} jsonString = JSON.stringify(formData); + //console.log(jsonString) //window.alert(jsonString); $.ajax({ url:"/stratins/"+formData.id+"/run", @@ -512,7 +526,10 @@ $("#runModal").on('submit','#runForm', function(event){ //pokud mame subscribnuto na RT if ($('#subscribe').prop('checked')) { //subscribe input value gets id of current runner - $('#runnerId').val($('#runid').val()); + //$('#runid').val() + //data obsuje ID runneru - na ten se subscribneme + console.log("vysledek z run:", data) + $('#runnerId').val(data); $( "#bt-conn" ).trigger( "click" ); } $('#runForm')[0].reset(); diff --git a/v2realbot/static/js/realtimechart.js b/v2realbot/static/js/realtimechart.js index 1b342e5..85b006d 100644 --- a/v2realbot/static/js/realtimechart.js +++ b/v2realbot/static/js/realtimechart.js @@ -1,76 +1,14 @@ - function populate_real_time_chart() { if (chart !== null) { chart.remove(); clear_status_header(); } - $('#chartContainerInner').addClass("show"); - //const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } }; - var chartOptions = { width: 1045, height: 600, leftPriceScale: {visible: true}} - // var chartOptions = { width: 1045, - // height: 600, - // leftPriceScale: {visible: true}, - // layout: { - // background: { - // type: 'solid', - // color: '#000000', - // }, - // textColor: '#d1d4dc', - // }, - // grid: { - // vertLines: { - // visible: false, - // }, - // horzLines: { - // color: 'rgba(42, 46, 57, 0.5)', - // }, - // }, - // } - - - chart = LightweightCharts.createChart(document.getElementById('chart'), chartOptions); - chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: { - mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true - }}) - candlestickSeries = chart.addCandlestickSeries({ lastValueVisible: true, priceLineWidth:2, priceLineColor: "red", priceFormat: { type: 'price', precision: 2, minMove: 0.01 }}); - candlestickSeries.priceScale().applyOptions({ - scaleMargins: { - top: 0.1, // highest point of the series will be 10% away from the top - bottom: 0.4, // lowest point will be 40% away from the bottom - }, - }); - - - volumeSeries = chart.addHistogramSeries({title: "Volume", color: '#26a69a', priceFormat: {type: 'volume'}, priceScaleId: ''}); - volumeSeries.priceScale().applyOptions({ - // set the positioning of the volume series - scaleMargins: { - top: 0.7, // highest point of the series will be 70% away from the top - bottom: 0, - }, - }); - - vwapSeries = chart.addLineSeries({ - // title: "vwap", - color: '#2962FF', - lineWidth: 1, - lastValueVisible: false - }) - - //chart.timeScale().fitContent(); - - - //TBD unifikovat legendu - //TBD dynamicky zobrazovat vsechny indikatory - //document.getElementById('chart').style.display = 'inline-block'; - var legendlist = document.getElementById('legend'); - var firstRow = document.createElement('div'); - firstRow.innerText = '-'; - // firstRow.style.color = 'white'; - legendlist.appendChild(firstRow); - + initialize_chart() + intitialize_candles() + initialize_vwap() + initialize_volume() chart.subscribeClick(param => { //display timestamp in trade-timestamp input field @@ -78,24 +16,8 @@ function populate_real_time_chart() { }); chart.subscribeCrosshairMove((param) => { - if (param.time) { - const data = param.seriesData.get(vwapSeries); - const vwap = data.value !== undefined ? data.value : data.close; - const bars = param.seriesData.get(candlestickSeries); - const volumes = param.seriesData.get(volumeSeries); - firstRow.innerText = ""; - //iterate of custom indicators dictionary to get values of custom lines - // var customIndicator = {name: key, series: null} - indList.forEach(function (item) { - const ind = param.seriesData.get(item.series) - firstRow.innerText += item.name + " " + ind.value + " "; - }); - - firstRow.innerText += ' vwap' + ' ' + vwap.toFixed(2) + " O" + bars.open + " H" + bars.high + " L" + bars.low + " C" + bars.close + " V" + volumes.value + ""; - } - else { - firstRow.innerText = '-'; - } + firstRow.style.color = 'white'; + update_chart_legend(param); }); } diff --git a/v2realbot/static/js/utils.js b/v2realbot/static/js/utils.js index 193bcdb..00ae9a2 100644 --- a/v2realbot/static/js/utils.js +++ b/v2realbot/static/js/utils.js @@ -1,6 +1,144 @@ API_KEY = localStorage.getItem("api-key") var chart = null +var colors = ["#8B1874","#B71375","#B46060","#61c740","#BE6DB7","#898121","#4389d9","#00425A","#B5D5C5","#e61957"] +var reset_colors = colors +var indList = [] + +indConfig = {} +settings = {} +settings +//ostatni indicatory nez vwap, volume a bary +indConfig = [ {name: "ema", titlevisible: false, embed: true, display: true, priceScaleId: "right", lastValueVisible: false}, + {name: "slope", titlevisible: true, embed: true, display: false, priceScaleId: "left", lastValueVisible: false}, + {name: "slopeMA", titlevisible: true, embed: true, display: true, priceScaleId: "left", lastValueVisible: false},] + +function get_ind_config(indName) { + const i = indConfig.findIndex(e => e.name === indName); + if (i>-1) + { + return indConfig[i] + } + return null +} + + +//LEGEND INIT +var legendlist = document.getElementById('legend'); +var firstRow = document.createElement('div'); +firstRow.innerHTML = '-'; +// firstRow.style.color = 'white'; +legendlist.appendChild(firstRow); + +function update_chart_legend(param) { + + function name(val) { + return '
' + val + '' + } + function val(val) { + return '
' + val + '' + } + + if (param.time) { + firstRow.innerHTML = ""; + //BASIC INDICATORS + const bars = param.seriesData.get(candlestickSeries); + if (bars !== undefined) { + firstRow.innerHTML += name("O") + val(bars.open) + name("H") + val(bars.high) + name("L") + val(bars.low) + name("C") + val(bars.close) + } + + const volumes = param.seriesData.get(volumeSeries); + if (volumes !== undefined) { + firstRow.innerHTML += name("Vol") +val(volumes.value) + } + const data = param.seriesData.get(vwapSeries); + if (data !== undefined) { + const vwap = data.value !== undefined ? data.value : data.close; + firstRow.innerHTML += name('vwap') + val(vwap.toFixed(2)) + } + //ADDITIONAL CUSTOM INDICATORS + //iterate of custom indicators dictionary to get values of custom lines + // var customIndicator = {name: key, series: null} + indList.forEach(function (item) { + var ind = param.seriesData.get(item.series) + if (ind !== undefined) { firstRow.innerHTML += name(item.name) + val(ind.value.toFixed(3))} + }); + } + else { + firstRow.innerHTML = ''; + } +} + +function initialize_chart() { + $('#chartContainerInner').addClass("show"); + //PUVODNI BILY MOD + //var chartOptions = { width: 1045, height: 600, leftPriceScale: {visible: true}} + + //TMAVY MOD + var chartOptions = { width: 1080, + height: 600, + leftPriceScale: {visible: true}, + layout: { + background: { + type: 'solid', + color: '#000000', + }, + textColor: '#d1d4dc', + }, + grid: { + vertLines: { + visible: true, + color: "#434d46" + }, + horzLines: { + color: "#667069", + visible:true + }, + }, + } + + chart = LightweightCharts.createChart(document.getElementById('chart'), chartOptions); + chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: { + mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true + }}) +} + +//mozna atributy last value visible +function intitialize_candles() { + candlestickSeries = chart.addCandlestickSeries({ lastValueVisible: false, priceLineWidth:1, priceLineColor: "red", priceFormat: { type: 'price', precision: 2, minMove: 0.01 }}); + candlestickSeries.priceScale().applyOptions({ + scaleMargins: { + top: 0.1, // highest point of the series will be 10% away from the top + bottom: 0.4, // lowest point will be 40% away from the bottom + }, + }); + candlestickSeries.applyOptions({ + lastValueVisible: true, + priceLineVisible: true, + }); + +} + +function initialize_volume() { + volumeSeries = chart.addHistogramSeries({title: "Volume", color: '#26a69a', priceFormat: {type: 'volume'}, priceScaleId: ''}); + volumeSeries.priceScale().applyOptions({ + // set the positioning of the volume series + scaleMargins: { + top: 0.7, // highest point of the series will be 70% away from the top + bottom: 0, + }, + }); +} + +function initialize_vwap() { + vwapSeries = chart.addLineSeries({ + // title: "vwap", + color: '#2962FF', + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false + }) +} //range switch pro chart https://jsfiddle.net/TradingView/qrb9a850/ function createSimpleSwitcher(items, activeItem, activeItemChangedCallback) { @@ -67,6 +205,7 @@ function clear_status_header() { $("#statusName").text("") $("#statusMode").text("") $("#statusAccount").text("") + $("#statusIlog").text("") $("#statusStratvars").text("") //clear previous logs from rt $("#lines").empty() diff --git a/v2realbot/static/main.css b/v2realbot/static/main.css index a0f7644..29ce6a8 100644 --- a/v2realbot/static/main.css +++ b/v2realbot/static/main.css @@ -1,9 +1,12 @@ -:root { +/* :root { --dt-row-selected: 18, 143, 175; +} */ +:root { + --dt-row-selected: 37, 120, 114; } tbody, td, tfoot, th, thead, tr { - border-color: inherit; + border-color: #7d7d8a; border-style: solid; border-width: 0; padding: 4px; @@ -11,10 +14,29 @@ tbody, td, tfoot, th, thead, tr { .secondary-bg { --bs-bg-opacity: 1; - background-color: #128faf; + background-color: #414749; } +/* #128faf; */ + .btn-outline-success { + --bs-btn-color: #75a9ac; + --bs-btn-border-color: #8b8b8b; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #3996a4; + --bs-btn-hover-border-color: #198754; + --bs-btn-focus-shadow-rgb: 25,135,84; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #40929d; + --bs-btn-active-border-color: #446379; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #4e7c85; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #839498; + --bs-gradient: none; +} + +/* .btn-outline-success { --bs-btn-color: #316164; --bs-btn-border-color: #247e85; --bs-btn-hover-color: #fff; @@ -29,6 +51,25 @@ tbody, td, tfoot, th, thead, tr { --bs-btn-disabled-bg: transparent; --bs-btn-disabled-border-color: #839498; --bs-gradient: none; +} */ + +.badge { + --bs-badge-padding-x: 0.65em; + --bs-badge-padding-y: 0.35em; + --bs-badge-font-size: 0.75em; + --bs-badge-font-weight: 700; + --bs-badge-color: #fff; + --bs-badge-border-radius: var(--bs-border-radius); + display: inline-block; + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-body-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: var(--bs-badge-border-radius); } html { @@ -63,13 +104,23 @@ html { position: absolute; color: #050505; left: 115px; - top: 99px; + top: 115px; z-index: 1; - font-size: 16px; + font-size: 12px; line-height: 18px; font-weight: 300; } +.legendItemName { + margin-left: 5px; + display: flex; +} + +.legendItemValue { + margin-left: 5px; + display: flex; +} + #msgContainer { /* display: inline-block; */ overflow: auto; @@ -83,16 +134,17 @@ html { overflow: auto; height: 568px; flex-direction: column-reverse; + margin-left: 28px; } } -@media (min-width : 1681px) { - .msgContainerInner { +@media (min-width: 1681px) {} + .msgContainerInner { display: flex; overflow: auto; height: 568px; flex-direction: column-reverse; - } + margin-left: 28px; } @@ -104,7 +156,7 @@ pre { /* margin: 0 0 10px; */ font-size: 10px; /* line-height: 2.428571; */ - color: #333; + /* color: #333; */ word-break: break-all; word-wrap: break-word; /* background-color: #f5f5f5; */ @@ -126,11 +178,12 @@ pre { margin-left: 0px; font-size: normal; /* font-weight: bold; */ - display: -webkit-inline-box; + display: flex; background-color: #dfe4e6; /* color: white; */ padding: 1px; padding-left: 5px; + width: max-content; } .headerItem { diff --git a/v2realbot/strategy/StrategyOrderLimitVykladaci.py b/v2realbot/strategy/StrategyOrderLimitVykladaci.py index add4877..6c9a6c9 100644 --- a/v2realbot/strategy/StrategyOrderLimitVykladaci.py +++ b/v2realbot/strategy/StrategyOrderLimitVykladaci.py @@ -11,12 +11,12 @@ from random import randrange from alpaca.common.exceptions import APIError import copy from threading import Event - +from uuid import UUID class StrategyOrderLimitVykladaci(Strategy): - def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: Mode = Mode.PAPER, stratvars: AttributeDict = None, open_rush: int = 30, close_rush: int = 30, pe: Event = None, se: Event = None) -> None: - super().__init__(name, symbol, next, init, account, mode, stratvars, open_rush, close_rush, pe, se) + def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: Mode = Mode.PAPER, stratvars: AttributeDict = None, open_rush: int = 30, close_rush: int = 30, pe: Event = None, se: Event = None, runner_id: UUID = None, ilog_save: bool = False) -> None: + super().__init__(name, symbol, next, init, account, mode, stratvars, open_rush, close_rush, pe, se, runner_id, ilog_save) async def orderUpdateBuy(self, data: TradeUpdate): o: Order = data.order diff --git a/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc b/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc index ad85ab9..6d1b12e 100644 Binary files a/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc and b/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc differ diff --git a/v2realbot/strategy/__pycache__/base.cpython-310.pyc b/v2realbot/strategy/__pycache__/base.cpython-310.pyc index 9dde1bb..afd922c 100644 Binary files a/v2realbot/strategy/__pycache__/base.cpython-310.pyc and b/v2realbot/strategy/__pycache__/base.cpython-310.pyc differ diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 04a400b..bbd7bc6 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -4,8 +4,9 @@ from datetime import datetime from v2realbot.utils.utils import AttributeDict, zoneNY, is_open_rush, is_close_rush, json_serial, print from v2realbot.utils.tlog import tlog +from v2realbot.utils.ilog import insert_log, insert_log_multiple from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Order, Account -from v2realbot.config import BT_DELAYS, get_key, HEARTBEAT_TIMEOUT, QUIET_MODE +from v2realbot.config import BT_DELAYS, get_key, HEARTBEAT_TIMEOUT, QUIET_MODE, LOG_RUNNER_EVENTS import queue #from rich import print from v2realbot.loader.aggregator import TradeAggregator2Queue, TradeAggregator2List, TradeAggregator @@ -22,10 +23,11 @@ from v2realbot.common.model import TradeUpdate from alpaca.trading.enums import TradeEvent, OrderStatus from threading import Event, current_thread import json +from uuid import UUID # obecna Parent strategie podporující queues class Strategy: - def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: str = Mode.PAPER, stratvars: AttributeDict = None, open_rush: int = 30, close_rush: int = 30, pe: Event = None, se: Event = None) -> None: + def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: str = Mode.PAPER, stratvars: AttributeDict = None, open_rush: int = 30, close_rush: int = 30, pe: Event = None, se: Event = None, runner_id: UUID = None, ilog_save: bool = False) -> None: #variable to store methods overriden by strategytypes (ie pre plugins) self.overrides = None self.symbol = symbol @@ -51,6 +53,8 @@ class Strategy: self.account = account self.key = get_key(mode=self.mode, account=self.account) self.rtqueue = None + self.runner_id = runner_id + self.ilog_save = ilog_save #TODO predelat na dynamické queues @@ -106,13 +110,13 @@ class Strategy: self.order_notifs = LiveOrderUpdatesStreamer(key=self.key, name="WS-STRMR-" + self.name) #propojujeme notifice s interfacem (pro callback) self.order_notifs.connect_callback(self) - self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype) + self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, ilog_save=self.ilog_save) elif mode == Mode.BT: self.dataloader = Trade_Offline_Streamer(start, end, btdata=self.btdata) self.bt = Backtester(symbol = self.symbol, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end) self.interface = BacktestInterface(symbol=self.symbol, bt=self.bt) - self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype) + self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save) self.order_notifs = None ##streamer bude plnit trady do listu trades - nad kterym bude pracovat paper trade #zatim takto - pak pripadne do fajlu nebo jinak OPTIMALIZOVAT @@ -195,7 +199,7 @@ class Strategy: #ic(self.state.time) if self.mode == Mode.BT: - self.state.ilog(e="----- BT exec START", msg=f"{self.bt.time=}") + #self.state.ilog(e="----- BT exec START", msg=f"{self.bt.time=}") #pozor backtester muze volat order_updates na minuly cas - nastavi si bt.time self.bt.execute_orders_and_callbacks(self.state.time) #ic(self.bt.time) @@ -207,7 +211,8 @@ class Strategy: #ic(self.state.time) if self.mode == Mode.BT: - self.state.ilog(e="----- BT exec FINISH", msg=f"{self.bt.time=}") + pass + #self.state.ilog(e="----- BT exec FINISH", msg=f"{self.bt.time=}") #ic(self.bt.time) #ic(len(self.btdata)) #ic(self.bt.cash) @@ -282,9 +287,9 @@ class Strategy: print("REQUEST COUNT:", self.interface.mincnt) self.bt.backtest_end = datetime.now() - print(40*"*",self.mode, "BACKTEST RESULTS",40*"*") + #print(40*"*",self.mode, "BACKTEST RESULTS",40*"*") #-> account, cash,trades,open_orders - self.bt.display_backtest_result(self.state) + #self.bt.display_backtest_result(self.state) #this is(WILL BE) called when strategy is stopped # LIVE - pause or stop signal received @@ -301,6 +306,10 @@ class Strategy: self.dataloader.remove_stream(i) #pamatujeme si streamy, ktere ma strategie a tady je removneme + #posilame break na RT queue na frontend + if self.rtqueue is not None: + self.rtqueue.put("break") + #zavolame na loaderu remove streamer - mohou byt dalsi bezici strategie, ktery loader vyuzivaji #pripadne udelat shared loader a nebo dedicated loader #pokud je shared tak volat remove @@ -319,6 +328,7 @@ class Strategy: else: now = self.bt.time + self.state.ilog(e="NOTIF ARRIVED AT"+str(now)) print("NOTIFICATION ARRIVED AT:", now) self.update_live_timenow() @@ -413,10 +423,10 @@ class Strategy: #cleaning iterlog lsit #TODO pridat cistku i mimo RT blok - self.state.iter_log_list = [] - else: - #mazeme logy pokud neni na ws pozadovano - self.state.iter_log_list = [] + + if self.ilog_save: insert_log_multiple(self.state.runner_id, self.state.iter_log_list) + #smazeme logy + self.state.iter_log_list = [] @staticmethod def append_bar(history_reference, new_bar: dict): @@ -468,7 +478,7 @@ class StrategyState: triggerují callback, který následně vyvolá např. buy (ten se musí ale udít v čase fillu, tzn. callback si nastaví čas interfacu na filltime) po dokončení bt kroků před zahájením iterace "NEXT" se časy znovu updatnout na původni state.time """ - def __init__(self, name: str, symbol: str, stratvars: AttributeDict, bars: AttributeDict = {}, trades: AttributeDict = {}, interface: GeneralInterface = None, rectype: RecordType = RecordType.BAR): + def __init__(self, name: str, symbol: str, stratvars: AttributeDict, bars: AttributeDict = {}, trades: AttributeDict = {}, interface: GeneralInterface = None, rectype: RecordType = RecordType.BAR, runner_id: UUID = None, bt: Backtester = None, ilog_save: bool = False): self.vars = stratvars self.interface = interface self.positions = 0 @@ -483,6 +493,9 @@ class StrategyState: #time of last trade processed self.last_trade_time = 0 self.timeframe = None + self.runner_id = runner_id + self.bt = bt + self.ilog_save = ilog_save bars = {'high': [], 'low': [], @@ -525,17 +538,24 @@ class StrategyState: if self.mode == Mode.LIVE or self.mode == Mode.PAPER: self.time = datetime.now().timestamp() + #pri backtestingu logujeme BT casem (muze byt jiny nez self.time - napr. pri notifikacich a naslednych akcích) + if self.mode == Mode.BT: + time = self.bt.time + else: + time = self.time + if e is None: if msg is None: - row = dict(time=self.time, details=kwargs) + row = dict(time=time, details=kwargs) else: - row = dict(time=self.time, message=msg, details=kwargs) + row = dict(time=time, message=msg, details=kwargs) else: if msg is None: - row = dict(time=self.time, event=e, details=kwargs) + row = dict(time=time, event=e, details=kwargs) else: - row = dict(time=self.time, event=e, message=msg, details=kwargs) + row = dict(time=time, event=e, message=msg, details=kwargs) self.iter_log_list.append(row) row["name"] = self.name print(row) - #TBD mozna odsud to posilat do nejakeho struct logger jako napr. structlog nebo loguru, separatni file podle name + #zatim obecny parametr -predelat per RUN? + #if LOG_RUNNER_EVENTS: insert_log(self.runner_id, time=self.time, logdict=row) \ No newline at end of file diff --git a/v2realbot/utils/__pycache__/utils.cpython-310.pyc b/v2realbot/utils/__pycache__/utils.cpython-310.pyc index ceec23a..46a00c7 100644 Binary files a/v2realbot/utils/__pycache__/utils.cpython-310.pyc and b/v2realbot/utils/__pycache__/utils.cpython-310.pyc differ diff --git a/v2realbot/utils/ilog.py b/v2realbot/utils/ilog.py new file mode 100644 index 0000000..d3a7b54 --- /dev/null +++ b/v2realbot/utils/ilog.py @@ -0,0 +1,92 @@ +import sqlite3 +from v2realbot.config import DATA_DIR +from v2realbot.utils.utils import json_serial +from uuid import UUID, uuid4 +import json +from datetime import datetime +from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account + +sqlite_db_file = DATA_DIR + "/v2trading.db" +conn = sqlite3.connect(sqlite_db_file, check_same_thread=False) +#standardne vraci pole tuplů, kde clen tuplu jsou sloupce +#conn.row_factory = lambda c, r: json.loads(r[0]) +#conn.row_factory = lambda c, r: r[0] +#conn.row_factory = sqlite3.Row + +#CREATE TABLE +# c = conn.cursor() +# createTable= "CREATE TABLE runner_logs (runner_id varchar(32) NOT NULL, time real NOT NULL, data json NOT NULL);" +# print(c.execute(createTable)) +# sql = ("CREATE INDEX index_runner_logs ON runner_logs (runner_id, time);") +# print(c.execute(sql)) + +#testovaci objekty +#insert = dict(time=datetime.now(), side="ddd", rectype=RecordType.BAR, id=uuid4()) +#insert_list = [dict(time=datetime.now().timestamp(), side="ddd", rectype=RecordType.BAR, id=uuid4()),dict(time=datetime.now().timestamp(), side="ddd", rectype=RecordType.BAR, id=uuid4()),dict(time=datetime.now().timestamp(), side="ddd", rectype=RecordType.BAR, id=uuid4()),dict(time=datetime.now().timestamp(), side="ddd", rectype=RecordType.BAR, id=uuid4())] + +#returns rowcount of inserted rows +def insert_log(runner_id: UUID, time: float, logdict: dict): + c = conn.cursor() + json_string = json.dumps(logdict, default=json_serial) + res = c.execute("INSERT INTO runner_logs VALUES (?,?,?)",[str(runner_id), time, json_string]) + conn.commit() + return res.rowcount + +#returns rowcount of inserted rows +def insert_log_multiple(runner_id: UUID, loglist: list): + c = conn.cursor() + insert_data = [] + for i in loglist: + row = (str(runner_id), i["time"], json.dumps(i, default=json_serial)) + insert_data.append(row) + c.executemany("INSERT INTO runner_logs VALUES (?,?,?)", insert_data) + conn.commit() + return c.rowcount + +#returns list of ilog jsons +def get_log_window(runner_id: UUID, timestamp_from: float = 0, timestamp_to: float = 9682851459): + conn.row_factory = lambda c, r: json.loads(r[0]) + c = conn.cursor() + res = c.execute(f"SELECT data FROM runner_logs WHERE runner_id='{str(runner_id)}' AND time >={timestamp_from} AND time <={timestamp_to} ORDER BY time") + return res.fetchall() + +#returns number of deleted elements +def delete_logs(runner_id: UUID): + c = conn.cursor() + res = c.execute(f"DELETE from runner_logs WHERE runner_id='{str(runner_id)}';") + print(res.rowcount) + conn.commit() + return res.rowcount + +# print(insert_log(str(uuid4()), datetime.now().timestamp(), insert)) +# c = conn.cursor() +# ts_from = 1683108821.08872 +# ts_to = 1683108821.08874 +# res = c.execute(f"SELECT runner_id, time, data FROM runner_logs where time > {ts_from} and time <{ts_to}") +# result = res.fetchall() + +# res= delete_logs("7f9866ac-c742-47f4-a329-1d2b6721e781") +# print(res) + +# res = read_log_window(runner_id="33", timestamp_from=11 , timestamp_to=22) +# print(res) + +# res = insert_log_multiple(uuid4(), insert_list) +# print(res) + +# res = read_log_window("3340e257-d19a-4179-baf3-3b39190acde3", ts_from, ts_to) + +# print(res) + +# for r in res.fetchall(): +# print(dict(r)) + + +#print(res.description) +#print(result) + + + + + +