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