diff --git a/testy/testExecList-standard.py b/testy/testExecList-standard.py index 008e644..89e61a1 100644 --- a/testy/testExecList-standard.py +++ b/testy/testExecList-standard.py @@ -11,34 +11,34 @@ btdata = [(1679081913.290388, 27.8634), (1679081913.68588, 27.865), (1679081913. from bisect import bisect_left -def get_last_price(time: float, symbol: str = None): - """"" - returns equity price in timestamp. Used for validations later. - TODO: optimalize - """"" - for i in range(len(btdata)): - #print(btdata[i][0]) - #print(i) - if btdata[i][0] >= time: - break - return btdata[i-1] +# def get_last_price(time: float, symbol: str = None): +# """"" +# returns equity price in timestamp. Used for validations later. +# TODO: optimalize +# """"" +# for i in range(len(btdata)): +# #print(btdata[i][0]) +# #print(i) +# if btdata[i][0] >= time: +# break +# return btdata[i-1] -def take_closest(myList, myNumber): - """ - Assumes myList is sorted. Returns first lower value to the number. - """ - pos = bisect_left(myList, (myNumber,)) - if pos == 0: - return myList[0] - # if pos == len(myList): - # return myList[-1] - after, afterPrice = myList[pos-1] - return after,afterPrice +# def take_closest(myList, myNumber): +# """ +# Assumes myList is sorted. Returns first lower value to the number. +# """ +# pos = bisect_left(myList, (myNumber,)) +# if pos == 0: +# return myList[0] +# # if pos == len(myList): +# # return myList[-1] +# after, afterPrice = myList[pos-1] +# return after,afterPrice -print("bisect price") -print(take_closest(btdata, 1679081913.986395)) -print("stamdard price") -print(get_last_price(1679081913.986395)) +# print("bisect price") +# print(take_closest(btdata, 1679081913.986395)) +# print("stamdard price") +# print(get_last_price(1679081913.986395)) #(1679081919.018929, 27.87), (1679081919.018932, 27.87), (1679081919.018938, 27.87), @@ -122,61 +122,66 @@ print(get_last_price(1679081913.986395)) -# def get_index_bisect(myList, time): -# """ -# Assumes myList is sorted. Returns first biggeer value to the number. -# """ -# pos = bisect_left(myList, (time,)) -# if pos == 0: -# return myList[0] -# if pos == len(myList): -# return myList[-1] -# return pos -# #after, afterPrice = myList[pos] -# #return after,afterPrice +def get_index_bisect(myList, time): + """ + Assumes myList is sorted. Returns first biggeer value to the number. + """ + pos = bisect_left(myList, (time,)) + if pos == 0: + return myList[0] + if pos == len(myList): + return myList[-1] + return pos + #after, afterPrice = myList[pos] + #return after,afterPrice -# def get_index(btdata, time: float): -# index_end = None # -# range_end = time -# print("range_end",range_end) +def get_index(btdata, time: float): + index_end = None # + range_end = time + print("range_end",range_end) -# for i in range(len(btdata)): -# #print(btdata[i][0]) -# #print(i) -# if btdata[i][0] >= range_end: -# index_end = i -# break + for i in range(len(btdata)): + #print(btdata[i][0]) + #print(i) + if btdata[i][0] >= range_end: + index_end = i + break -# print("index_end", index_end) -# print("oriznuto",btdata[0:index_end+1]) -# return index_end + print("index_end", index_end) + print("oriznuto",btdata[0:index_end+1]) + return index_end -# index_end = get_index(btdata, 1679081919.018939) -# print("get_index", index_end) -# index_end = get_index_bisect(btdata, 1679081919.018939) -# print("get_index_bisect", index_end) -# new_range = btdata[0:index_end+1] +index_end = get_index(btdata, 1679081919.018939) +print("get_index", index_end) +index_end = get_index_bisect(btdata, 1679081919.018939) +print("get_index_bisect", index_end) +new_range = btdata[0:index_end+1] -# print("novy rozsah?", len(new_range)) -# print("puvodni pole", len(btdata)) +print("novy rozsah?", len(new_range)) +print("puvodni pole", len(btdata)) -# #LIMIT FILL - BUY -# submitted_at: float = 1679081914.739644 -# limit_price: float = 27.865 -# fill_time = None -# for i in new_range: -# #print(i) -# ##najde prvni nejvetsi čas vetsi nez minfill a majici -# ## pro LIMITku uděláme nějaký spešl BT_DELAY.LIMIT_OFFSET, aby se nevyplnilo hned jako prvni s touto cenou -# ## tzn. o kolik se prumerne vyplni limitka pozdeji -# if float(i[0]) > float(float(submitted_at) + float(0.020)) and i[1] <= limit_price: -# #(1679081919.381649, 27.88) -# print(i) -# fill_time = i[0] -# print("FILL LIMIT BUY at", fill_time, "at",i[1]) -# break -# if not fill_time: print("NO FILL for ", limit_price) +#LIMIT FILL - BUY +submitted_at: float = 1679081914.739644 +limit_price: float = 27.865 +fill_time = None +for index, i in enumerate(new_range): +#for i in new_range: + #print(i) + ##najde prvni nejvetsi čas vetsi nez minfill a majici + ## pro LIMITku uděláme nějaký spešl BT_DELAY.LIMIT_OFFSET, aby se nevyplnilo hned jako prvni s touto cenou + ## tzn. o kolik se prumerne vyplni limitka pozdeji + if float(i[0]) > float(float(submitted_at) + float(0.020)) and i[1] <= limit_price: + #(1679081919.381649, 27.88) + print("pět předtím",new_range[index-5:index]) + print(i) + print("pět potom",new_range[index+1:index+6]) + # print(index) + fill_time = i[0] + ##zalogovat fill time + print("FILL LIMIT BUY at", fill_time, "at",i[1]) + break +if not fill_time: print("NO FILL for ", limit_price) # #LIMIT FILL - SELL # for i in new_range: diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index aa6c4a1..6467955 100644 Binary files a/v2realbot/__pycache__/config.cpython-310.pyc and b/v2realbot/__pycache__/config.cpython-310.pyc differ diff --git a/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc b/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc index 01a0da4..6a3157d 100644 Binary files a/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc and b/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc differ diff --git a/v2realbot/backtesting/backtester.py b/v2realbot/backtesting/backtester.py index 19e88d0..a04a6a3 100644 --- a/v2realbot/backtesting/backtester.py +++ b/v2realbot/backtesting/backtester.py @@ -43,9 +43,10 @@ from v2realbot.common.model import TradeUpdate, Order #from rich import print import threading import asyncio -from v2realbot.config import BT_DELAYS, DATA_DIR +from v2realbot.config import BT_DELAYS, DATA_DIR, FILL_CONDITION_BUY_LIMIT, FILL_CONDITION_SELL_LIMIT, FILL_LOG_SURROUNDING_TRADES, FILL_CONS_TRADES_REQUIRED from v2realbot.utils.utils import AttributeDict, ltp, zoneNY, trunc, count_decimals,print from v2realbot.utils.tlog import tlog +from v2realbot.enums.enums import FillCondition from datetime import datetime, timedelta import pandas as pd #import matplotlib.pyplot as plt @@ -192,7 +193,8 @@ class Backtester: #TEST zkusime to nemazat, jak ovlivni performance #Mazeme, jinak je to hruza - del self.btdata[0:index_end-2] + #nechavame na konci trady, které muzeme potrebovat pro consekutivni pravidlo + del self.btdata[0:index_end-2-FILL_CONS_TRADES_REQUIRED] #ic("after delete",len(self.btdata[0:index_end])) if changes: return 1 @@ -217,29 +219,63 @@ class Backtester: if o.order_type == OrderType.LIMIT: if o.side == OrderSide.BUY: - for i in work_range: + for index, i in enumerate(work_range): #print(i) ##najde prvni nejvetsi čas vetsi nez minfill a majici odpovídající cenu ## pro LIMITku nejspíš přidat BT_DELAY.LIMIT_OFFSET, aby se nevyplnilo hned jako prvni s touto cenou ## offest by se pocital od nize nalezeneho casu, zvetsil by ho o LIMIT_OFFSET a zjistil, zda by ##v novem case doslo take k plneni a tam ho vyplnil. Uvidime az jestli bude aktualni prilis optimisticke. ## TBD zjistit na LIVE jaky je tento offset - if float(i[0]) > float(order_min_fill_time+BT_DELAYS.limit_order_offset) and i[1] <= o.limit_price: + + #TODO pridat pokud je EXECUTION_DEBUG zalogování okolnich tradu (5 z kazde strany) od toho, který triggeroval plnění + #TODO pridat jako dalsi nastavovaci atribut pocet tradu po ktere musi byt cena zde (aby to nevyplnil knot high) + + #NASTVENI PODMINEK PLNENI + fast_fill_condition = i[1] <= o.limit_price + slow_fill_condition = i[1] < o.limit_price + if FILL_CONDITION_BUY_LIMIT == FillCondition.FAST: + fill_condition = fast_fill_condition + elif FILL_CONDITION_BUY_LIMIT == FillCondition.SLOW: + fill_condition = slow_fill_condition + else: + print("unknow fill condition") + return -1 + + if float(i[0]) > float(order_min_fill_time+BT_DELAYS.limit_order_offset) and fill_condition: #(1679081919.381649, 27.88) ic(i) fill_time = i[0] fill_price = i[1] print("FILL LIMIT BUY at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",i[1]) + if FILL_LOG_SURROUNDING_TRADES != 0: + #TODO loguru + print("FILL SURR TRADES: before",work_range[index-FILL_LOG_SURROUNDING_TRADES:index]) + print("FILL SURR TRADES: after",work_range[index+1:index+FILL_LOG_SURROUNDING_TRADES+1]) break else: - for i in work_range: + for index, i in enumerate(work_range): #print(i) - if float(i[0]) > float(order_min_fill_time+BT_DELAYS.limit_order_offset) and i[1] >= o.limit_price: + #NASTVENI PODMINEK PLNENI + fast_fill_condition = i[1] >= o.limit_price + slow_fill_condition = i[1] > o.limit_price + if FILL_CONDITION_SELL_LIMIT == FillCondition.FAST: + fill_condition = fast_fill_condition + elif FILL_CONDITION_SELL_LIMIT == FillCondition.SLOW: + fill_condition = slow_fill_condition + else: + print("unknown fill condition") + return -1 + + if float(i[0]) > float(order_min_fill_time+BT_DELAYS.limit_order_offset) and fill_condition: #(1679081919.381649, 27.88) ic(i) fill_time = i[0] fill_price = i[1] print("FILL LIMIT SELL at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",i[1]) + if FILL_LOG_SURROUNDING_TRADES != 0: + #TODO loguru + print("FILL SELL SURR TRADES: before",work_range[index-FILL_LOG_SURROUNDING_TRADES:index]) + print("FILL SELL SURR TRADES: after",work_range[index+1:index+FILL_LOG_SURROUNDING_TRADES+1]) break elif o.order_type == OrderType.MARKET: @@ -410,9 +446,9 @@ class Backtester: reserved = 0 #with lock: for o in self.open_orders: - if o.qty == OrderSide.SELL and o.symbol == symbol: + if o.side == OrderSide.SELL and o.symbol == symbol and o.canceled_at is None: reserved += o.qty - #print("blokovano v open orders pro sell: ", reserved) + print("blokovano v open orders pro sell: ", reserved) if int(self.account[symbol][0]) - reserved - int(size) < 0: print("not enough shares having",self.account[symbol][0],"reserved",reserved,"available",int(self.account[symbol][0]) - reserved,"selling",size) @@ -423,10 +459,10 @@ class Backtester: reserved = 0 #with lock: for o in self.open_orders: - if o.qty == OrderSide.BUY: + if o.side == OrderSide.BUY and o.canceled_at is None: cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol) reserved += o.qty * cena - #print("blokovano v open orders: ", reserved) + print("blokovano v open orders: ", reserved) cena = price if price else self.get_last_price(time, self.symbol) if (self.cash - reserved - float(int(size)*float(cena))) < 0: @@ -441,6 +477,7 @@ class Backtester: status = OrderStatus.ACCEPTED, side=side, qty=int(size), + filled_qty=0, limit_price=(float(price) if price else None)) self.open_orders.append(order) diff --git a/v2realbot/config.py b/v2realbot/config.py index 6f07067..c84fbb7 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -1,7 +1,20 @@ from alpaca.data.enums import DataFeed -from v2realbot.enums.enums import Mode, Account +from v2realbot.enums.enums import Mode, Account, FillCondition from appdirs import user_data_dir + +#how many consecutive trades with the fill price are necessary for limit fill to happen() +#0 - optimistic, every knot high will fill the order +#N - N consecutive trades required +#not impl.yet +FILL_CONS_TRADES_REQUIRED = 0 +#during trade execution logs X-surrounding trades of the one that triggers the fill +FILL_LOG_SURROUNDING_TRADES = 10 +#fill condition for limit order +# fast - price has to be equal or bigger <= +# slow - price has to be bigger < +FILL_CONDITION_BUY_LIMIT = FillCondition.FAST +FILL_CONDITION_SELL_LIMIT = FillCondition.FAST #no print in console QUIET_MODE = False #backend counter of api requests diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 1ebc057..0b26b50 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -218,9 +218,16 @@ def is_stratin_running(id: UUID): return False def save_history(id: UUID, st: object, runner: Runner, reason: str = None): + + #zkousime precist profit z objektu + try: + profit = st.state.profit + except Exception as e: + profit = str(e) + for i in db.stratins: if str(i.id) == str(id): - i.history += "START:"+str(runner.run_started)+"STOP:"+str(runner.run_stopped)+"ACC:"+runner.run_account.value+"M:"+runner.run_mode.value+"PROFIT:XX" + reason + "
" + i.history += "START:"+str(runner.run_started)+"STOP:"+str(runner.run_stopped)+"ACC:"+runner.run_account.value+"M:"+runner.run_mode.value+"PROFIT:"+str(profit)+ "REASON:" + str(reason) #i.history += str(runner.__dict__)+"
" db.save() diff --git a/v2realbot/enums/__pycache__/enums.cpython-310.pyc b/v2realbot/enums/__pycache__/enums.cpython-310.pyc index 05334ee..4d9c1db 100644 Binary files a/v2realbot/enums/__pycache__/enums.cpython-310.pyc and b/v2realbot/enums/__pycache__/enums.cpython-310.pyc differ diff --git a/v2realbot/enums/enums.py b/v2realbot/enums/enums.py index cdee934..047c167 100644 --- a/v2realbot/enums/enums.py +++ b/v2realbot/enums/enums.py @@ -12,6 +12,16 @@ class Order: self.filled_time = filled_time self.limit_price = limit_price + +class FillCondition(Enum): + """ + Execution settings: + fast = pro vyplneni limi orderu musi byt cena stejne + slow = vetsi (prip. mensi pro sell) + TBD nejspis pridat jeste stredni cestu - musi byt stejna + """ + FAST = "fast" + SLOW = "slow" class Account(Enum): """ Accounts - keys to config diff --git a/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc b/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc index 469eb00..778a6e7 100644 Binary files a/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc and b/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc differ diff --git a/v2realbot/strategy/__pycache__/base.cpython-310.pyc b/v2realbot/strategy/__pycache__/base.cpython-310.pyc index 4f9323d..f23230f 100644 Binary files a/v2realbot/strategy/__pycache__/base.cpython-310.pyc and b/v2realbot/strategy/__pycache__/base.cpython-310.pyc differ diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 499483e..534e8ae 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -334,6 +334,7 @@ class Strategy: ##kroky po iteraci def after_iteration(self, item): + #DAT DO VNORENE FUNKCE ##check if real time chart is requested ##posilame dict s objekty: bars, trades podle cbaru, a dale indicators naplnene time a pripadnymi identifikatory (EMA) if self.rtqueue is not None: @@ -389,7 +390,9 @@ 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 = [] # inicializace poplatna typu strategie (např. u LIMITu dotažení existující limitky) def strat_init(self): @@ -502,4 +505,6 @@ class StrategyState: else: row = dict(time=self.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