renko + prescribedTrades and tradeList linked
This commit is contained in:
@ -17,9 +17,13 @@ class TradeStoplossType(str, Enum):
|
|||||||
FIXED = "fixed"
|
FIXED = "fixed"
|
||||||
TRAILING = "trailing"
|
TRAILING = "trailing"
|
||||||
|
|
||||||
|
#Predpis obchodu vygenerovany signalem, je to zastresujici jednotka
|
||||||
|
#ke kteremu jsou pak navazany jednotlivy FILLy (reprezentovany model.TradeUpdate) - napr. castecne exity atp.
|
||||||
class Trade(BaseModel):
|
class Trade(BaseModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
last_update: datetime
|
last_update: datetime
|
||||||
|
entry_time: Optional[datetime] = None
|
||||||
|
exit_time: Optional[datetime] = None
|
||||||
status: TradeStatus
|
status: TradeStatus
|
||||||
generated_by: Optional[str] = None
|
generated_by: Optional[str] = None
|
||||||
direction: TradeDirection
|
direction: TradeDirection
|
||||||
|
|||||||
@ -178,6 +178,7 @@ class Order(BaseModel):
|
|||||||
side: OrderSide
|
side: OrderSide
|
||||||
limit_price: Optional[float]
|
limit_price: Optional[float]
|
||||||
|
|
||||||
|
#entita pro kazdy kompletni FILL, je navazana na prescribed_trade
|
||||||
class TradeUpdate(BaseModel):
|
class TradeUpdate(BaseModel):
|
||||||
event: Union[TradeEvent, str]
|
event: Union[TradeEvent, str]
|
||||||
execution_id: Optional[UUID]
|
execution_id: Optional[UUID]
|
||||||
@ -194,6 +195,7 @@ class TradeUpdate(BaseModel):
|
|||||||
rel_profit: Optional[float]
|
rel_profit: Optional[float]
|
||||||
rel_profit_cum: Optional[float]
|
rel_profit_cum: Optional[float]
|
||||||
signal_name: Optional[str]
|
signal_name: Optional[str]
|
||||||
|
prescribed_trade_id: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class RunArchiveChange(BaseModel):
|
class RunArchiveChange(BaseModel):
|
||||||
|
|||||||
@ -56,6 +56,7 @@ class RecordType(str, Enum):
|
|||||||
BAR = "bar"
|
BAR = "bar"
|
||||||
CBAR = "cbar"
|
CBAR = "cbar"
|
||||||
CBARVOLUME = "cbarvolume"
|
CBARVOLUME = "cbarvolume"
|
||||||
|
CBARRENKO = "cbarrenko"
|
||||||
TRADE = "trade"
|
TRADE = "trade"
|
||||||
|
|
||||||
class Mode(str, Enum):
|
class Mode(str, Enum):
|
||||||
|
|||||||
@ -51,16 +51,21 @@ class TradeAggregator:
|
|||||||
self.lasttimestamp = 0
|
self.lasttimestamp = 0
|
||||||
#inicalizace pro prvni agregaci
|
#inicalizace pro prvni agregaci
|
||||||
self.newBar = dict(high=0, low=999999, volume = 0, trades = 0, confirmed = 0, vwap = 0, close=0, index = 1, updated = 0)
|
self.newBar = dict(high=0, low=999999, volume = 0, trades = 0, confirmed = 0, vwap = 0, close=0, index = 1, updated = 0)
|
||||||
self.openedVolumeBar = None
|
self.openedBar = None
|
||||||
self.lastConfirmedTime = 0
|
self.lastConfirmedTime = 0
|
||||||
self.bar_start = 0
|
self.bar_start = 0
|
||||||
self.curr_bar_volume = None
|
self.curr_bar_volume = None
|
||||||
|
self.current_bar_open = None
|
||||||
|
|
||||||
self.align = align
|
self.align = align
|
||||||
self.tm: datetime = None
|
self.tm: datetime = None
|
||||||
self.firstpass = True
|
self.firstpass = True
|
||||||
self.vwaphelper = 0
|
self.vwaphelper = 0
|
||||||
self.returnBar = {}
|
self.returnBar = {}
|
||||||
self.lastBarConfirmed = False
|
self.lastBarConfirmed = False
|
||||||
|
self.lastConfirmedBar = None
|
||||||
|
self.lasthigh = None
|
||||||
|
self.lastlow = None
|
||||||
#min trade size
|
#min trade size
|
||||||
self.minsize = minsize
|
self.minsize = minsize
|
||||||
|
|
||||||
@ -168,6 +173,9 @@ class TradeAggregator:
|
|||||||
|
|
||||||
if self.rectype == RecordType.CBARVOLUME:
|
if self.rectype == RecordType.CBARVOLUME:
|
||||||
return await self.calculate_volume_bar(data, symbol)
|
return await self.calculate_volume_bar(data, symbol)
|
||||||
|
|
||||||
|
if self.rectype == RecordType.CBARRENKO:
|
||||||
|
return await self.calculate_renko_bar(data, symbol)
|
||||||
|
|
||||||
async def calculate_time_bar(self, data, symbol):
|
async def calculate_time_bar(self, data, symbol):
|
||||||
#print("barstart",datetime.fromtimestamp(self.bar_start))
|
#print("barstart",datetime.fromtimestamp(self.bar_start))
|
||||||
@ -372,7 +380,7 @@ class TradeAggregator:
|
|||||||
""""
|
""""
|
||||||
Agreguje VOLUME BARS -
|
Agreguje VOLUME BARS -
|
||||||
hlavni promenne
|
hlavni promenne
|
||||||
- self.openedVolumeBar (dict) = stavová obsahují aktivní nepotvrzený bar
|
- self.openedBar (dict) = stavová obsahují aktivní nepotvrzený bar
|
||||||
- confirmedBars (list) = nestavová obsahuje confirmnute bary, které budou na konci funkceflushnuty
|
- confirmedBars (list) = nestavová obsahuje confirmnute bary, které budou na konci funkceflushnuty
|
||||||
"""""
|
"""""
|
||||||
#volume_bucket = 10000 #daily MA volume z emackova na 30 deleno 50ti - dat do configu
|
#volume_bucket = 10000 #daily MA volume z emackova na 30 deleno 50ti - dat do configu
|
||||||
@ -381,16 +389,16 @@ class TradeAggregator:
|
|||||||
confirmedBars = []
|
confirmedBars = []
|
||||||
#potvrdi existujici a nastavi k vraceni
|
#potvrdi existujici a nastavi k vraceni
|
||||||
def confirm_existing():
|
def confirm_existing():
|
||||||
self.openedVolumeBar['confirmed'] = 1
|
self.openedBar['confirmed'] = 1
|
||||||
self.openedVolumeBar['vwap'] = self.vwaphelper / self.openedVolumeBar['volume']
|
self.openedBar['vwap'] = self.vwaphelper / self.openedBar['volume']
|
||||||
self.vwaphelper = 0
|
self.vwaphelper = 0
|
||||||
|
|
||||||
#ulozime zacatek potvrzeneho baru
|
#ulozime zacatek potvrzeneho baru
|
||||||
self.lastBarConfirmed = self.openedVolumeBar['time']
|
#self.lastBarConfirmed = self.openedBar['time']
|
||||||
|
|
||||||
self.openedVolumeBar['updated'] = data['t']
|
self.openedBar['updated'] = data['t']
|
||||||
confirmedBars.append(deepcopy(self.openedVolumeBar))
|
confirmedBars.append(deepcopy(self.openedBar))
|
||||||
self.openedVolumeBar = None
|
self.openedBar = None
|
||||||
#TBD po každém potvrzení zvýšíme čas o nanosekundu (pro zobrazení v gui)
|
#TBD po každém potvrzení zvýšíme čas o nanosekundu (pro zobrazení v gui)
|
||||||
#data['t'] = data['t'] + 0.000001
|
#data['t'] = data['t'] + 0.000001
|
||||||
|
|
||||||
@ -399,7 +407,7 @@ class TradeAggregator:
|
|||||||
#inicializuji pro nový bar
|
#inicializuji pro nový bar
|
||||||
self.vwaphelper += (data['p'] * size)
|
self.vwaphelper += (data['p'] * size)
|
||||||
self.barindex +=1
|
self.barindex +=1
|
||||||
self.openedVolumeBar = {
|
self.openedBar = {
|
||||||
"close": data['p'],
|
"close": data['p'],
|
||||||
"high": data['p'],
|
"high": data['p'],
|
||||||
"low": data['p'],
|
"low": data['p'],
|
||||||
@ -418,20 +426,20 @@ class TradeAggregator:
|
|||||||
def update_unconfirmed(size):
|
def update_unconfirmed(size):
|
||||||
#spočteme vwap - potřebujeme předchozí hodnoty
|
#spočteme vwap - potřebujeme předchozí hodnoty
|
||||||
self.vwaphelper += (data['p'] * size)
|
self.vwaphelper += (data['p'] * size)
|
||||||
self.openedVolumeBar['updated'] = data['t']
|
self.openedBar['updated'] = data['t']
|
||||||
self.openedVolumeBar['close'] = data['p']
|
self.openedBar['close'] = data['p']
|
||||||
self.openedVolumeBar['high'] = max(self.openedVolumeBar['high'],data['p'])
|
self.openedBar['high'] = max(self.openedBar['high'],data['p'])
|
||||||
self.openedVolumeBar['low'] = min(self.openedVolumeBar['low'],data['p'])
|
self.openedBar['low'] = min(self.openedBar['low'],data['p'])
|
||||||
self.openedVolumeBar['volume'] = self.openedVolumeBar['volume'] + size
|
self.openedBar['volume'] = self.openedBar['volume'] + size
|
||||||
self.openedVolumeBar['trades'] = self.openedVolumeBar['trades'] + 1
|
self.openedBar['trades'] = self.openedBar['trades'] + 1
|
||||||
self.openedVolumeBar['vwap'] = self.vwaphelper / self.openedVolumeBar['volume']
|
self.openedBar['vwap'] = self.vwaphelper / self.openedBar['volume']
|
||||||
#pohrat si s timto round
|
#pohrat si s timto round
|
||||||
self.openedVolumeBar['hlcc4'] = round((self.openedVolumeBar['high']+self.openedVolumeBar['low']+self.openedVolumeBar['close']+self.openedVolumeBar['close'])/4,3)
|
self.openedBar['hlcc4'] = round((self.openedBar['high']+self.openedBar['low']+self.openedBar['close']+self.openedBar['close'])/4,3)
|
||||||
|
|
||||||
#init new - confirmed
|
#init new - confirmed
|
||||||
def initialize_confirmed(size):
|
def initialize_confirmed(size):
|
||||||
#ulozime zacatek potvrzeneho baru
|
#ulozime zacatek potvrzeneho baru
|
||||||
self.lastBarConfirmed = datetime.fromtimestamp(data['t'])
|
#self.lastBarConfirmed = datetime.fromtimestamp(data['t'])
|
||||||
self.barindex +=1
|
self.barindex +=1
|
||||||
confirmedBars.append({
|
confirmedBars.append({
|
||||||
"close": data['p'],
|
"close": data['p'],
|
||||||
@ -450,17 +458,17 @@ class TradeAggregator:
|
|||||||
})
|
})
|
||||||
|
|
||||||
#existuje stávající bar a vejdeme se do nej
|
#existuje stávající bar a vejdeme se do nej
|
||||||
if self.openedVolumeBar is not None and int(data['s']) + self.openedVolumeBar['volume'] < volume_bucket:
|
if self.openedBar is not None and int(data['s']) + self.openedBar['volume'] < volume_bucket:
|
||||||
#vejdeme se do stávajícího baru (tzn. neprekracujeme bucket)
|
#vejdeme se do stávajícího baru (tzn. neprekracujeme bucket)
|
||||||
update_unconfirmed(int(data['s']))
|
update_unconfirmed(int(data['s']))
|
||||||
#updatujeme stávající nepotvrzeny bar
|
#updatujeme stávající nepotvrzeny bar
|
||||||
#nevejdem se do nej nebo neexistuje predchozi bar
|
#nevejdem se do nej nebo neexistuje predchozi bar
|
||||||
else:
|
else:
|
||||||
#1)existuje predchozi bar - doplnime zbytkem do valikosti bucketu a nastavime confirmed
|
#1)existuje predchozi bar - doplnime zbytkem do valikosti bucketu a nastavime confirmed
|
||||||
if self.openedVolumeBar is not None:
|
if self.openedBar is not None:
|
||||||
|
|
||||||
#doplnime je zbytkem
|
#doplnime je zbytkem
|
||||||
bucket_left = volume_bucket - self.openedVolumeBar['volume']
|
bucket_left = volume_bucket - self.openedBar['volume']
|
||||||
# - update and confirm bar
|
# - update and confirm bar
|
||||||
update_unconfirmed(bucket_left)
|
update_unconfirmed(bucket_left)
|
||||||
confirm_existing()
|
confirm_existing()
|
||||||
@ -468,7 +476,7 @@ class TradeAggregator:
|
|||||||
#zbytek mnozství jde do dalsiho zpracovani
|
#zbytek mnozství jde do dalsiho zpracovani
|
||||||
data['s'] = int(data['s']) - bucket_left
|
data['s'] = int(data['s']) - bucket_left
|
||||||
#nastavime cas o nanosekundu vyssi
|
#nastavime cas o nanosekundu vyssi
|
||||||
data['t'] = data['t'] + 0.000001
|
data['t'] = round((data['t']) + 0.000001,6)
|
||||||
|
|
||||||
#2 vytvarime novy bar (bary) a vejdeme se do nej
|
#2 vytvarime novy bar (bary) a vejdeme se do nej
|
||||||
if int(data['s']) < volume_bucket:
|
if int(data['s']) < volume_bucket:
|
||||||
@ -483,10 +491,10 @@ class TradeAggregator:
|
|||||||
# 500
|
# 500
|
||||||
|
|
||||||
#vytvarime plne potvrzene buckety (kolik se jich plne vejde)
|
#vytvarime plne potvrzene buckety (kolik se jich plne vejde)
|
||||||
for size in range(0, int(data['s']), volume_bucket):
|
for size in range(volume_bucket, int(data['s']), volume_bucket):
|
||||||
initialize_confirmed(volume_bucket)
|
initialize_confirmed(volume_bucket)
|
||||||
#nastavime cas o nanosekundu vyssi
|
#nastavime cas o nanosekundu vyssi
|
||||||
data['t'] = data['t'] + 0.000001
|
data['t'] = round((data['t']) + 0.000001,6)
|
||||||
#create complete full bucket with same prices and size
|
#create complete full bucket with same prices and size
|
||||||
#naplnit do return pole
|
#naplnit do return pole
|
||||||
|
|
||||||
@ -517,17 +525,170 @@ class TradeAggregator:
|
|||||||
|
|
||||||
#pokud mame confirm bary, tak FLUSHNEME confirm a i případný open (zrejme se pak nejaky vytvoril)
|
#pokud mame confirm bary, tak FLUSHNEME confirm a i případný open (zrejme se pak nejaky vytvoril)
|
||||||
if len(confirmedBars) > 0:
|
if len(confirmedBars) > 0:
|
||||||
return_set = confirmedBars + ([self.openedVolumeBar] if self.openedVolumeBar is not None else [])
|
return_set = confirmedBars + ([self.openedBar] if self.openedBar is not None else [])
|
||||||
confirmedBars = []
|
confirmedBars = []
|
||||||
return return_set
|
return return_set
|
||||||
|
|
||||||
#nemame confirm, FLUSHUJEME CBARVOLUME open - neresime zmenu ceny, ale neposilame kulomet (pokud nam nevytvari conf. bar)
|
#nemame confirm, FLUSHUJEME CBARVOLUME open - neresime zmenu ceny, ale neposilame kulomet (pokud nam nevytvari conf. bar)
|
||||||
if self.openedVolumeBar is not None and self.rectype == RecordType.CBARVOLUME:
|
if self.openedBar is not None and self.rectype == RecordType.CBARVOLUME:
|
||||||
|
|
||||||
#zkousime pustit i stejnou cenu(potrebujeme kvuli MYSELLU), ale blokoval kulomet,tzn. trady mensi nez GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN (1ms)
|
#zkousime pustit i stejnou cenu(potrebujeme kvuli MYSELLU), ale blokoval kulomet,tzn. trady mensi nez GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN (1ms)
|
||||||
#if self.diff_price is True:
|
#if self.diff_price is True:
|
||||||
if self.trades_too_close is False:
|
if self.trades_too_close is False:
|
||||||
return [self.openedVolumeBar]
|
return [self.openedBar]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def calculate_renko_bar(self, data, symbol):
|
||||||
|
""""
|
||||||
|
Agreguje RENKO BARS - dle brick size
|
||||||
|
hlavni promenne
|
||||||
|
- self.openedBar (dict) = stavová obsahují aktivní nepotvrzený bar
|
||||||
|
- confirmedBars (list) = nestavová obsahuje confirmnute bary, které budou na konci funkceflushnuty
|
||||||
|
|
||||||
|
Omezeni: vzhledek tomu, že strategie v CBARu potřebuje realný průběh tick by tick a skutečné Renko bary znamenají
|
||||||
|
vyřazování určitých průběhů cenu, tak je realizováno Renko bary s high and low a následným updatem open ceny před confirmací.
|
||||||
|
|
||||||
|
open a close bude tedy v potvrzeném baru správně, high-low bude ukazovat na celkový pohyb cen v rámci baru.
|
||||||
|
|
||||||
|
Ve strategii je třeba počítat s tím, že open v nepotvrzeném baru není finální.
|
||||||
|
"""""
|
||||||
|
|
||||||
|
#pocet ticku např. 10ticků, případně pak na procenta
|
||||||
|
brick_size = self.resolution
|
||||||
|
#potvrzene pripravene k vraceni
|
||||||
|
confirmedBars = []
|
||||||
|
#potvrdi existujici a nastavi k vraceni
|
||||||
|
def confirm_existing():
|
||||||
|
self.openedBar['confirmed'] = 1
|
||||||
|
self.openedBar['vwap'] = self.vwaphelper / self.openedBar['volume']
|
||||||
|
self.vwaphelper = 0
|
||||||
|
|
||||||
|
self.openedBar['updated'] = data['t']
|
||||||
|
obar_copy = deepcopy(self.openedBar)
|
||||||
|
confirmedBars.append(obar_copy)
|
||||||
|
self.lastConfirmedBar = obar_copy
|
||||||
|
self.openedBar = None
|
||||||
|
#TBD po každém potvrzení zvýšíme čas o nanosekundu (pro zobrazení v gui)
|
||||||
|
#data['t'] = data['t'] + 0.000001
|
||||||
|
|
||||||
|
#init unconfirmed - velikost bucketu kontrolovana predtim
|
||||||
|
def initialize_unconfirmed():
|
||||||
|
#inicializuji pro nový bar
|
||||||
|
self.vwaphelper += (data['p'] * int(data['s']))
|
||||||
|
self.barindex +=1
|
||||||
|
self.openedBar = {
|
||||||
|
"close": data['p'],
|
||||||
|
"high": data['p'],
|
||||||
|
"low": data['p'],
|
||||||
|
"open": data['p'],
|
||||||
|
"volume": int(data['s']),
|
||||||
|
"trades": 1,
|
||||||
|
"hlcc4": data['p'],
|
||||||
|
"confirmed": 0,
|
||||||
|
"time": datetime.fromtimestamp(data['t']),
|
||||||
|
"updated": data['t'],
|
||||||
|
"vwap": data['p'],
|
||||||
|
"index": self.barindex,
|
||||||
|
"resolution":self.resolution
|
||||||
|
}
|
||||||
|
|
||||||
|
def update_unconfirmed(open = None):
|
||||||
|
|
||||||
|
if open is not None:
|
||||||
|
self.openedBar['open'] = open
|
||||||
|
#spočteme vwap - potřebujeme předchozí hodnoty
|
||||||
|
self.vwaphelper += (data['p'] * int(data['s']))
|
||||||
|
self.openedBar['updated'] = data['t']
|
||||||
|
self.openedBar['close'] = data['p']
|
||||||
|
self.openedBar['high'] = max(self.openedBar['high'],data['p'])
|
||||||
|
self.openedBar['low'] = min(self.openedBar['low'],data['p'])
|
||||||
|
self.openedBar['volume'] = self.openedBar['volume'] + int(data['s'])
|
||||||
|
self.openedBar['trades'] = self.openedBar['trades'] + 1
|
||||||
|
self.openedBar['vwap'] = self.vwaphelper / self.openedBar['volume']
|
||||||
|
#pohrat si s timto round
|
||||||
|
self.openedBar['hlcc4'] = round((self.openedBar['high']+self.openedBar['low']+self.openedBar['close']+self.openedBar['close'])/4,3)
|
||||||
|
|
||||||
|
#init new - confirmed
|
||||||
|
def initialize_confirmed(size):
|
||||||
|
self.barindex +=1
|
||||||
|
cf_bar = {
|
||||||
|
"close": data['p'],
|
||||||
|
"high": data['p'],
|
||||||
|
"low": data['p'],
|
||||||
|
"open": data['p'],
|
||||||
|
"volume": size,
|
||||||
|
"trades": 1,
|
||||||
|
"hlcc4":data['p'],
|
||||||
|
"confirmed": 1,
|
||||||
|
"time": datetime.fromtimestamp(data['t']),
|
||||||
|
"updated": data['t'],
|
||||||
|
"vwap": data['p'],
|
||||||
|
"index": self.barindex,
|
||||||
|
"resolution":self.resolution
|
||||||
|
}
|
||||||
|
self.lastConfirmedBar = cf_bar
|
||||||
|
confirmedBars.append(cf_bar)
|
||||||
|
|
||||||
|
#nastaveni top a low boundary comparatorů bud podle h/l predchoziho potvrzeneho baru
|
||||||
|
if self.lastConfirmedBar is not None:
|
||||||
|
top_boundary = max(self.lastConfirmedBar["open"], self.lastConfirmedBar["close"])
|
||||||
|
low_boundary = min(self.lastConfirmedBar["open"], self.lastConfirmedBar["close"])
|
||||||
|
#nebo openu, pokud mame jen nepotvrzeny
|
||||||
|
elif self.openedBar is not None:
|
||||||
|
top_boundary = self.openedBar["open"]
|
||||||
|
low_boundary = self.openedBar["open"]
|
||||||
|
|
||||||
|
if self.openedBar is None:
|
||||||
|
initialize_unconfirmed()
|
||||||
|
#pct variant: brick_size = self.brick_percentage * self.open_price / 100.0
|
||||||
|
elif data['p'] >= top_boundary + brick_size: # Check if the price has moved by the brick size
|
||||||
|
#confirm nese novou cenu, muzou tam byt skryte trady se stejnou cenou nebo kulomet o ktere bychom prisli
|
||||||
|
#jinymi slovy prekonací tick renkobaru patří do starého baru
|
||||||
|
#novy bar je vytvoren az dalsim tickem, snad to nebude vadit
|
||||||
|
|
||||||
|
#updatujeme open, kam patri
|
||||||
|
update_unconfirmed(open=top_boundary)
|
||||||
|
confirm_existing()
|
||||||
|
elif data['p'] <= low_boundary - brick_size:
|
||||||
|
update_unconfirmed(open=low_boundary)
|
||||||
|
confirm_existing()
|
||||||
|
else:
|
||||||
|
#update stávající
|
||||||
|
update_unconfirmed()
|
||||||
|
|
||||||
|
#je cena stejna od predchoziho tradu? pro nepotvrzeny cbar vracime jen pri zmene ceny
|
||||||
|
if self.last_price == data['p']:
|
||||||
|
self.diff_price = False
|
||||||
|
else:
|
||||||
|
self.diff_price = True
|
||||||
|
self.last_price = data['p']
|
||||||
|
|
||||||
|
if float(data['t']) - float(self.lasttimestamp) < GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN:
|
||||||
|
self.trades_too_close = True
|
||||||
|
else:
|
||||||
|
self.trades_too_close = False
|
||||||
|
|
||||||
|
#uložíme do předchozí hodnoty (poznáme tak open a close)
|
||||||
|
self.lasttimestamp = data['t']
|
||||||
|
self.iterace += 1
|
||||||
|
# print(self.iterace, data)
|
||||||
|
|
||||||
|
#pokud mame confirm bary, tak FLUSHNEME confirm a i případný open (zrejme se pak nejaky vytvoril)
|
||||||
|
if len(confirmedBars) > 0:
|
||||||
|
return_set = confirmedBars + ([self.openedBar] if self.openedBar is not None else [])
|
||||||
|
confirmedBars = []
|
||||||
|
return return_set
|
||||||
|
|
||||||
|
#nemame confirm, FLUSHUJEME CBARVOLUME open - neresime zmenu ceny, ale neposilame kulomet (pokud nam nevytvari conf. bar)
|
||||||
|
if self.openedBar is not None and self.rectype == RecordType.CBARRENKO:
|
||||||
|
|
||||||
|
#zkousime pustit i stejnou cenu(potrebujeme kvuli MYSELLU), ale blokoval kulomet,tzn. trady mensi nez GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN (1ms)
|
||||||
|
#if self.diff_price is True:
|
||||||
|
if self.trades_too_close is False:
|
||||||
|
return [self.openedBar]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -672,6 +672,12 @@ function populate_indicator_buttons(def) {
|
|||||||
buttonElement.appendChild(itemEl); ;
|
buttonElement.appendChild(itemEl); ;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var funcButtonElement = document.createElement('div');
|
||||||
|
funcButtonElement.id = "funcIndicatorsButtons"
|
||||||
|
funcButtonElement.classList.add('funcButtons');
|
||||||
|
|
||||||
|
|
||||||
//create toggle all button
|
//create toggle all button
|
||||||
var itemEl = document.createElement('button');
|
var itemEl = document.createElement('button');
|
||||||
itemEl.innerText = "all"
|
itemEl.innerText = "all"
|
||||||
@ -682,7 +688,7 @@ function populate_indicator_buttons(def) {
|
|||||||
itemEl.addEventListener('click', function() {
|
itemEl.addEventListener('click', function() {
|
||||||
onResetClicked();
|
onResetClicked();
|
||||||
});
|
});
|
||||||
buttonElement.appendChild(itemEl);
|
funcButtonElement.appendChild(itemEl);
|
||||||
|
|
||||||
//button pro toggle profitu
|
//button pro toggle profitu
|
||||||
var itemEl = document.createElement('button');
|
var itemEl = document.createElement('button');
|
||||||
@ -693,7 +699,7 @@ function populate_indicator_buttons(def) {
|
|||||||
itemEl.addEventListener('click', function(e) {
|
itemEl.addEventListener('click', function(e) {
|
||||||
profitLineToggle();
|
profitLineToggle();
|
||||||
});
|
});
|
||||||
buttonElement.appendChild(itemEl);
|
funcButtonElement.appendChild(itemEl);
|
||||||
|
|
||||||
//button pro toggle fullscreenu
|
//button pro toggle fullscreenu
|
||||||
var itemEl = document.createElement('button');
|
var itemEl = document.createElement('button');
|
||||||
@ -704,7 +710,7 @@ function populate_indicator_buttons(def) {
|
|||||||
itemEl.addEventListener('click', function(e) {
|
itemEl.addEventListener('click', function(e) {
|
||||||
toggleWide();
|
toggleWide();
|
||||||
});
|
});
|
||||||
buttonElement.appendChild(itemEl);
|
funcButtonElement.appendChild(itemEl);
|
||||||
|
|
||||||
//button pro toggle fullscreenu
|
//button pro toggle fullscreenu
|
||||||
var itemEl = document.createElement('button');
|
var itemEl = document.createElement('button');
|
||||||
@ -716,7 +722,7 @@ function populate_indicator_buttons(def) {
|
|||||||
itemEl.addEventListener('click', function(e) {
|
itemEl.addEventListener('click', function(e) {
|
||||||
toggleVolume();
|
toggleVolume();
|
||||||
});
|
});
|
||||||
buttonElement.appendChild(itemEl);
|
funcButtonElement.appendChild(itemEl);
|
||||||
|
|
||||||
// //button pro toggle markeru nakupu/prodeju
|
// //button pro toggle markeru nakupu/prodeju
|
||||||
var itemEl = document.createElement('button');
|
var itemEl = document.createElement('button');
|
||||||
@ -745,7 +751,7 @@ function populate_indicator_buttons(def) {
|
|||||||
mrkLineToggle();
|
mrkLineToggle();
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonElement.appendChild(itemEl);
|
funcButtonElement.appendChild(itemEl);
|
||||||
|
|
||||||
//create plus button to create new button
|
//create plus button to create new button
|
||||||
var itemEl = document.createElement('button');
|
var itemEl = document.createElement('button');
|
||||||
@ -757,7 +763,7 @@ function populate_indicator_buttons(def) {
|
|||||||
index_ind++
|
index_ind++
|
||||||
onItemClickedEdit(e, index_ind);
|
onItemClickedEdit(e, index_ind);
|
||||||
});
|
});
|
||||||
buttonElement.appendChild(itemEl);
|
funcButtonElement.appendChild(itemEl);
|
||||||
|
|
||||||
//save indicator buttons - will generate indicators to stratvars
|
//save indicator buttons - will generate indicators to stratvars
|
||||||
var itemEl = document.createElement('button');
|
var itemEl = document.createElement('button');
|
||||||
@ -769,7 +775,9 @@ function populate_indicator_buttons(def) {
|
|||||||
index_ind++
|
index_ind++
|
||||||
generateIndicators(e);
|
generateIndicators(e);
|
||||||
});
|
});
|
||||||
buttonElement.appendChild(itemEl);
|
funcButtonElement.appendChild(itemEl);
|
||||||
|
|
||||||
|
buttonElement.appendChild(funcButtonElement)
|
||||||
|
|
||||||
return buttonElement;
|
return buttonElement;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -150,8 +150,11 @@ class StrategyClassicSL(Strategy):
|
|||||||
trade.rel_profit = rel_profit
|
trade.rel_profit = rel_profit
|
||||||
trade.rel_profit_cum = rel_profit_cum_calculated
|
trade.rel_profit_cum = rel_profit_cum_calculated
|
||||||
signal_name = trade.generated_by
|
signal_name = trade.generated_by
|
||||||
if data.event == TradeEvent.FILL:
|
|
||||||
trade.status == TradeStatus.CLOSED
|
#Pokud FILL uzaviral celou pozici - uzavreme prescribed trade
|
||||||
|
if data.event == TradeEvent.FILL and data.position_qty == 0:
|
||||||
|
trade.status = TradeStatus.CLOSED
|
||||||
|
trade.exit_time = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
|
||||||
break
|
break
|
||||||
|
|
||||||
if data.event == TradeEvent.FILL:
|
if data.event == TradeEvent.FILL:
|
||||||
@ -164,6 +167,7 @@ class StrategyClassicSL(Strategy):
|
|||||||
setattr(tradeData, "profit", trade_profit)
|
setattr(tradeData, "profit", trade_profit)
|
||||||
setattr(tradeData, "profit_sum", self.state.profit)
|
setattr(tradeData, "profit_sum", self.state.profit)
|
||||||
setattr(tradeData, "signal_name", signal_name)
|
setattr(tradeData, "signal_name", signal_name)
|
||||||
|
setattr(tradeData, "prescribed_trade_id", self.state.vars.pending)
|
||||||
#self.state.ilog(f"updatnut tradeList o profit", tradeData=json.loads(json.dumps(tradeData, default=json_serial)))
|
#self.state.ilog(f"updatnut tradeList o profit", tradeData=json.loads(json.dumps(tradeData, default=json_serial)))
|
||||||
setattr(tradeData, "rel_profit", rel_profit)
|
setattr(tradeData, "rel_profit", rel_profit)
|
||||||
setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated)
|
setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated)
|
||||||
@ -184,11 +188,15 @@ class StrategyClassicSL(Strategy):
|
|||||||
for trade in self.state.vars.prescribedTrades:
|
for trade in self.state.vars.prescribedTrades:
|
||||||
if trade.id == self.state.vars.pending:
|
if trade.id == self.state.vars.pending:
|
||||||
signal_name = trade.generated_by
|
signal_name = trade.generated_by
|
||||||
|
#zapiseme entry_time (jen pokud to neni partial add) - tzn. jen poprvé
|
||||||
|
if data.event == TradeEvent.FILL and trade.entry_time is None:
|
||||||
|
trade.entry_time = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
|
||||||
|
|
||||||
#zapsat do tradeList
|
#zapsat do tradeList
|
||||||
for tradeData in self.state.tradeList:
|
for tradeData in self.state.tradeList:
|
||||||
if tradeData.execution_id == data.execution_id:
|
if tradeData.execution_id == data.execution_id:
|
||||||
setattr(tradeData, "signal_name", signal_name)
|
setattr(tradeData, "signal_name", signal_name)
|
||||||
|
setattr(tradeData, "prescribed_trade_id", self.state.vars.pending)
|
||||||
|
|
||||||
self.state.ilog(e="BUY: Jde o LONG nakuú nepocitame profit zatim")
|
self.state.ilog(e="BUY: Jde o LONG nakuú nepocitame profit zatim")
|
||||||
|
|
||||||
@ -271,8 +279,10 @@ class StrategyClassicSL(Strategy):
|
|||||||
trade.rel_profit = rel_profit
|
trade.rel_profit = rel_profit
|
||||||
trade.rel_profit_cum = rel_profit_cum_calculated
|
trade.rel_profit_cum = rel_profit_cum_calculated
|
||||||
signal_name = trade.generated_by
|
signal_name = trade.generated_by
|
||||||
if data.event == TradeEvent.FILL:
|
#Pokud FILL uzaviral celou pozici - uzavreme prescribed trade
|
||||||
trade.status == TradeStatus.CLOSED
|
if data.event == TradeEvent.FILL and data.position_qty == 0:
|
||||||
|
trade.status = TradeStatus.CLOSED
|
||||||
|
trade.exit_time = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
|
||||||
break
|
break
|
||||||
|
|
||||||
if data.event == TradeEvent.FILL:
|
if data.event == TradeEvent.FILL:
|
||||||
@ -285,6 +295,7 @@ class StrategyClassicSL(Strategy):
|
|||||||
setattr(tradeData, "profit", trade_profit)
|
setattr(tradeData, "profit", trade_profit)
|
||||||
setattr(tradeData, "profit_sum", self.state.profit)
|
setattr(tradeData, "profit_sum", self.state.profit)
|
||||||
setattr(tradeData, "signal_name", signal_name)
|
setattr(tradeData, "signal_name", signal_name)
|
||||||
|
setattr(tradeData, "prescribed_trade_id", self.state.vars.pending)
|
||||||
#self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}")
|
#self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}")
|
||||||
setattr(tradeData, "rel_profit", rel_profit)
|
setattr(tradeData, "rel_profit", rel_profit)
|
||||||
setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated)
|
setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated)
|
||||||
@ -305,11 +316,15 @@ class StrategyClassicSL(Strategy):
|
|||||||
for trade in self.state.vars.prescribedTrades:
|
for trade in self.state.vars.prescribedTrades:
|
||||||
if trade.id == self.state.vars.pending:
|
if trade.id == self.state.vars.pending:
|
||||||
signal_name = trade.generated_by
|
signal_name = trade.generated_by
|
||||||
|
#zapiseme entry_time (jen pokud to neni partial add) - tzn. jen poprvé
|
||||||
|
if data.event == TradeEvent.FILL and trade.entry_time is None:
|
||||||
|
trade.entry_time = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
|
||||||
|
|
||||||
#zapsat update profitu do tradeList
|
#zapsat update profitu do tradeList
|
||||||
for tradeData in self.state.tradeList:
|
for tradeData in self.state.tradeList:
|
||||||
if tradeData.execution_id == data.execution_id:
|
if tradeData.execution_id == data.execution_id:
|
||||||
setattr(tradeData, "signal_name", signal_name)
|
setattr(tradeData, "signal_name", signal_name)
|
||||||
|
setattr(tradeData, "prescribed_trade_id", self.state.vars.pending)
|
||||||
|
|
||||||
self.state.ilog(e="SELL: Jde o SHORT nepocitame profit zatim")
|
self.state.ilog(e="SELL: Jde o SHORT nepocitame profit zatim")
|
||||||
|
|
||||||
|
|||||||
@ -65,6 +65,7 @@ class Strategy:
|
|||||||
self.ilog_save = ilog_save
|
self.ilog_save = ilog_save
|
||||||
self.secondary_res_start_time = dict()
|
self.secondary_res_start_time = dict()
|
||||||
self.secondary_res_start_index = dict()
|
self.secondary_res_start_index = dict()
|
||||||
|
self.last_index = -1
|
||||||
|
|
||||||
#TODO predelat na dynamické queues
|
#TODO predelat na dynamické queues
|
||||||
self.q1 = queue.Queue()
|
self.q1 = queue.Queue()
|
||||||
@ -156,9 +157,11 @@ class Strategy:
|
|||||||
#tzn. v NEXT dealame u indikatoru vzdy pouze UPDATE
|
#tzn. v NEXT dealame u indikatoru vzdy pouze UPDATE
|
||||||
|
|
||||||
def save_item_history(self,item):
|
def save_item_history(self,item):
|
||||||
|
""""
|
||||||
|
Logika obsahujici ukladani baru a indikatoru(standardnich a cbar) do historie a inicializace novych zaznamu
|
||||||
|
"""
|
||||||
if self.rectype == RecordType.BAR:
|
if self.rectype == RecordType.BAR:
|
||||||
#jako cas indikatorů pridavame cas baru a inicialni hodnoty vsech indikatoru
|
#jako cas indikatorů pridavame cas baru a inicialni hodnoty vsech indikatoru
|
||||||
|
|
||||||
for key in self.state.indicators:
|
for key in self.state.indicators:
|
||||||
if key == 'time':
|
if key == 'time':
|
||||||
self.state.indicators['time'].append(item['updated'])
|
self.state.indicators['time'].append(item['updated'])
|
||||||
@ -170,7 +173,7 @@ class Strategy:
|
|||||||
#implementovat az podle skutecnych pozadavku
|
#implementovat az podle skutecnych pozadavku
|
||||||
#self.state.indicators['time'].append(datetime.fromtimestamp(self.state.last_trade_time))
|
#self.state.indicators['time'].append(datetime.fromtimestamp(self.state.last_trade_time))
|
||||||
#self.append_trade(self.state.trades,item)
|
#self.append_trade(self.state.trades,item)
|
||||||
elif self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME):
|
elif self.rectype in (RecordType.CBAR):
|
||||||
if self.nextnew:
|
if self.nextnew:
|
||||||
#standardni identifikatory - populace hist zaznamu pouze v novem baru (dale se deji jen udpaty)
|
#standardni identifikatory - populace hist zaznamu pouze v novem baru (dale se deji jen udpaty)
|
||||||
for key in self.state.indicators:
|
for key in self.state.indicators:
|
||||||
@ -220,7 +223,40 @@ class Strategy:
|
|||||||
#zatim jedno, predelat pak na list
|
#zatim jedno, predelat pak na list
|
||||||
# if safe_get(self.state.vars, "secondary_resolution",None):
|
# if safe_get(self.state.vars, "secondary_resolution",None):
|
||||||
# self.process_secondary_indicators(item)
|
# self.process_secondary_indicators(item)
|
||||||
|
elif self.rectype in (RecordType.CBARVOLUME, RecordType.CBARRENKO):
|
||||||
|
|
||||||
|
#u cbarvolume muze prijit i samostatny confirm nesouci data, tzn. chytame se na INDEX (tzn. jestli prisel udpate nebo novy)
|
||||||
|
#NEW
|
||||||
|
if item['index'] != self.last_index:
|
||||||
|
#standardni identifikatory - populace hist zaznamu pouze v novem baru (dale se deji jen udpaty)
|
||||||
|
for key in self.state.indicators:
|
||||||
|
if key == 'time':
|
||||||
|
self.state.indicators['time'].append(item['time'])
|
||||||
|
else:
|
||||||
|
self.state.indicators[key].append(0)
|
||||||
|
|
||||||
|
#populujeme i novy bar v historii
|
||||||
|
self.append_bar(self.state.bars,item)
|
||||||
|
#UPDATE
|
||||||
|
else:
|
||||||
|
#bary updatujeme, pridavame jen prvni
|
||||||
|
self.replace_prev_bar(self.state.bars,item)
|
||||||
|
|
||||||
|
#UPD
|
||||||
|
#tady mozna u standardnich(barovych) identifikatoru updatnout cas na "updated" - aby nebyl
|
||||||
|
#stale zarovnan s casem baru
|
||||||
|
for key in self.state.indicators:
|
||||||
|
if key == 'time':
|
||||||
|
self.state.indicators['time'][-1] = item['updated']
|
||||||
|
|
||||||
|
#cbar indikatory populace v kazde iteraci
|
||||||
|
for key in self.state.cbar_indicators:
|
||||||
|
if key == 'time':
|
||||||
|
self.state.cbar_indicators['time'].append(item['updated'])
|
||||||
|
else:
|
||||||
|
self.state.cbar_indicators[key].append(0)
|
||||||
|
|
||||||
|
self.last_index = item['index']
|
||||||
|
|
||||||
# #tady jsem skoncil
|
# #tady jsem skoncil
|
||||||
# def process_secondary_indicators(self, item):
|
# def process_secondary_indicators(self, item):
|
||||||
@ -275,14 +311,14 @@ class Strategy:
|
|||||||
a,p = self.interface.pos()
|
a,p = self.interface.pos()
|
||||||
if a != -1:
|
if a != -1:
|
||||||
self.state.avgp, self.state.positions = a,p
|
self.state.avgp, self.state.positions = a,p
|
||||||
elif self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME) and item['confirmed'] == 1:
|
elif self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME, RecordType.CBARRENKO) and item['confirmed'] == 1:
|
||||||
a,p = self.interface.pos()
|
a,p = self.interface.pos()
|
||||||
if a != -1:
|
if a != -1:
|
||||||
self.state.avgp, self.state.positions = a,p
|
self.state.avgp, self.state.positions = a,p
|
||||||
|
|
||||||
"""update state.last_trade_time a time of iteration"""
|
"""update state.last_trade_time a time of iteration"""
|
||||||
def update_times(self, item):
|
def update_times(self, item):
|
||||||
if self.rectype == RecordType.BAR or self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME):
|
if self.rectype == RecordType.BAR or self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME, RecordType.CBARRENKO):
|
||||||
self.state.last_trade_time = item['updated']
|
self.state.last_trade_time = item['updated']
|
||||||
elif self.rectype == RecordType.TRADE:
|
elif self.rectype == RecordType.TRADE:
|
||||||
self.state.last_trade_time = item['t']
|
self.state.last_trade_time = item['t']
|
||||||
@ -524,7 +560,7 @@ class Strategy:
|
|||||||
if self.rtqueue is not None:
|
if self.rtqueue is not None:
|
||||||
rt_out = dict()
|
rt_out = dict()
|
||||||
|
|
||||||
if self.rectype == RecordType.BAR or self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME):
|
if self.rectype == RecordType.BAR or self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME, RecordType.CBARRENKO):
|
||||||
rt_out["bars"] = item
|
rt_out["bars"] = item
|
||||||
else:
|
else:
|
||||||
rt_out["trades"] = item
|
rt_out["trades"] = item
|
||||||
|
|||||||
@ -5,7 +5,7 @@ def populate_cbar_tick_price_indicator(data, state: StrategyState):
|
|||||||
conf_bar = data['confirmed']
|
conf_bar = data['confirmed']
|
||||||
|
|
||||||
#specifická sekce pro CBARVOLUME, kde vzdy máme nova data v confirmation baru (tzn. tickprice pocitame jak pri potvrzenem tak nepotvrzenem)
|
#specifická sekce pro CBARVOLUME, kde vzdy máme nova data v confirmation baru (tzn. tickprice pocitame jak pri potvrzenem tak nepotvrzenem)
|
||||||
if state.rectype == RecordType.CBARVOLUME:
|
if state.rectype in (RecordType.CBARVOLUME, RecordType.CBARRENKO):
|
||||||
try:
|
try:
|
||||||
tick_price = data['close']
|
tick_price = data['close']
|
||||||
tick_delta_volume = data['volume'] - state.vars.last_tick_volume
|
tick_delta_volume = data['volume'] - state.vars.last_tick_volume
|
||||||
|
|||||||
@ -62,10 +62,10 @@ def execute_prescribed_trades(state: StrategyState, data):
|
|||||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }")
|
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }")
|
||||||
elif isinstance(sl_defvalue, str):
|
elif isinstance(sl_defvalue, str):
|
||||||
#from indicator
|
#from indicator
|
||||||
ind = sl_defvalue_abs
|
ind = sl_defvalue
|
||||||
sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue))
|
sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue))
|
||||||
if sl_defvalue_abs >= float(data['close']):
|
if sl_defvalue_abs >= float(data['close']):
|
||||||
raise Exception(f"error in stoploss {sl_defvalue_abs} >= curr price")
|
raise Exception(f"error in stoploss {ind} {sl_defvalue_abs} >= curr price")
|
||||||
state.vars.activeTrade.stoploss_value = sl_defvalue_abs
|
state.vars.activeTrade.stoploss_value = sl_defvalue_abs
|
||||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}")
|
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}")
|
||||||
insert_SL_history(state)
|
insert_SL_history(state)
|
||||||
@ -92,10 +92,10 @@ def execute_prescribed_trades(state: StrategyState, data):
|
|||||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }")
|
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }")
|
||||||
elif isinstance(sl_defvalue, str):
|
elif isinstance(sl_defvalue, str):
|
||||||
#from indicator
|
#from indicator
|
||||||
ind = sl_defvalue_abs
|
ind = sl_defvalue
|
||||||
sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue))
|
sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue))
|
||||||
if sl_defvalue_abs <= float(data['close']):
|
if sl_defvalue_abs <= float(data['close']):
|
||||||
raise Exception(f"error in stoploss {sl_defvalue_abs} <= curr price")
|
raise Exception(f"error in stoploss {ind} {sl_defvalue_abs} <= curr price")
|
||||||
state.vars.activeTrade.stoploss_value = sl_defvalue_abs
|
state.vars.activeTrade.stoploss_value = sl_defvalue_abs
|
||||||
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}")
|
state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}")
|
||||||
insert_SL_history(state)
|
insert_SL_history(state)
|
||||||
|
|||||||
Reference in New Issue
Block a user