gui js refactors + ilogs database

This commit is contained in:
David Brazda
2023-05-05 18:20:52 +02:00
parent 4691599276
commit d9abc19c1f
26 changed files with 1105 additions and 305 deletions

BIN
test.db Normal file

Binary file not shown.

97
testy/testSqlite3.py Normal file
View File

@ -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)

View File

@ -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)
#res = db_runner_log.all()
#print(res)

44
testy/tinyFLUXtest.py Normal file
View File

@ -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)

View File

@ -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__)

View File

@ -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),

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" data-bs-theme="light">
<html lang="en" data-bs-theme="dark">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
@ -51,12 +51,16 @@
<button onclick="sendMessage(event)" id="bt.send" class="btn btn-outline-success btn-sm">Send</button>
</form>
</div>
<div id="statusHeader" data-bs-toggle="collapse" data-bs-target="#statusStratvars">
<div id="statusHeader" data-bs-toggle="collapse" data-bs-target="#statusDetail">
<div id="statusRegime" class="headerItem"></div>
<div id="statusName" class="headerItem"></div>
<div id="statusMode" class="headerItem"></div>
<div id="statusAccount" class="headerItem"></div>
<pre id="statusStratvars" class="headerItem collapse"></pre>
<div id="statusIlog" class="headerItem"></div>
<div id="statusDetail" class="headerItem collapse">
<pre id="statusStratvars" class="headerItem"></pre>
<pre id="statusSettings" class="headerItem"></pre>
</div>
</div>
<div id="chart" style="display: None; float: left; "></div>
<div class="legend" id="legend"></div>
@ -95,16 +99,18 @@
<button id="button_pause" class="btn btn-outline-success btn-sm">Pause/Unpause</button>
<button id="button_stop" class="btn btn-outline-success btn-sm">Stop</button>
<button id="button_stopall" class="btn btn-outline-success btn-sm">Stop All</button>
<button id="button_refresh" class="btn btn-outline-success btn-sm">Refresh</button>
<button id="button_refresh" class="refresh btn btn-outline-success btn-sm">Refresh</button>
</div>
<table id="runnerTable" class="table-striped table-bordered dataTable" style="width:100%; border-color: #dce1dc;">
<thead>
<tr>
<th>Id</th>
<th>StratId</th>
<th>Started</th>
<th>Mode</th>
<th>Symbol</th>
<th>Account</th>
<th>Account</th>
<th>ilog</th>
<th>Paused</th>
<th>Profit</th>
<th>Trades</th>
@ -152,6 +158,7 @@
<button id="button_edit_arch" class="btn btn-outline-success btn-sm">Edit</button>
<button id="button_delete_arch" class="btn btn-outline-success btn-sm">Delete</button>
<button id="button_show_arch" class="btn btn-outline-success btn-sm">Show</button>
<button id="button_refresh" class="refresh btn btn-outline-success btn-sm">Refresh</button>
<!-- <button id="button_stopall" class="btn btn-outline-success btn-sm">Stop All</button>
<button id="button_refresh" class="btn btn-outline-success btn-sm">Refresh</button> -->
</div>
@ -159,20 +166,22 @@
<thead>
<tr>
<th>Id</th>
<th>StratId</th>
<th>Name</th>
<th>Symbol</th>
<th>Sym</th>
<th>Note</th>
<th>started</th>
<th>stopped</th>
<th>mode</th>
<th>account</th>
<th>bt_from</th>
<th>bt_to</th>
<th>bt_to</th>
<th>ilog</th>
<th>stratvars</th>
<th>profit</th>
<th>tradecnt</th>
<th>end_pos</th>
<th>end_pos_avgp</th>
<th>trade</th>
<th>pos</th>
<th>pos_avgp</th>
<th>open</th>
</tr>
</thead>
@ -216,7 +225,7 @@
<input type="text" class="form-control" id="editidarchive" name="id" placeholder="id" readonly>
</div>
<div class="form-group">
<label for="note" class="form-label">note</label>
<label for="editnote" class="form-label">note</label>
<textarea class="form-control" rows="2" id="editnote" name="note"></textarea>
</div>
<div class="form-group">
@ -245,6 +254,7 @@
<button id="button_copy" class="btn btn-outline-success btn-sm">Copy JSON</button>
<button id="button_delete" class="btn btn-outline-success btn-sm">Delete</button>
<button id="button_run" class="btn btn-outline-success btn-sm">Run Strategy</button>
<button id="button_refresh" class="refresh btn btn-outline-success btn-sm">Refresh</button>
<table id="stratinTable" class="table-striped table-bordered dataTable" style="width:100%; border-color: #dce1dc;">
<thead>
<tr>
@ -418,8 +428,16 @@
<input type="number" class="form-control" id="cash" name="cash" placeholder="cash" value="100000">
</div>
<div class="form-group">
<label for="cash" class="form-label">Subscribe for RT</label>
<input type="checkbox" class="form-check-input mt-0" id="subscribe" name="subscribe" aria-label="Real time subscribe">
<label for="ilog_save" class="form-label">Enable logs</label>
<input type="checkbox" class="form-check" id="ilog_save" name="ilog_save" aria-label="Enable logs">
</div>
<div class="form-group">
<label for="subscribe" class="form-label">Subscribe for RT</label>
<input type="checkbox" class="form-check" id="subscribe" name="subscribe" aria-label="Real time subscribe">
</div>
<div class="form-group">
<label for="note" class="form-label">note</label>
<textarea class="form-control" rows="1" id="note" name="note"></textarea>
</div>
</div>
<div class="modal-footer">
@ -433,6 +451,14 @@
</div>
</div>
</div>
<div id="configContainer" class="flex-items">
<label data-bs-toggle="collapse" data-bs-target="#configInner" aria-expanded="true">
<h4><span class="badge secondary-bg">Config</span></h4>
</label>
<div id="configInner" class="collapse show">
config options
</div>
</div>
<div id="bottomContainer" class="flex-items" style="height: 800px">
<BR>
</div>

View File

@ -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))
// <div class="line">
// <div data-toggle="collapse" data-target="#rec1">12233 <strong>Event</strong></div>
// <div id="rec1" class="collapse">
// 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.
// </div>
// </div>
highlighted = (parseInt(logLine.time) == parseInt(timestamp)) ? "highlighted" : ""
logcnt++;
row = '<div data-bs-toggle="collapse" class="'+ highlighted + '" onclick="set_timestamp(' + logLine.time + ')" data-bs-target="#rec'+logcnt+'">'+logLine.time + " " + logLine.event + ' - '+ (logLine.message == undefined ? "" : logLine.message) +'</div>'
str_row = JSON.stringify(logLine.details, null, 2)
//row_detail = '<div id="rec'+logcnt+'" data-toggle="collapse" data-target="#rec'+logcnt+'"class="collapse pidi"><pre>' + str_row + '</pre></div>'
row_detail = '<div id="rec'+logcnt+'" class="collapse pidi"><pre>' + str_row + '</pre></div>'
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);
}

View File

@ -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("<pre>"+JSON.stringify(row.stratvars,null,2)+"</pre>")
//$("#lines").html("<pre>"+JSON.stringify(row.stratvars,null,2)+"</pre>")
//$('#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){

View File

@ -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

View File

@ -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();

View File

@ -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);
});
}

View File

@ -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 '<div class="legendItemName">' + val + '</>'
}
function val(val) {
return '<div class="legendItemValue">' + 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()

View File

@ -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 {

View File

@ -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

View File

@ -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)

92
v2realbot/utils/ilog.py Normal file
View File

@ -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)