BT FILL CONDITIONS a FILL LOG SURROUNDING TRADES

This commit is contained in:
David Brazda
2023-04-22 21:02:07 +02:00
parent a49b9c831e
commit 8eb4a3f045
11 changed files with 164 additions and 87 deletions

View File

@ -11,34 +11,34 @@ btdata = [(1679081913.290388, 27.8634), (1679081913.68588, 27.865), (1679081913.
from bisect import bisect_left from bisect import bisect_left
def get_last_price(time: float, symbol: str = None): # def get_last_price(time: float, symbol: str = None):
""""" # """""
returns equity price in timestamp. Used for validations later. # returns equity price in timestamp. Used for validations later.
TODO: optimalize # TODO: optimalize
""""" # """""
for i in range(len(btdata)): # for i in range(len(btdata)):
#print(btdata[i][0]) # #print(btdata[i][0])
#print(i) # #print(i)
if btdata[i][0] >= time: # if btdata[i][0] >= time:
break # break
return btdata[i-1] # return btdata[i-1]
def take_closest(myList, myNumber): # def take_closest(myList, myNumber):
""" # """
Assumes myList is sorted. Returns first lower value to the number. # Assumes myList is sorted. Returns first lower value to the number.
""" # """
pos = bisect_left(myList, (myNumber,)) # pos = bisect_left(myList, (myNumber,))
if pos == 0: # if pos == 0:
return myList[0] # return myList[0]
# if pos == len(myList): # # if pos == len(myList):
# return myList[-1] # # return myList[-1]
after, afterPrice = myList[pos-1] # after, afterPrice = myList[pos-1]
return after,afterPrice # return after,afterPrice
print("bisect price") # print("bisect price")
print(take_closest(btdata, 1679081913.986395)) # print(take_closest(btdata, 1679081913.986395))
print("stamdard price") # print("stamdard price")
print(get_last_price(1679081913.986395)) # print(get_last_price(1679081913.986395))
#(1679081919.018929, 27.87), (1679081919.018932, 27.87), (1679081919.018938, 27.87), #(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): def get_index_bisect(myList, time):
# """ """
# Assumes myList is sorted. Returns first biggeer value to the number. Assumes myList is sorted. Returns first biggeer value to the number.
# """ """
# pos = bisect_left(myList, (time,)) pos = bisect_left(myList, (time,))
# if pos == 0: if pos == 0:
# return myList[0] return myList[0]
# if pos == len(myList): if pos == len(myList):
# return myList[-1] return myList[-1]
# return pos return pos
# #after, afterPrice = myList[pos] #after, afterPrice = myList[pos]
# #return after,afterPrice #return after,afterPrice
# def get_index(btdata, time: float): def get_index(btdata, time: float):
# index_end = None # index_end = None #
# range_end = time range_end = time
# print("range_end",range_end) print("range_end",range_end)
# for i in range(len(btdata)): for i in range(len(btdata)):
# #print(btdata[i][0]) #print(btdata[i][0])
# #print(i) #print(i)
# if btdata[i][0] >= range_end: if btdata[i][0] >= range_end:
# index_end = i index_end = i
# break break
# print("index_end", index_end) print("index_end", index_end)
# print("oriznuto",btdata[0:index_end+1]) print("oriznuto",btdata[0:index_end+1])
# return index_end return index_end
# index_end = get_index(btdata, 1679081919.018939) index_end = get_index(btdata, 1679081919.018939)
# print("get_index", index_end) print("get_index", index_end)
# index_end = get_index_bisect(btdata, 1679081919.018939) index_end = get_index_bisect(btdata, 1679081919.018939)
# print("get_index_bisect", index_end) print("get_index_bisect", index_end)
# new_range = btdata[0:index_end+1] new_range = btdata[0:index_end+1]
# print("novy rozsah?", len(new_range)) print("novy rozsah?", len(new_range))
# print("puvodni pole", len(btdata)) print("puvodni pole", len(btdata))
# #LIMIT FILL - BUY #LIMIT FILL - BUY
# submitted_at: float = 1679081914.739644 submitted_at: float = 1679081914.739644
# limit_price: float = 27.865 limit_price: float = 27.865
# fill_time = None fill_time = None
# for i in new_range: for index, i in enumerate(new_range):
# #print(i) #for i in new_range:
# ##najde prvni nejvetsi čas vetsi nez minfill a majici #print(i)
# ## pro LIMITku uděláme nějaký spešl BT_DELAY.LIMIT_OFFSET, aby se nevyplnilo hned jako prvni s touto cenou ##najde prvni nejvetsi čas vetsi nez minfill a majici
# ## tzn. o kolik se prumerne vyplni limitka pozdeji ## pro LIMITku uděláme nějaký spešl BT_DELAY.LIMIT_OFFSET, aby se nevyplnilo hned jako prvni s touto cenou
# if float(i[0]) > float(float(submitted_at) + float(0.020)) and i[1] <= limit_price: ## tzn. o kolik se prumerne vyplni limitka pozdeji
# #(1679081919.381649, 27.88) if float(i[0]) > float(float(submitted_at) + float(0.020)) and i[1] <= limit_price:
# print(i) #(1679081919.381649, 27.88)
# fill_time = i[0] print("pět předtím",new_range[index-5:index])
# print("FILL LIMIT BUY at", fill_time, "at",i[1]) print(i)
# break print("pět potom",new_range[index+1:index+6])
# if not fill_time: print("NO FILL for ", limit_price) # 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 # #LIMIT FILL - SELL
# for i in new_range: # for i in new_range:

View File

@ -43,9 +43,10 @@ from v2realbot.common.model import TradeUpdate, Order
#from rich import print #from rich import print
import threading import threading
import asyncio 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.utils import AttributeDict, ltp, zoneNY, trunc, count_decimals,print
from v2realbot.utils.tlog import tlog from v2realbot.utils.tlog import tlog
from v2realbot.enums.enums import FillCondition
from datetime import datetime, timedelta from datetime import datetime, timedelta
import pandas as pd import pandas as pd
#import matplotlib.pyplot as plt #import matplotlib.pyplot as plt
@ -192,7 +193,8 @@ class Backtester:
#TEST zkusime to nemazat, jak ovlivni performance #TEST zkusime to nemazat, jak ovlivni performance
#Mazeme, jinak je to hruza #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])) #ic("after delete",len(self.btdata[0:index_end]))
if changes: return 1 if changes: return 1
@ -217,29 +219,63 @@ class Backtester:
if o.order_type == OrderType.LIMIT: if o.order_type == OrderType.LIMIT:
if o.side == OrderSide.BUY: if o.side == OrderSide.BUY:
for i in work_range: for index, i in enumerate(work_range):
#print(i) #print(i)
##najde prvni nejvetsi čas vetsi nez minfill a majici odpovídající cenu ##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 ## 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 ## 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. ##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 ## 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) #(1679081919.381649, 27.88)
ic(i) ic(i)
fill_time = i[0] fill_time = i[0]
fill_price = i[1] fill_price = i[1]
print("FILL LIMIT BUY at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",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 break
else: else:
for i in work_range: for index, i in enumerate(work_range):
#print(i) #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) #(1679081919.381649, 27.88)
ic(i) ic(i)
fill_time = i[0] fill_time = i[0]
fill_price = i[1] fill_price = i[1]
print("FILL LIMIT SELL at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",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 break
elif o.order_type == OrderType.MARKET: elif o.order_type == OrderType.MARKET:
@ -410,9 +446,9 @@ class Backtester:
reserved = 0 reserved = 0
#with lock: #with lock:
for o in self.open_orders: 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 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: 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) 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 reserved = 0
#with lock: #with lock:
for o in self.open_orders: 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) cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol)
reserved += o.qty * cena 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) cena = price if price else self.get_last_price(time, self.symbol)
if (self.cash - reserved - float(int(size)*float(cena))) < 0: if (self.cash - reserved - float(int(size)*float(cena))) < 0:
@ -441,6 +477,7 @@ class Backtester:
status = OrderStatus.ACCEPTED, status = OrderStatus.ACCEPTED,
side=side, side=side,
qty=int(size), qty=int(size),
filled_qty=0,
limit_price=(float(price) if price else None)) limit_price=(float(price) if price else None))
self.open_orders.append(order) self.open_orders.append(order)

View File

@ -1,7 +1,20 @@
from alpaca.data.enums import DataFeed 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 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 #no print in console
QUIET_MODE = False QUIET_MODE = False
#backend counter of api requests #backend counter of api requests

View File

@ -218,9 +218,16 @@ def is_stratin_running(id: UUID):
return False return False
def save_history(id: UUID, st: object, runner: Runner, reason: str = None): 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: for i in db.stratins:
if str(i.id) == str(id): 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 + "<BR>" 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__)+"<BR>" #i.history += str(runner.__dict__)+"<BR>"
db.save() db.save()

View File

@ -12,6 +12,16 @@ class Order:
self.filled_time = filled_time self.filled_time = filled_time
self.limit_price = limit_price 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): class Account(Enum):
""" """
Accounts - keys to config Accounts - keys to config

View File

@ -334,6 +334,7 @@ class Strategy:
##kroky po iteraci ##kroky po iteraci
def after_iteration(self, item): def after_iteration(self, item):
#DAT DO VNORENE FUNKCE
##check if real time chart is requested ##check if real time chart is requested
##posilame dict s objekty: bars, trades podle cbaru, a dale indicators naplnene time a pripadnymi identifikatory (EMA) ##posilame dict s objekty: bars, trades podle cbaru, a dale indicators naplnene time a pripadnymi identifikatory (EMA)
if self.rtqueue is not None: if self.rtqueue is not None:
@ -389,7 +390,9 @@ class Strategy:
#cleaning iterlog lsit #cleaning iterlog lsit
#TODO pridat cistku i mimo RT blok #TODO pridat cistku i mimo RT blok
self.state.iter_log_list = [] 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) # inicializace poplatna typu strategie (např. u LIMITu dotažení existující limitky)
def strat_init(self): def strat_init(self):
@ -502,4 +505,6 @@ class StrategyState:
else: else:
row = dict(time=self.time, event=e, message=msg, details=kwargs) row = dict(time=self.time, event=e, message=msg, details=kwargs)
self.iter_log_list.append(row) self.iter_log_list.append(row)
row["name"] = self.name
print(row) print(row)
#TBD mozna odsud to posilat do nejakeho struct logger jako napr. structlog