From 7a3567a8a6955baca5291e3b81308b89a4b1bd18 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Fri, 28 Apr 2023 21:13:05 +0200 Subject: [PATCH] gui refactor backtest chart --- db.json | 0 testy/tinydbTest.py | 152 ++++++ v2realbot/__pycache__/config.cpython-310.pyc | Bin 2779 -> 2779 bytes .../common/__pycache__/model.cpython-310.pyc | Bin 3518 -> 4583 bytes v2realbot/common/model.py | 41 +- v2realbot/controller/services.py | 98 +++- .../enums/__pycache__/enums.cpython-310.pyc | Bin 2289 -> 2304 bytes v2realbot/enums/enums.py | 10 +- v2realbot/main.py | 40 +- v2realbot/static/index.html | 177 ++++--- v2realbot/static/js/mychart.js | 130 ++--- v2realbot/static/js/mytables.js | 461 +++++++++++++++++- v2realbot/static/js/mywebsocket.js | 1 + .../strategy/__pycache__/base.cpython-310.pyc | Bin 13519 -> 13520 bytes v2realbot/strategy/base.py | 3 +- .../utils/__pycache__/utils.cpython-310.pyc | Bin 8049 -> 8151 bytes v2realbot/utils/utils.py | 7 +- 17 files changed, 991 insertions(+), 129 deletions(-) create mode 100644 db.json create mode 100644 testy/tinydbTest.py diff --git a/db.json b/db.json new file mode 100644 index 0000000..e69de29 diff --git a/testy/tinydbTest.py b/testy/tinydbTest.py new file mode 100644 index 0000000..62a62c4 --- /dev/null +++ b/testy/tinydbTest.py @@ -0,0 +1,152 @@ +from tinydb import TinyDB, Query, where +from tinydb.operations import set +from tinydb.storages import JSONStorage +from tinydb_serialization import SerializationMiddleware, Serializer +from tinydb_serialization.serializers import DateTimeSerializer +from v2realbot.common.model import Trade +from v2realbot.utils.utils import parse_toml_string, zoneNY, json_serial +from v2realbot.config import DATA_DIR +from datetime import datetime +from uuid import UUID, uuid4 +from enum import Enum +from v2realbot.common.model import Order, TradeUpdate as btTradeUpdate +from alpaca.trading.models import TradeUpdate +from alpaca.trading.enums import TradeEvent, OrderType, OrderSide, OrderType, OrderStatus +from rich import print +import json + +#storage_with_injected_serialization = JSONStorage() + +serialization = SerializationMiddleware(JSONStorage) + +#builtin DateTimeSerializer + +#customer DateTime2TimestampSerializer +class DateTime2TimestampSerializer(Serializer): + OBJ_CLASS = datetime # The class this serializer handles + + def encode(self, obj): + return str(obj.timestamp()) + + def decode(self, s): + return s + #return datetime.fromtimestamp(s) + +class EnumSerializer(Serializer): + OBJ_CLASS = Enum # The class this serializer handles + + def encode(self, obj): + return str(obj) + + def decode(self, s): + return s + +class UUIDSerializer(Serializer): + OBJ_CLASS = UUID # The class this serializer handles + + def encode(self, obj): + return str(obj) + + def decode(self, s): + return s + +class TradeUpdateSerializer(Serializer): + OBJ_CLASS = TradeUpdate # The class this serializer handles + + def encode(self, obj): + return obj.__dict__ + + def decode(self, s): + return str(s) + +class btTradeUpdateSerializer(Serializer): + OBJ_CLASS = btTradeUpdate # The class this serializer handles + + def encode(self, obj): + return obj.__dict__ + + def decode(self, s): + return str(s) + +class OrderSerializer(Serializer): + OBJ_CLASS = Order # The class this serializer handles + + def encode(self, obj): + return obj.__dict__ + + def decode(self, s): + return s + +orderList =[Order(id=uuid4(), + submitted_at = datetime(2023, 3, 17, 9, 30, 0, 0, tzinfo=zoneNY), + symbol = "BAC", + qty = 1, + status = OrderStatus.ACCEPTED, + order_type = OrderType.LIMIT, + side = OrderSide.BUY, + limit_price=22.4), + Order(id=uuid4(), + submitted_at = datetime(2023, 3, 17, 9, 30, 0, 0, tzinfo=zoneNY), + symbol = "BAC", + qty = 1, + status = OrderStatus.ACCEPTED, + order_type = OrderType.LIMIT, + side = OrderSide.BUY, + limit_price=22.4)] + +# serialization.register_serializer(DateTime2TimestampSerializer(), 'TinyDate') +# serialization.register_serializer(UUIDSerializer(), 'TinyUUID') +# serialization.register_serializer(TradeUpdateSerializer(), 'TinyTradeUpdate') +# serialization.register_serializer(btTradeUpdateSerializer(), 'TinybtTradeUpdate') +# serialization.register_serializer(OrderSerializer(), 'TinyOrder') + +a = Order(id=uuid4(), + submitted_at = datetime(2023, 3, 17, 9, 30, 0, 0, tzinfo=zoneNY), + symbol = "BAC", + qty = 1, + status = OrderStatus.ACCEPTED, + order_type = OrderType.LIMIT, + side = OrderSide.BUY, + limit_price=22.4) + +db_file = DATA_DIR + "/db.json" +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__) + +#get records by id +#res = db.search(where('id') == "0a9da064-708c-4645-8a07-e749d93a213d") +#or get one: +res = db.get(where('id') == "0a9da064-708c-4645-8a07-e749d93a213d") + + +#update one document +#db.update(set('name', "nove jmeno1"), where('id') == "0a9da064-708c-4645-8a07-e749d93a213d") + + +#get all documents +res = db.all() +print("vsechny zaznamy, res)", res) + +#fetch one docuemnt +# >>> db.get(User.name == 'John') +# {'name': 'John', 'age': 22} +# >>> db.get(User.name == 'Bobby') +# None + + +#delete record by id +#res = db.remove(where('id') == "0a9da064-708c-4645-8a07-e749d93a213d") +#print("removed", res) + +#res = db.search(qorder.order.id == "af447235-c01a-4c88-9f85-f3c267d2e2e1") +#res = db.search(qorder.orderList.side == "") +#print(res) + + +#print(db.all()) + + diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index 31817bd9ae204cc6d702ae6d4cdbf6a3eb35f4d6..586477cd1b85db6c949cafbfc718bcd8cc98d894 100644 GIT binary patch delta 183 zcmW-bJr==000wtiB9SE$Nr-Rt7Qk5yT z=@kkAJLu9!$N&+$h#8{C2>nzZ6ATz*$R0+fj;S#D*D3p$F~PiU@`6$M*)~`2iWz(V P_U=3Rs>$7bY2B({bpR{5 delta 182 zcmW-bI~Kt}07YkHh~LQ15dYsMtUy}lP`yI+!Z{jCP{;aX*1eZs^5 z$P^-m=rV#~7d;yE*+Wc)K`PHfjM&GRF($T7>0Y}T7~XOA`@`Nic9S?oYT{DW6r@z3qN30?=?4|6Qk5L4t|(^hnY0G#wPrUF z2c#f@kb0<4H75>;{)b8kao`8^$Z>9*xbg##;CaWjTe#G#dG>i`XUEU`G5g!vpWS+q zB%y`hdGR3+)5rDW?8M_U|D5{3;>&#HTZ^xV);9~;I`k^JP82ehMP>y%S#yRxM$3XUpPl7K#Ym1GlqSB;dZ`b;ASjFl&eu^AXKs%UC^gPMq15 zo3M0EJ6sG7?rCpO_4j4*Ksz8+F?#YHv$Q?T%lo}WmAWS*S}kLOb+zeUO`4#gFWOEB-5b!54g)#sXQ*I+eNw)aNUmj~ni zIDa7YlogeF(>u)?@YPX;*Xq1)Y!O@_SR%MYu&q*OW#v84D;VX>b4s4!`d9B1`x&mc zjv`|N_FyB1jj5)rzS1VL>x$I8ADZ=xb4?kqWzD&cjb57TgfzmetFQdk_C<1RLNQTX zBDt-e_~%*cd8Dgn{}<_W4K2J3&~7pKObjbY!OE)y*C+wu4(LscvZHPV%RWM9Ehcx= zE$6kT4}$@7uH)fd^;g(s*J)Bc44Ue4^b5P8{*G>9?nb=v`X1JNPMaJ9gjUL8Q~MbA z>6J3MM{l!d)T~Eh0fnxi;12E!h}6zdZsE)tL-?4djN51!PxxjY85beu(GaGusS&8J zXU{3aHX3K9O;4k7Jkc=kQ-+f)6^r7?=5-^QVhI~I3~w2p8s5etn=cxE%*;2z>3}Th z@S}tAwjACc9SXUIaJ64nGOzk0E_;yEv!lJT%4H=ukzz6tT)u;c@@;~1eZqGEIxz1Z z=CYKGv^$Z-{;1Lsw&bEeEDpw%PQ;k^Cq+4`M#Z?)&Hu=Let2)9qhcb){X%jf%ic1z z#O-zZlNTqmLGoh)>h$S3I7f_p$h!b-^AQ^46VhD*WAh}jPYH5@0X5CpJD^`;lp6q? z^cpTS&UBdb1NBnv@0i-}y!4@R1UlX3Hfl+Ce6Qwpqq=)YwXf3`slFnreDsA$xJk3N z2S}O@M`M`Is{$N#$SdelyC!?(ZZDurq$U$Xa-R_FQ3zcQ`Us<}17;tqX&928?e@Cr nhxqq4oexQ8KvI)Sid=H+u&f>>4Yr|vOgd4LMrj>a(rW(;b=Evr delta 630 zcmaJ+J8u&~5WYQm_a3`dXeiT%ih`1YlG1;GGCdT@%sR1BoL{wR|)_4T+|%! z-0bw;v|vF@u@w5zHFzyz_#hhN8bxC-WE9GUX7(Yx=hG;7EbhYBus9oB^>C@crG6%j zFvFb0eG~~{FSrGV!8}vA`jM#NO1ld8d3kyXX&qlTMIagNgfuHLyW~W^oQr(CLc2748rQWPTqTK cY79Hw83gUkG3A%>1T}mmt#qm*3{^@`0S>x&AOHXW diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index cac50ae..1ca88a0 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -25,7 +25,7 @@ from alpaca.data.enums import Exchange # raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}") -#for GUI +#for GUI to fetch historical trades on given symbol class Trade(BaseModel): symbol: str timestamp: datetime @@ -56,15 +56,21 @@ class RunRequest(BaseModel): id: UUID account: Account mode: Mode + note: Optional[str] = None debug: bool = False bt_from: datetime = None bt_to: datetime = None cash: int = 100000 + + + class RunnerView(BaseModel): id: UUID run_started: Optional[datetime] = None run_mode: Mode + run_name: Optional[str] = None + run_note: Optional[str] = None run_account: Account run_stopped: Optional[datetime] = None run_paused: Optional[datetime] = None @@ -75,6 +81,8 @@ class Runner(BaseModel): run_started: Optional[datetime] = None run_mode: Mode run_account: Account + run_name: Optional[str] = None + run_note: Optional[str] = None run_stopped: Optional[datetime] = None run_paused: Optional[datetime] = None run_thread: Optional[object] = None @@ -108,6 +116,37 @@ class TradeUpdate(BaseModel): cash: Optional[float] pos_avg_price: Optional[float] +#Contains archive of running strategies (runner) - master +class RunArchive(BaseModel): + #unique id of algorun + id: UUID + #id of running strategy (stratin/runner) + strat_id: UUID + name: str + note: Optional[str] = None + started: datetime + stopped: Optional[datetime] = None + mode: Mode + account: Account + bt_from: Optional[datetime] = None + bt_to: Optional[datetime] = None + stratvars: Optional[dict] = None + profit: float = 0 + trade_count: int = 0 + end_positions: int = 0 + end_positions_avgp: float = 0 + open_orders: int = 0 + +#Contains archive of running strategies (runner) - detail data +class RunArchiveDetail(BaseModel): + id: UUID + name: str + bars: dict + #trades: Optional[dict] + indicators: dict + statinds: dict + trades: List[TradeUpdate] + # class Trade(BaseModel): # order: Order # value: float diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 22dd025..098ecf2 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -5,13 +5,24 @@ from alpaca.data.historical import StockHistoricalDataClient from alpaca.data.requests import StockTradesRequest from alpaca.data.enums import DataFeed from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account -from v2realbot.common.model import StrategyInstance, Runner, RunRequest -from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string +from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail +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 +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 + +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) + +#db layer to store stratins, TBD zmigrovat do TinyDB db = Store() def get_all_threads(): @@ -259,6 +270,8 @@ def capsule(target: object, db: object): i.run_stop_ev = None #ukladame radek do historie (pozdeji refactor) save_history(id=i.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) @@ -342,6 +355,8 @@ def run_stratin(id: UUID, runReq: RunRequest): runner = Runner(id = i.id, run_started = datetime.now(zoneNY), run_pause_ev = pe, + run_name = name, + run_note = runReq.note, run_stop_ev = se, run_thread = vlakno, run_account = runReq.account, @@ -368,4 +383,79 @@ def get_trade_history(symbol: str, timestamp_from: float, timestamp_to:float): #print(all_trades[symbol]) return 0, all_trades[symbol] except Exception as e: - return (-2, f"problem {e}") \ No newline at end of file + return (-2, f"problem {e}") + +#archives runner and details +def archive_runner(runner: Runner, strat: StrategyInstance): + print("inside archive_runner") + try: + if strat.bt is not None: + bp_from = strat.bt.bp_from + bp_to = strat.bt.bp_to + else: + bp_from = None + bp_to = None + id = uuid4() + runArchive: RunArchive = RunArchive(id = id, + strat_id = runner.id, + name=runner.run_name, + note=runner.run_note, + started=runner.run_started, + stopped=runner.run_stopped, + mode=runner.run_mode, + account=runner.run_account, + bt_from=bp_from, + bt_to = bp_to, + stratvars = strat.state.vars, + profit=round(strat.state.profit,2), + trade_count=len(strat.state.tradeList), + end_positions=strat.state.positions, + end_positions_avgp=round(strat.state.avgp,3), + open_orders=9999 + ) + + runArchiveDetail: RunArchiveDetail = RunArchiveDetail(id = id, + name=runner.run_name, + bars=strat.state.bars, + indicators=strat.state.indicators, + statinds=strat.state.statinds, + trades=strat.state.tradeList) + resh = db_arch_h.insert(runArchive.__dict__) + resd = db_arch_d.insert(runArchiveDetail.__dict__) + print("archive runner finished") + return 0, str(resh) + " " + str(resd) + except Exception as e: + print(str(e)) + return -2, str(e) + +def get_all_archived_runners(): + res = db_arch_h.all() + return 0, res + +#delete runner in archive and archive detail +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) + except Exception as e: + return -2, str(e) + +def get_all_archived_runners_detail(): + res = db_arch_d.all() + return 0, res + +def get_archived_runner_details_byID(id: UUID): + res = db_arch_d.get(where('id') == id) + if res==None: + return -2, "not found" + else: + return 0, res + + +# change_archived_runner +# delete_archived_runner_details + + diff --git a/v2realbot/enums/__pycache__/enums.cpython-310.pyc b/v2realbot/enums/__pycache__/enums.cpython-310.pyc index 017f62b5abb55cd5541bae2f635670eb3bdef7ce..7abbfb4f052b02975a63f49c885b7374dd14de63 100644 GIT binary patch delta 371 zcmYk1KTE?v7>6%Sa=BcRia{HzO|`XFV{s7$N1b%4yNd{yu`Qf*6bd>xRt`Ubi>s60 z!cieOC=Sj}uKr#O=)wE&-gAF`cVDxUxHpO-$uQnNkJPuz*auDi4>-1uI={Z@?H%*n zx^zywL#8|>u9y<}uMsf5k}$F~K1`rOn6NYwCQ?8)mnGVivp!6!C zL)*-*_u449J{zjhpr3I)M@#dB8X+}XVm<4k-A4T-yGSP6&%(mVhvR{+U{;t5v9_3? c(@O+;v2GJ6T6gh{#5{?*jHB3(dFU1f` zql=sn3JE2ZN7Cl*QfKb5HImuv1$7g+J6EBK$fn(af| zLfUmMeYV`te}j`z56&)a%F-QCx==zmtlG62pPU`r8Bpz}w2>}zBOhmZx8;GhbkDRC MCQ%Y3ItdoTKhA$Wod5s; diff --git a/v2realbot/enums/enums.py b/v2realbot/enums/enums.py index 047c167..4e649af 100644 --- a/v2realbot/enums/enums.py +++ b/v2realbot/enums/enums.py @@ -13,7 +13,7 @@ class Order: self.limit_price = limit_price -class FillCondition(Enum): +class FillCondition(str, Enum): """ Execution settings: fast = pro vyplneni limi orderu musi byt cena stejne @@ -22,13 +22,13 @@ class FillCondition(Enum): """ FAST = "fast" SLOW = "slow" -class Account(Enum): +class Account(str, Enum): """ Accounts - keys to config """ ACCOUNT1 = "ACCOUNT1" ACCOUNT2 = "ACCOUNT2" -class RecordType(Enum): +class RecordType(str, Enum): """ Represents output of aggregator """ @@ -37,7 +37,7 @@ class RecordType(Enum): CBAR = "continuosbar" TRADE = "trade" -class Mode(Enum): +class Mode(str, Enum): """ LIVE or BT """ @@ -47,7 +47,7 @@ class Mode(Enum): BT = "backtest" -class StartBarAlign(Enum): +class StartBarAlign(str, Enum): """ Represents first bar start time alignement according to timeframe ROUND = bar starts at 0,5,10 (for 5s timeframe) diff --git a/v2realbot/main.py b/v2realbot/main.py index 10b3b92..23537b2 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -12,7 +12,7 @@ from fastapi.security import APIKeyHeader import uvicorn from uuid import UUID import v2realbot.controller.services as cs -from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest, Trade +from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveDetail from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query from fastapi.responses import HTMLResponse, FileResponse from fastapi.staticfiles import StaticFiles @@ -262,6 +262,44 @@ def get_trade_history(symbol: str, timestamp_from: float, timestamp_to:float) -> else: raise HTTPException(status_code=404, detail=f"No trades found {res}") +#ARCHIVE RUNNERS SECTION + +#get all archived runners header +@app.get("/archived_runners/", dependencies=[Depends(api_key_auth)]) +def _get_all_archived_runners() -> list[RunArchive]: + res, set =cs.get_all_archived_runners() + if res == 0: + return set + else: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found") + +#delete archive runner from header and detail +@app.delete("/archived_runners/{runner_id}", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) +def _delete_archived_runners_byID(runner_id): + res, id = cs.delete_archived_runners_byID(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}") + +#get all archived runners detail +@app.get("/archived_runners_detail/", dependencies=[Depends(api_key_auth)]) +def _get_all_archived_runners_detail() -> list[RunArchiveDetail]: + res, set =cs.get_all_archived_runners_detail() + if res == 0: + return set + else: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found") + +#get archived runners detail by id +@app.get("/archived_runners_detail/{runner_id}", dependencies=[Depends(api_key_auth)]) +def _get_archived_runner_details_byID(runner_id) -> RunArchiveDetail: + res, set = cs.get_archived_runner_details_byID(runner_id) + if res == 0: + return set + else: + raise HTTPException(status_code=404, detail=f"No runner with id: {runner_id} a {set}") + + #join cekej na dokonceni vsech for i in cs.db.runners: i.run_thread.join() diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index 5889221..c8278ae 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -17,25 +17,28 @@
-

Status: Not connected

-
-
- - - - - -
-
- -
-
-
-
+ +
Status: Not connected
+
-
@@ -50,15 +53,16 @@
-
- - - - - - -
-
+ + + +
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
IdId2NameSymbolclassscriptORCRStratvarsadd_datanotehistory
+ +
- +
+
+
diff --git a/v2realbot/static/js/mychart.js b/v2realbot/static/js/mychart.js index d0a64e5..104941b 100644 --- a/v2realbot/static/js/mychart.js +++ b/v2realbot/static/js/mychart.js @@ -1,70 +1,80 @@ -//const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } }; -const chartOptions = { width: 1045, height: 600, leftPriceScale: {visible: true}} -const chart = LightweightCharts.createChart(document.getElementById('chart'), chartOptions); -chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: { - mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true -}}) -const 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 - }, -}); + +//it is called after population +function populate_real_time_chart() { + if (chart !== null) { + chart.remove() + } + + //const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } }; + const chartOptions = { width: 1045, height: 600, leftPriceScale: {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 + }}) + const 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 + }, + }); -const 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, - }, -}); + const 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, + }, + }); -const vwapSeries = chart.addLineSeries({ -// title: "vwap", - color: '#2962FF', - lineWidth: 1, - lastValueVisible: false -}) + const vwapSeries = chart.addLineSeries({ + // title: "vwap", + color: '#2962FF', + lineWidth: 1, + lastValueVisible: false + }) -//chart.timeScale().fitContent(); + //chart.timeScale().fitContent(); -//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); + //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); + + + chart.subscribeClick(param => { + //display timestamp in trade-timestamp input field + $('#trade-timestamp').val(param.time) + }); + + 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 = '-'; + } + }); +} function pad(n) { var s = ('0' + n); return s.substr(s.length - 2); } - -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 + ""; - - //display timestamp in trade-timestamp input field - $('#trade-timestamp').val(param.time) - - } - else { - firstRow.innerText = '-'; - } -}); diff --git a/v2realbot/static/js/mytables.js b/v2realbot/static/js/mytables.js index 3ee103c..8c326a3 100644 --- a/v2realbot/static/js/mytables.js +++ b/v2realbot/static/js/mytables.js @@ -1,5 +1,24 @@ API_KEY = localStorage.getItem("api-key") +var chart = null +//Date.prototype.toJSON = function(){ return Date.parse(this)/1000 } + +// safely handles circular references https://stackoverflow.com/questions/11616630/how-can-i-print-a-circular-structure-in-a-json-like-format +JSON.safeStringify = (obj, indent = 2) => { + let cache = []; + const retVal = JSON.stringify( + obj, + (key, value) => + typeof value === "object" && value !== null + ? cache.includes(value) + ? undefined // Duplicate reference found, discard key + : cache.push(value) && value // Store value in our collection + : value, + indent + ); + cache = null; + return retVal; + }; // Iterate through each element in the // first array and if some of them @@ -92,9 +111,448 @@ function is_running(id) { // //console.log(obj); // //alert(JSON.stringify(obj)) +var tradeDetails = new Map(); +//CHART ARCHIVED RUN - move to own file +//input array object bars = { high: [1,2,3], time: [1,2,3], close: [2,2,2]...} +//output array [{ time: 111, open: 11, high: 33, low: 333, close: 333},..] +function transform_data(data) { + transformed = [] + //get basic bars, volume and vvwap + var bars = [] + var volume = [] + var vwap = [] + data.bars.time.forEach((element, index, array) => { + sbars = {}; + svolume = {}; + svwap = {}; + + sbars["time"] = element; + sbars["close"] = data.bars.close[index] + sbars["open"] = data.bars.open[index] + sbars["high"] = data.bars.high[index] + sbars["low"] = data.bars.low[index] + + + svwap["time"] = element + svwap["value"] = data.bars.vwap[index] + + svolume["time"] = element + svolume["value"] = data.bars.volume[index] + + bars.push(sbars) + vwap.push(svwap) + volume.push(svolume) + }); + transformed["bars"] = bars + transformed["vwap"] = vwap + transformed["volume"] = volume + + //get markers - avgp line for all buys + var avgp_buy_line = [] + var avgp_markers = [] + var markers = [] + var markers_line = [] + data.trades.forEach((trade, index, array) => { + obj = {}; + a_markers = {} + timestamp = Date.parse(trade.order.filled_at)/1000 + if (trade.order.side == "buy") { + //line pro avgp markers + obj["time"] = timestamp; + obj["value"] = trade.pos_avg_price; + avgp_buy_line.push(obj) + + //avgp markers pro prumernou cenu aktualnich pozic + a_markers["time"] = timestamp + a_markers["position"] = "aboveBar" + a_markers["color"] = "#e8c76d" + a_markers["shape"] = "arrowDown" + a_markers["text"] = trade.position_qty + " " + parseFloat(trade.pos_avg_price).toFixed(3) + avgp_markers.push(a_markers) + } + + //buy sell markery + marker = {} + marker["time"] = timestamp; + // marker["position"] = (trade.order.side == "buy") ? "belowBar" : "aboveBar" + marker["position"] = (trade.order.side == "buy") ? "inBar" : "aboveBar" + marker["color"] = (trade.order.side == "buy") ? "blue" : "red" + //marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown" + marker["shape"] = (trade.order.side == "buy") ? "circle" : "arrowDown" + marker["text"] = trade.qty + " " + trade.price + markers.push(marker) + + //prevedeme iso data na timestampy + trade.order.submitted_at = Date.parse(trade.order.submitted_at)/1000 + trade.order.filled_at = Date.parse(trade.order.filled_at)/1000 + trade.timestamp = Date.parse(trade.order.timestamp)/1000 + tradeDetails.set(timestamp, trade) + + //line pro buy/sell markery + mline = {} + mline["time"] = timestamp + mline["value"] = trade.price + markers_line.push(mline) + + // time: datesForMarkers[i].time, + // position: 'aboveBar', + // color: '#e91e63', + // shape: 'arrowDown', + // text: 'Sell @ ' + Math.floor(datesForMarkers[i].high + 2), + + + }); + transformed["avgp_buy_line"] = avgp_buy_line + transformed["markers"] = markers + transformed["markers_line"] = markers_line + transformed["avgp_markers"] = avgp_markers + //get additional indicators + //TBD + return transformed +} + +function chart_archived_run(data) { + if (chart !== null) { + chart.remove() + } + + //console.log("inside") + var transformed_data = transform_data(data) + //console.log(transformed_data) + //tbd transform indicators + //var markersData = transform_trades(data) + + // time: datesForMarkers[i].time, + // position: 'aboveBar', + // color: '#e91e63', + // shape: 'arrowDown', + // text: 'Sell @ ' + Math.floor(datesForMarkers[i].high + 2), + document.getElementById("chart").style.display = "block" + //initialize chart + var chartOptions = { width: 1300, height: 600, leftPriceScale: {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 + }}) + var 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 + }, + }); + + 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, + }, + }); + + archVwapSeries.setData(transformed_data["vwap"]) + archCandlestickSeries.setData(transformed_data["bars"]) + archVolumeSeries.setData(transformed_data["volume"]) + + + var avgBuyLine = chart.addLineSeries({ + // title: "avgpbuyline", + color: '#e8c76d', + // color: 'transparent', + lineWidth: 1, + lastValueVisible: false + }); + + avgBuyLine.setData(transformed_data["avgp_buy_line"]); + + avgBuyLine.setMarkers(transformed_data["avgp_markers"]) + + var markersLine = chart.addLineSeries({ + // title: "avgpbuyline", + // color: '#d6d1c3', + color: 'transparent', + lineWidth: 1, + lastValueVisible: false + }); + + markersLine.setData(transformed_data["markers_line"]); + + //console.log("markers") + //console.log(transformed_data["markers"]) + + markersLine.setMarkers(transformed_data["markers"]) + //TBD dynamicky + //pokud je nazev atributu X_candles vytvorit candles + //pokud je objekt Y_line pak vytvorit lajnu + //pokud je objekt Z_markers pak vytvorit markers + //pokud je Z = X nebo Y, pak markers dat na danou lajnu (priklad vvwap_line, avgp_line, avgp_markers) + //udelat si nahodny vyber barev z listu + + //DO BUDOUCNA MARKERS + // chart.subscribeCrosshairMove(param => { + // console.log(param.hoveredObjectId); + // }); + + + //define tooltip + const container1 = document.getElementById('chart'); + + const toolTipWidth = 90; + const toolTipHeight = 90; + const toolTipMargin = 15; + + // Create and style the tooltip html element + const toolTip = document.createElement('div'); + //width: 90px; , height: 80px; + toolTip.style = `position: absolute; display: none; padding: 8px; box-sizing: border-box; font-size: 12px; text-align: left; z-index: 1000; top: 12px; left: 12px; pointer-events: none; border: 1px solid; border-radius: 2px;font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;`; + toolTip.style.background = 'white'; + toolTip.style.color = 'black'; + toolTip.style.borderColor = '#2962FF'; + container1.appendChild(toolTip); + + + //TODO onlick zkopirovat timestamp param.time + // chart.subscribeClick(param => { + // $('#trade-timestamp').val(param.time) + // //alert(JSON.safeStringify(param)) + // //console.log(param.hoveredObjectId); + // }); + + //chart.subscribeCrosshairMove(param => { + + chart.subscribeClick(param => { + $('#trade-timestamp').val(param.time) + if ( + param.point === undefined || + !param.time || + param.point.x < 0 || + param.point.x > container1.clientWidth || + param.point.y < 0 || + param.point.y > container1.clientHeight + ) { + toolTip.style.display = 'none'; + } else { + //vyber serie s jakou chci pracovat - muzu i dynamicky + //je to mapa https://tradingview.github.io/lightweight-charts/docs/api/interfaces/MouseEventParams + + //key = series (key.seriestype vraci Line/Candlestick atp.) https://tradingview.github.io/lightweight-charts/docs/api/interfaces/SeriesOptionsMap + + toolTip.style.display = 'none'; + toolTip.innerHTML = ""; + var data = param.seriesData.get(markersLine); + if (data !== undefined) { + //param.seriesData.forEach((value, key) => { + //console.log("key",key) + //console.log("value",value) + + //data = value + //DOCASNE VYPNUTO + toolTip.style.display = 'block'; + + //console.log(JSON.safeStringify(key)) + if (toolTip.innerHTML == "") { + toolTip.innerHTML = `
${param.time}
` + } + var price = data.value + // !== undefined ? data.value : data.close; + + + toolTip.innerHTML += `
${JSON.stringify(tradeDetails.get(param.time),null,2)}
${price.toFixed(3)}
`; + + //inspirace + // toolTip.innerHTML = `
Apple Inc.
+ // ${Math.round(100 * price) / 100} + //
+ // ${dateStr} + //
`; + + + // Position tooltip according to mouse cursor position + toolTip.style.left = param.point.x+120 + 'px'; + toolTip.style.top = param.point.y-100 + 'px'; + } + //}); + } + }); + + + + + chart.timeScale().fitContent(); + + //TBD other dynamically created indicators + +} + + + +//ARCHIVE TABLES +$(document).ready(function () { + archiveRecords.ajax.reload(); + + //disable buttons (enable on row selection) + $('#button_show_arch').attr('disabled','disabled'); + $('#button_delete_arch').attr('disabled','disabled'); + + + //selectable rows in archive table + $('#archiveTable tbody').on('click', 'tr', function () { + if ($(this).hasClass('selected')) { + $(this).removeClass('selected'); + $('#button_show_arch').attr('disabled','disabled'); + $('#button_delete_arch').attr('disabled','disabled'); + } else { + stratinRecords.$('tr.selected').removeClass('selected'); + $(this).addClass('selected'); + $('#button_show_arch').attr('disabled',false); + $('#button_delete_arch').attr('disabled',false); + } + }); + + //delete button + $('#button_delete_arch').click(function () { + row = archiveRecords.row('.selected').data(); + window.$('#delModalArchive').modal('show'); + $('#delidarchive').val(row.id); + }); + + + //show button + $('#button_show_arch').click(function () { + row = archiveRecords.row('.selected').data(); + $('#button_show_arch').attr('disabled',true); + $.ajax({ + url:"/archived_runners_detail/"+row.id, + beforeSend: function (xhr) { + xhr.setRequestHeader('X-API-Key', + API_KEY); }, + method:"GET", + contentType: "application/json", + dataType: "json", + success:function(data){ + $('#button_show_arch').attr('disabled',false); + //$('#chartArchive').append(JSON.stringify(data,null,2)); + console.log(JSON.stringify(data,null,2)); + chart_archived_run(data); + }, + error: function(xhr, status, error) { + var err = eval("(" + xhr.responseText + ")"); + window.alert(JSON.stringify(xhr)); + //console.log(JSON.stringify(xhr)); + $('#button_show_arch').attr('disabled',false); + } + }) + }); + + + + + + + + + + +}) + +//delete modal +$("#delModalArchive").on('submit','#delFormArchive', function(event){ + event.preventDefault(); + $('#deletearchive').attr('disabled','disabled'); + id = $('#delidarchive').val() + //var formData = $(this).serializeJSON(); + $.ajax({ + url:"/archived_runners/"+id, + beforeSend: function (xhr) { + xhr.setRequestHeader('X-API-Key', + API_KEY); }, + method:"DELETE", + contentType: "application/json", + dataType: "json", + success:function(data){ + $('#delFormArchive')[0].reset(); + window.$('#delModalArchive').modal('hide'); + $('#deletearchive').attr('disabled', false); + archiveRecords.ajax.reload(); + }, + error: function(xhr, status, error) { + var err = eval("(" + xhr.responseText + ")"); + window.alert(JSON.stringify(xhr)); + console.log(JSON.stringify(xhr)); + $('#deletearchive').attr('disabled', false); + } + }) +}); + + +//https://www.w3schools.com/jsref/jsref_tolocalestring.asp +function format_date(datum) { + //const options = { weekday: 'long', year: 'numeric', month: 'numeric', day: 'numeric', }; + const options = {dateStyle: "short", timeStyle: "short"} + const date = new Date(datum); + return date.toLocaleString('cs-CZ', options); +} + +//stratin table +var archiveRecords = + $('#archiveTable').DataTable( { + ajax: { + url: '/archived_runners/', + dataSrc: '', + beforeSend: function (xhr) { + xhr.setRequestHeader('X-API-Key', + API_KEY); }, + error: function(xhr, status, error) { + //var err = eval("(" + xhr.responseText + ")"); + //window.alert(JSON.stringify(xhr)); + console.log(JSON.stringify(xhr)); + } + }, + columns: [{ data: 'id' }, + {data: 'name'}, + {data: 'note'}, + {data: 'started'}, + {data: 'stopped'}, + {data: 'mode'}, + {data: 'account', visible: true}, + {data: 'bt_from', visible: true}, + {data: 'bt_to', visible: true}, + {data: 'stratvars', visible: true}, + {data: 'profit'}, + {data: 'trade_count', visible: true}, + {data: 'end_positions', visible: true}, + {data: 'end_positions_avgp', visible: true}, + {data: 'open_orders', visible: true} + ], + columnDefs: [{ + targets: [3,4,7,8], + render: function ( data, type, row ) { + return format_date(data) + }, + }], + order: [[4, 'desc']], + paging: true, + lengthChange: false, + // createdRow: function( row, data, dataIndex){ + // if (is_running(data.id) ){ + // alert("runner"); + // $(row).addClass('highlight'); + // } + //} + } ); + +//STRATIN and RUNNERS TABELS $(document).ready(function () { //reaload hlavni tabulky @@ -239,6 +697,7 @@ $(document).ready(function () { $('#button_refresh').click(function () { runnerRecords.ajax.reload(); stratinRecords.ajax.reload(); + archiveRecords.ajax.reload(); }) //button copy @@ -258,7 +717,7 @@ $(document).ready(function () { rec.add_data_conf = row.add_data_conf; rec.note = row.note; rec.history = ""; - jsonString = JSON.stringify(rec); + jsonString = JSON.stringify(rec, null, 2); navigator.clipboard.writeText(jsonString); $('#button_copy').attr('disabled', false); }) diff --git a/v2realbot/static/js/mywebsocket.js b/v2realbot/static/js/mywebsocket.js index 395349a..f26ab3c 100644 --- a/v2realbot/static/js/mywebsocket.js +++ b/v2realbot/static/js/mywebsocket.js @@ -16,6 +16,7 @@ function connect(event) { console.log("nejaky error" + err) } ws.onopen = function(event) { + populate_real_time_chart() document.getElementById("status").textContent = "Connected to" + runnerId.value document.getElementById("bt-disc").style.display = "initial" document.getElementById("bt-conn").style.display = "none" diff --git a/v2realbot/strategy/__pycache__/base.cpython-310.pyc b/v2realbot/strategy/__pycache__/base.cpython-310.pyc index 5ab3345f8bb391202141c5c9d8381acd9e6b46d3..9dde1bb8efcc79596860b597d8be23e85d33a9b4 100644 GIT binary patch delta 1645 zcmai!U2GIp6vum~-R*X_OSdhRT`0Sy?QS}xZf$9WDv27?R;5PKR5T*f>F(4H?(S^Q zOh0VE20s!L6$qE`WrSkV5JLiz5%Ghns6gdG^u;Fg;*0S?6JJd5#Tf7ZuG)qdcaz`z z@44rkd+)ht=BD|b8MeaVV43(0zxGJ>#D(y!%21;i+4l(e6&?vB+&csBG1l)cRXwT% zy~>fIHcD3BbJNwmj6Lg)h0ilK!lN~hv6uN+%_7V2&PW5x@_|UJe@tYd-F!TfWGCID zks!M(3yU=O3gy-0blcG!(zAxbY7MX7ldm zx@}5i>h^#`t2g^|Fc$gE)#L01{y;+;d*9vNa7z&bJ$j&_lnyQ$pa2ux7t?EBfvghn zDs0Cpish$bTf~|h67{~Pg!w1F82cl%BA(w^JLG#)%(S|#>+0Ex)TOM*H^~PZ4~OS?e5}gJjrNZo)CGf(V?_ixSr39^IHs)Vkj zIG_XQFG4GongG{N`v6=cp>|%i?dXn~$s5j?m8CPVqcU_IF!>&}k*(v$)nC}K`|8Fa z#gAL3Irqo*IGbHQxIFIi>1AzhJH0%(EP*^YN}yH}YENGRUjbhOR{`7sT@z55tfLyE zvoLJ|N-f6sq?;2q#@m2$ptL2V{3+YX(&I{@Ji)4+O4jtlur4x~5Kbf!xEy3^a zs9;OHuVb%&8o@8}#g1QRe-KhFFWu!qn5Cg&g<td-HUfBFOVbAT*I3`%wn;zhTK zW5$Pt{FaIDJ=O}8Rj{r=w1;3-d&qNHSG%(2j>#@E^xTN$q%&4NZxzycD{JKVgWbu< g1>s380AfT^e4XFu-c+kZScC=Gs`9c~{Oj)i8@=j@H2?qr delta 1638 zcmai!e@t6d6vw%RmQpAbShZ!N1xg8T!6F;D;Y`#-m}Zz|OPI>$-d@`WJks`+^BzAk zF@(hUhccaBvR`JKiQ6BGGeqCSUt@9Vl$e-l;;+1aG%@iHP5j4X{$q^yd#ghBk8Sel zx!-%wdFP&UUcWSM8bLD{43vpadSx&>@n-N=W%XvUvhEe|d3eYlckc3EVr;8T}<9srdWM`d| zp#Zx(4MocR!gA|!nq{jtX<1#hjl51ru)h_k0Y>;&^-14Bn1}eE^-+dkja+F6vJ1|R zhCQCp)a?#0Z`$FTfLP!+H%&5wKN#+0%g#jjmPahK$cy1pI2?Hr@BkL~Mz#6{m{kI9 zg=L#X@%WkOPVvmvc%%1#kbmN9(Lbs;`18-3hP}^;orKfAxsh!MU5bi$HXquuRJ{n( z0$!5gRAKJo<;_RgqFgVD^&=;W(oeNrUV)@C-7!nrvZjNzbg%FYr^zpIeJK!Dd}nbeZL{ z3_UM}qjXjLDjvGVyF2gXD;>4$igTkQ!G;{6)8oJu;B8<9cn5eFkas{v^d9g&fK2EE zzytFAh`V3eTa4S_cYi3mSpZS1(8Ia|O!jjVie;UL! zV~2(L1rz`Dc$UAcf^G9h`UtAhN8dYaTbJjKu$C)o87+mk+bE2s@@7`gS^S~iWM~iih8!T3Zo&W#< diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 6799f2e..04a400b 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -17,7 +17,8 @@ from v2realbot.interfaces.backtest_interface import BacktestInterface from v2realbot.interfaces.live_interface import LiveInterface from alpaca.trading.enums import OrderSide from v2realbot.backtesting.backtester import Backtester -from alpaca.trading.models import TradeUpdate +#from alpaca.trading.models import TradeUpdate +from v2realbot.common.model import TradeUpdate from alpaca.trading.enums import TradeEvent, OrderStatus from threading import Event, current_thread import json diff --git a/v2realbot/utils/__pycache__/utils.cpython-310.pyc b/v2realbot/utils/__pycache__/utils.cpython-310.pyc index 85233dc29d6ddc0aa18fd052512eae93d5463ca1..ceec23a02c937753b5a0bdc607ed501a59cd5e25 100644 GIT binary patch delta 2703 zcmZ{le@q<56~}k(_LjTj?l{Z==YxX-4CKII3^+CzV{mX?+vyL5IJGuuPUo9BSbKNx z@^;U^;+CeSX_cmtoJm~ehpO7G+M+6UBO*o0A5p9R*Q))esv_+_k)o!O6<2B%scNOF z>U(<>0(C+A(ad+=y!U2q=FQ(`KRBAG#N$x~|87T5a?yDs(aEN748OKwW%P{Ip?5Ha zD?DO$T3J2ow*zLE-i2D!?6z`x&g#*7HuPS-7xhN7&)TE!@wJ%QZw=@JemicqTZ8&T zR$d?4=nrFm5c_%TFYp9!T4nlPp5)D|ioP3En9V@T@pcR(Tii#(59(oUa^2rHA*TatK5A z@jV!FiXZ0#BE$y;jXuQlt08@QWt%v}hfzGt_wo^RKf_1G5x&niALIMc+3aVOaXx{{ zQ9dacrhR}vjJ-Kb`yfArX&=J0=lK+$MnA_;JkZp;3Fqv+5{Vtb6S?YFQ3+)?{B!KTxV{)rogFd z%K6Bm;?|V}DvKLRll)lw6HCh9H1zhjp)W7!xB%_cQr#<60cmQ63k7bJ+(H3%%jR(B z!DfoOi_n3{GcQ;VY9=t~D+LVhj$J2oOu zM>2aS$y=WAnI(~%=4uffQE)4kS-{*3d+AYmBeI)K%lqx=^t`k6HLZHim#9MPCWDqVo1-Di;g%=i=s})<= zZl1xDIJ9GJa27QeKi^4Og|o7yshr!Mm&dAC$wWn@*)9ep`C8LQjLU1uA!f^;CQoYu zhoCI~k?fk-F*jy7g-TV}1*kd8Uq`b~V~D644Mdst?_M>>;EFui{6b-gTr3kf!629f zMxcd4g-|5`;Sym-xHyI@nH4-V6z>JHpxoiv_3>Q|Y~?H1z856WV*YQqe9#MD65;|c zUU9%COA2lLxdA6q`uFH&06Bvd!lp?+1P%$brJxbQ2(Mwo*@1iz{H?l7YIwO~8h(fe zEW+Qje&3tly*fDYThaYijz-@^yoaAd7GXtYVFqtXtNrZYIU3%YlEbbSa-n9I-p6MBN;0^$e!u-+ z>=bQEUiCsw4T~z=#$LTz>?1u0pC{pQ!WqOG^CAU2!$rrzn!A+?)tBGO@yy_v?Mv_) zus~NzXiFAed}D4&nC5@y`%ZpL0dfRN;8*x8!cxEkHm#h zaoGhbl<))jc=n{WNX@UxA7zI$dIDfx{vq4H0-KMUr{N2TXAfFv^dQ1ZUK|HeG|yGs z(Ne`O8%vO)u~xz!GEi$ZgM1b?qzq1H{p z8w5JpaEsvYzkepD`~>|?+&bCy11Lk?doyW#yTdGrUkv|~fh3Ct!cE~=A|p?C5BvvZ C)@{B3 delta 2651 zcmZ`)U1%KF72dluyIM)RtCg&^WUY2rmMzPxwSK&^W7$?@TaKf+NlF_v!I=~$qrG?K zv1VsCXJ%!~5JYUnf&4qyf!GA11d<~1+lo8{`cP;ov{2|<+o2Dol+Z%^5K7-ll5?(X zM^X}-{dDekzw>j?+@C+4_+lzqOC(xC`0t-D-OqoMxt{E0rR&9;%Pho0yxHlgW{ixo z!%nZ!i(HG7t>%neHE-l^^L<7i@~zJ9YQNF1xVW>Yy4TpN?1a-*9WeG)2aWx=#|0b@ z;CK+n_wgieTVci!Px1DZkWu6vdH8aK!Vz9|gOo@*pb1y$67?ypQ z--DwmEc*zbz_KT>>=XPbKZbEmB72-qB0DKgUDb@~<`BPEO!Fx*jKxn^qWlEUoJ9B2 z{1hhKppPRx%}*nHpXA{ynQ)Mr^}+JR{L)$1^DVb5g6Ja+*A=jy*K&+y<&^#m%dh`R zA7HFcejYj1n#Ioy3Uaa(9oP5cSiY3(VYlQ;^cqXZ(WZZ}r0k6K4|L*Gpm{z(nk?O4 zstedjX3R9XUG_~Ay5tkF-r-$H7FeLw<}Pf+-bg^w)(c+EH9Y~g<&=={a`K~CUhigb zQ2r(MhnXUYI#e_eb;l|TbJ21dq7Vj>!yv(?qDkIfx4;voU#mJMmS?;3Gx9&pyV%5f zQ%gT%9df93eCQY|*TRAB38xZ-EA{O`a1FL=;~Hk=bFD8Pnx;Hc1g9y5&KuP_Pz*Rj zu&n~!vlhkUaweW-lTyTsmQuEpvW?u^Ow)4Rns51b&GoQP$7w1>@BqPs1mDEG+b%eY z6F9z(8nN8hc(W3tv(>Dv~X z)f*(y5xQ9i155s|?eA<({y8I1`Y4LZ&$_k zdgHcd*6PAFNw6eeX-~6@^4<35O+m3L1ak!Q1Q!Sx!2&^*z$K^?P<7kGCop{3so~C| z_|KB!62a2?%N?*1DEaEtbov%5zinguCRIh4>YTI>rwsQ}=BCDMrkI@cNt6S58@W@!e z?0|jiWHwLE3s+pK!_=C5C|#Vrj83T69N`9<VI}gd+&Uoo=VQ`J2Ze0*OkKD(^zkqN#V|!qG z^2u~piAsYP30@`G>K4S55G(kACMCfQ`R>jOsn@rOWxgwu_yv+`t9LTEDQCJ)PCrDl zhY5a2piT;%rA>KMMs?b5;l!u-Q>QJ4=PkzImi%4Uxxpv5x{x}E_|S06pOIdjE(UL{ zKhXVu_5*4}K@TEc1MgH=#nHyaFigA#_Y?65!7RcW3z|*bnid`rbE7I%3tc6@l`r=U zKDu=YK@;B3wK8f+5hQN=mWAWo@mC9ahZ3krt7!cae|J~a*$k_)CG+oM0a?5a@wSH} z%5-ec4>}0VEf+QvQLB!ppW{KHkEpW1M`Wo@^Bpe*P?W#PYBsx1#Zy_Bx+Dr` zjMl0*U$@E&BjS<|^m*V-`APO{^v7gAA-~EFMNgA?LXPGJmV+q1f4Ip&-SUTVB1kQk zKv>RP%^xY(+=@L99i-nu&`$!r(XctFcT^W{qpBWK^@PF*iMj|zNtw1BEX5kZ=bU4b zk!p<#$Adfx_7f~0B&0UL1OYu1M);{l5SbUge;I(tHwexl1e)#o>ZS&I8sH+q62TP$ z)#2C3xRh&s#+weFT*x2jNwGNY{}le>_&*iK$0{6ai^Y>^ J`Sbj~uK|vRX}tgd diff --git a/v2realbot/utils/utils.py b/v2realbot/utils/utils.py index 7424b83..bd563eb 100644 --- a/v2realbot/utils/utils.py +++ b/v2realbot/utils/utils.py @@ -9,7 +9,7 @@ import decimal from v2realbot.enums.enums import RecordType, Mode, StartBarAlign import pickle import os -from v2realbot.common.model import StrategyInstance, Runner +from v2realbot.common.model import StrategyInstance, Runner, RunArchive, RunArchiveDetail from typing import List import tomli from v2realbot.config import DATA_DIR, QUIET_MODE @@ -66,6 +66,11 @@ def json_serial(obj): return obj.__dict__ if type(obj) is btTradeUpdate: return obj.__dict__ + if type(obj) is RunArchive: + return obj.__dict__ + if type(obj) is RunArchiveDetail: + return obj.__dict__ + raise TypeError (str(obj)+"Type %s not serializable" % type(obj)) def parse_toml_string(tomlst: str):