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
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
# 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]
# 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)
for i in range(len(btdata)):
#print(btdata[i][0])
#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)
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
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))
#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:

View File

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

View File

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

View File

@ -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 + "<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>"
db.save()

View File

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

View File

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