refactor RSI SELL

This commit is contained in:
David Brazda
2023-05-25 20:17:56 +02:00
parent 8cf2956720
commit bf7c1773bd
16 changed files with 736 additions and 231 deletions

View File

@ -5,7 +5,7 @@ from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import Strat
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide, OrderType
from v2realbot.indicators.indicators import ema
from v2realbot.indicators.oscillators import rsi
from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, get_tick
from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, get_tick, round2five
from datetime import datetime
#from icecream import install, ic
#from rich import print
@ -175,6 +175,10 @@ def next(data, state: StrategyState):
akt_pozic = int(state.positions)
max_pozic = int(state.vars.maxpozic)
if akt_pozic >= max_pozic:
state.ilog(e="MAX pozic reached, cannot vyklad")
return
#mame polovinu a vic vylozeno, pouzivame defenzicni krivku
if is_defensive_mode():
state.ilog(e="DEF: Pouzivame defenzivni krivku", akt_pozic=akt_pozic, max_pozic=max_pozic, curve_def=curve_def)
@ -243,31 +247,38 @@ def next(data, state: StrategyState):
#na urovni CBARU mame zajisteno, ze update prichazi pri zmene ceny
#v kazde iteraci testujeme sell
#pri confirmed tesutjeme i buy
#pri potvrzenem baru muzeme provest kroky per hlavni BAR
#potvrzeni neprinasi nikdy zadna nova data, ale pouze potvrzeni.
state.ilog(e="-----")
eval_sell()
conf_bar = data['confirmed']
#for CBAR TICK and VOLUME change info
#price change vs Volume
tick_price = data['close']
tick_volume = data['volume'] - state.vars.last_tick_volume
#pouze potvrzovací BAR CBARu, mozna id confirmed = 1, pak ignorovat
if tick_volume == 0:
if conf_bar == 1:
#delej veci per standardni bar
state.ilog(e="BAR potvrzeny")
else:
pass
#delej veci tick-based
##naplneni indikatoru vnitrniho CBAR tick price
##pozor CBAR identifikatory jsou ukladane do historie az pro konfirmnuty bar
try:
state.indicators.tick_price[-1] = tick_price
state.indicators.tick_volume[-1] = tick_volume
except:
pass
#CBAR INDICATOR pro tick price a deltu VOLUME
tick_price = round2five(data['close'])
tick_delta_volume = data['volume'] - state.vars.last_tick_volume
if conf_bar == 0:
try:
#pokud v potvrzovacím baru nebyly zmeny, nechavam puvodni hodnoty
# if tick_delta_volume == 0:
# state.indicators.tick_price[-1] = state.indicators.tick_price[-2]
# state.indicators.tick_volume[-1] = state.indicators.tick_volume[-2]
# else:
state.ilog(e=f"TICK PRICE {tick_price} VOLUME {tick_volume} {conf_bar=}", last_price=state.vars.last_tick_price, last_volume=state.vars.last_tick_volume)
#docasne dame pryc volume deltu a davame absolutni cislo
state.cbar_indicators.tick_price[-1] = tick_price
state.cbar_indicators.tick_volume[-1] = tick_delta_volume
except:
pass
state.ilog(e=f"TICK PRICE {tick_price} VOLUME {tick_delta_volume} {conf_bar=}", last_price=state.vars.last_tick_price, last_volume=state.vars.last_tick_volume)
state.vars.last_tick_price = tick_price
state.vars.last_tick_volume = data['volume']
@ -278,6 +289,18 @@ def next(data, state: StrategyState):
#TEST BUY SIGNALu z cbartick_price - 3klesave za sebou
buy_tp = isfalling(state.cbar_indicators.tick_price,state.vars.Trend)
state.ilog(e=f"TICK SIGNAL ISFALLING {buy_tp}", last_tp=state.cbar_indicators.tick_price[-6:], trend=state.vars.Trend)
#IVWAP - PRUBEZNY persistovany VWAP
# try:
# #naplneni cbar tick indikatoru s prubeznou vwap
# state.cbar_indicators.ivwap[-1]=data['vwap']
# except:
# pass
# if data['confirmed'] == 0:
# state.ilog(e="CBAR unconfirmed - returned", msg=str(data))
# #TBD zde muzeme i nakupovat
@ -287,7 +310,7 @@ def next(data, state: StrategyState):
# else:
# state.ilog(e="CBAR confirmed - continue", msg=str(data))
#EMA INDICATOR -
#BAR EMA INDICATOR -
#plnime MAcko - nyni posilame jen N poslednich hodnot
#zaroven osetrujeme pripady, kdy je malo dat a ukladame nulu
try:
@ -296,27 +319,31 @@ def next(data, state: StrategyState):
source = state.bars.close[-ma:] #state.bars.vwap
ema_value = ema(source, ma)
state.indicators.ema[-1]=trunc(ema_value[-1],3)
state.ilog(e=f"EMA {state.indicators.ema[-1]}", ema_last=state.indicators.ema[-6:])
except Exception as e:
state.ilog(e="EMA nechavame 0", message=str(e)+format_exc())
#state.indicators.ema[-1]=(0)
#RSI14 INDICATOR
#CBAR RSI14 INDICATOR
try:
##mame v atributech nastaveni?
rsi_dont_buy_above = safe_get(state.vars, "rsi_dont_buy_above",50)
rsi_buy_signal_conf = safe_get(state.vars, "rsi_buy_signal_below",40)
rsi_buy_signal = False
rsi_dont_buy = False
rsi_length = 2
source = state.bars.close #[-rsi_length:] #state.bars.vwap
rsi_length = 14
#source = state.bars.close #[-rsi_length:] #state.bars.vwap
#jako zdroj je prubezna CBAR tickprice
source = state.cbar_indicators.tick_price
rsi_res = rsi(source, rsi_length)
rsi_value = trunc(rsi_res[-1],3)
state.indicators.RSI14[-1]=rsi_value
state.cbar_indicators.RSI14[-1]=rsi_value
rsi_dont_buy = rsi_value > rsi_dont_buy_above
rsi_buy_signal = rsi_value < rsi_buy_signal_conf
state.ilog(e=f"RSI{rsi_value} {rsi_length=} {rsi_dont_buy=} {rsi_buy_signal=}", rsi_indicator=state.indicators.RSI14[-5:])
state.ilog(e=f"CBARRSI{rsi_value} {rsi_length=} {rsi_dont_buy=} {rsi_buy_signal=}", rsi_indicator=state.cbar_indicators.RSI14[-5:])
except Exception as e:
state.ilog(e=f"RSI {rsi_length=} nechavame 0", message=str(e)+format_exc())
state.ilog(e=f"CBARRSI {rsi_length=} nechavame 0", message=str(e)+format_exc())
#state.indicators.RSI14.append(0)
@ -417,9 +444,8 @@ def next(data, state: StrategyState):
#TODO: zvazit jestli nechat i pri otevrenych pozicich, zatim nechavame
#TODO int(int(state.oa.poz)/int(state.variables.chunk)) > X
#TODO predelat mechanismus ticků (zrelativizovat), aby byl pouzitelny na tituly s ruznou cenou
#TODO spoustet 1x za X iteraci nebo cas
if state.vars.jevylozeno == 1:
if state.vars.jevylozeno == 1 and len(state.vars.pendingbuys)>0:
#pokud mame vylozeno a cena je vetsi nez tick2reset
if len(state.vars.pendingbuys)>0:
maxprice = max(state.vars.pendingbuys.values())
@ -443,34 +469,6 @@ def next(data, state: StrategyState):
state.vars.jevylozeno = 0
state.ilog(e="PB se vyklepaly nastavujeme: neni vylozeno", jevylozeno=state.vars.jevylozeno)
#TODO toto dodelat konzolidaci a mozna lock na limitku a pendingbuys a jevylozeno ??
#kdykoliv se muze notifikace ztratit
# - pendingbuys - vsechny open orders buy
# - limitka - open order sell
#pokud je vylozeno a mame pozice a neexistuje limitka - pak ji vytvorim
# if int(state.oe.poz)>0 and state.oe.limitka == 0:
# #pro jistotu updatujeme pozice
# state.oe.avgp, state.oe.poz = state.oe.pos()
# if int(state.oe.poz) > 0:
# cena = round(float(state.oe.avgp) + float(state.oe.stratvars["profit"]),2)
# print("BUGF: limitka neni vytvarime, a to za cenu",cena,"mnozstvi",state.oe.poz)
# print("aktuzalni ltp",ltp.price[state.oe.symbol])
# try:
# state.oe.limitka = state.oe.sell_noasync(cena, state.oe.poz)
# print("vytvorena limitka", state.oe.limitka)
# except Exception as e:
# print("Neslo vytvorit profitku. Problem,ale jedeme dal",str(e))
# pass
# ##raise Exception(e)
print(10*"*","NEXT STOP",10*"*")
def init(state: StrategyState):
@ -481,12 +479,13 @@ def init(state: StrategyState):
state.vars.last_tick_price = 0
state.vars.last_tick_volume = 0
state.vars.next_new = 0
state.indicators['tick_price'] = []
state.indicators['tick_volume'] = []
#state.cbar_indicators['ivwap'] = []
state.cbar_indicators['tick_price'] = []
state.cbar_indicators['tick_volume'] = []
state.indicators['ema'] = []
state.indicators['slope'] = []
state.indicators['slopeMA'] = []
state.indicators['RSI14'] = []
state.cbar_indicators['RSI14'] = []
#static indicators - those not series based
state.statinds['angle'] = dict(minimum_slope=state.vars["minimum_slope"])
state.vars["ticks2reset_backup"] = state.vars.ticks2reset

View File

@ -194,7 +194,7 @@ class RunArchiveDetail(BaseModel):
name: str
bars: dict
#trades: Optional[dict]
indicators: dict
indicators: List[dict]
statinds: dict
trades: List[TradeUpdate]

View File

@ -6,7 +6,7 @@ from appdirs import user_data_dir
NORMALIZED_TICK_BASE_PRICE = 30.00
LOG_RUNNER_EVENTS = False
#no print in console
QUIET_MODE = True
QUIET_MODE = False
#how many consecutive trades with the fill price are necessary for LIMIT fill to happen in backtesting
#0 - optimistic, every knot high will fill the order
#N - N consecutive trades required

View File

@ -458,6 +458,8 @@ def archive_runner(runner: Runner, strat: StrategyInstance):
#flatten indicators from numpy array
flattened_indicators = {}
#pole indicatoru, kazdy ma svoji casovou osu time
flattened_indicators_list = []
for key, value in strat.state.indicators.items():
if isinstance(value, ndarray):
#print("is numpy", key,value)
@ -466,11 +468,22 @@ def archive_runner(runner: Runner, strat: StrategyInstance):
else:
#print("is not numpy", key, value)
flattened_indicators[key]= value
flattened_indicators_list.append(flattened_indicators)
flattened_indicators = {}
for key, value in strat.state.cbar_indicators.items():
if isinstance(value, ndarray):
#print("is numpy", key,value)
flattened_indicators[key]= value.tolist()
#print("changed numpy:",value.tolist())
else:
#print("is not numpy", key, value)
flattened_indicators[key]= value
flattened_indicators_list.append(flattened_indicators)
runArchiveDetail: RunArchiveDetail = RunArchiveDetail(id = runner.id,
name=runner.run_name,
bars=strat.state.bars,
indicators=flattened_indicators,
indicators=flattened_indicators_list,
statinds=strat.state.statinds,
trades=strat.state.tradeList)
resh = db_arch_h.insert(runArchive.__dict__)

View File

@ -23,6 +23,8 @@ class TradeAggregator:
mintick: int = 0,
exthours: bool = False):
"""
UPDATED VERSION - vrací více záznamů
Create trade agregator. Instance accepts trades one by one and process them and returns output type
Trade - return trade one by one (no change)
Bar - return finished bar in given timeframe
@ -62,6 +64,8 @@ class TradeAggregator:
#instance variable to hold last trade price
self.last_price = 0
self.barindex = 1
self.diff_price = True
self.preconfBar = {}
async def ingest_trade(self, indata, symbol):
"""
@ -72,7 +76,7 @@ class TradeAggregator:
data = unpackb(indata)
#last item signal
if data == "last": return data
if data == "last": return [data]
#print(data)
##implementing fitlers - zatim natvrdo a jen tyto: size: 1, cond in [O,C,4] opening,closed a derivately priced,
@ -82,15 +86,15 @@ class TradeAggregator:
## přidán W - average price trade, U - Extended hours - sold out of sequence
try:
for i in data['c']:
if i in ('C','O','4','B','7','V','P','W','U'): return 0
if i in ('C','O','4','B','7','V','P','W','U'): return []
except KeyError:
pass
#EXPERIMENT zkusime vyhodit vsechny pod 50 #puv if int(data['s']) == 1: return 0
#EXPERIMENT zkusime vyhodit vsechny pod 50 #puv if int(data['s']) == 1: return []
#zatim nechavame - výsledek je naprosto stejný jako v tradingview
if int(data['s']) < self.minsize: return 0
if int(data['s']) < self.minsize: return []
#{'t': 1678982075.242897, 'x': 'D', 'p': 29.1333, 's': 18000, 'c': [' ', '7', 'V'], 'i': 79372107591749, 'z': 'A', 'u': 'incorrect'}
if 'u' in data: return 0
if 'u' in data: return []
#pokud projde TRADE s cenou 0.33% rozdilna oproti predchozi, pak vyhazujeme v ramci cisteni dat (cca 10ticku na 30USD)
pct_off = 0.33
@ -106,7 +110,7 @@ class TradeAggregator:
if float(data['p']) > float(ltp.price[symbol]) + (float(data['p'])/100*pct_off) or float(data['p']) < float(ltp.price[symbol])-(float(data['p'])/100*pct_off):
print("ZLO", data,ltp.price[symbol])
#nechavame zlo zatim projit
##return 0
##return []
# with open("cache/wrongtrades.txt", 'a') as fp:
# fp.write(str(data) + 'predchozi:'+str(ltp.price[symbol])+'\n')
@ -128,7 +132,7 @@ class TradeAggregator:
if not is_open_hours(datetime.fromtimestamp(data['t'])) and self.exthours is False:
#print("AGG: trade not in open hours skipping", datetime.fromtimestamp(data['t']).astimezone(zoneNY))
return 0
return []
#tady bude vzdycky posledni cena a posledni cas
if self.update_ltp:
@ -137,7 +141,7 @@ class TradeAggregator:
#if data['p'] < self.last_price - 0.02: print("zlo:",data)
if self.rectype == RecordType.TRADE: return data
if self.rectype == RecordType.TRADE: return [data]
#print("agr přišel trade", datetime.fromtimestamp(data['t']),data)
@ -167,9 +171,45 @@ class TradeAggregator:
else:
self.newBar['confirmed'] = 1
self.newBar['vwap'] = self.vwaphelper / self.newBar['volume']
#updatujeme čas - obsahuje datum tradu, který confirm triggeroval
self.newBar['updated'] = data['t']
#HACK pro update casu, který confirm triggeroval
#u CBARu v confirmnutem muze byt
# 1) no trades (pak potvrzujeme predchozi)
# 2) trades with same price , ktere zaroven timto flushujeme (v tomto pripade je cas updatu cas predchoziho tradu)
# variantu vyse pozname podle nastavene self.diff_price = True (mame trady a i ulozeny cas)
if self.rectype == RecordType.CBAR:
#UPDATE ať confirmace nenese zadna data, vsechny zmenena data jsou vyflusnute predtim
#pokud byly nejake trady
if self.diff_price is False:
#self.newBar['updated'] = self.lasttimestamp
#TODO tady bychom nejdriv vyflushnuly nekonfirmovany bar s trady
#a nasladne poslali prazdny confirmacni bar
self.preconfBar = deepcopy(self.newBar)
self.preconfBar['updated'] = self.lasttimestamp
self.preconfBar['confirmed'] = 0
#pridat do promenne
#else:
#NASTY HACK pro GUI
#zkousime potvrzeni baru dat o chlup mensi cas nez cas noveho baru, ktery jde hned za nim
#gui neumi zobrazit duplicity a v RT grafu nejde upravovat zpetne
#zarovname na cas baru podle timeframu(např. 5, 10, 15 ...) (ROUND)
if self.align:
t = datetime.fromtimestamp(data['t'])
t = t - timedelta(seconds=t.second % self.timeframe,microseconds=t.microsecond)
#nebo pouzijeme datum tradu zaokrouhlene na vteriny (RANDOM)
else:
#ulozime si jeho timestamp (odtum pocitame timeframe)
t = datetime.fromtimestamp(int(data['t']))
#self.newBar['updated'] = float(data['t']) - 0.001
self.newBar['updated'] = datetime.timestamp(t) - 0.000001
#PRO standardní BAR nechavame puvodni
else:
self.newBar['updated'] = data['t']
#ulozime datum akt.tradu pro mintick
self.lastBarConfirmed = True
#ukládám si předchozí (confirmed)bar k vrácení
@ -199,9 +239,9 @@ class TradeAggregator:
#je cena stejna od predchoziho tradu? pro nepotvrzeny cbar vracime jen pri zmene ceny
if self.last_price == data['p']:
diff_price = False
self.diff_price = False
else:
diff_price = True
self.diff_price = True
self.last_price = data['p']
#spočteme vwap - potřebujeme předchozí hodnoty
@ -216,6 +256,7 @@ class TradeAggregator:
self.newBar['hlcc4'] = round((self.newBar['high']+self.newBar['low']+self.newBar['close']+self.newBar['close'])/4,3)
#predchozi bar byl v jine vterine, tzn. ukladame do noveho (aktualniho) pocatecni hodnoty
#NEW BAR POPULATION
if (issamebar == False):
#zaciname novy bar
@ -249,14 +290,30 @@ class TradeAggregator:
#je tu maly bug pro CBAR - kdy prvni trade, který potvrzuje predchozi bar
#odesle potvrzeni predchoziho baru a nikoliv open stávajícího, ten posle až druhý trade
#což asi nevadí
#OPRAVENO
#pokud je pripraveny, vracíme předchozí confirmed bar
#pokud je pripraveny, vracíme předchozí confirmed bar PLUS NOVY, který ho triggeroval. pokud bylo
# pred confirmem nejake trady beze zmeny ceny flushujeme je take (preconfBar)
#predchozi bar muze obsahovat zmenena data
if len(self.returnBar) > 0:
self.tmp = self.returnBar
self.returnBar = {}
#print(self.tmp)
return self.tmp
return_set = []
#pridame preconfirm bar pokud je
if len(self.preconfBar)>0:
return_set.append(self.preconfBar)
self.preconfBar = {}
#pridame confirmation bar
return_set.append(self.returnBar)
#self.tmp = self.returnBar
self.returnBar = []
#doplnime prubezny vwap
self.newBar['vwap'] = self.vwaphelper / self.newBar['volume']
return_set.append(self.newBar)
#TODO pridat sem podporu pro mintick jako nize, tzn. pokud je v ochrannem okne, tak novy bar nevracet
#zatim je novy bar odesilan nehlede na mintick
#return_set = [self.tmp, self.newBar]
return return_set
#pro cont bar posilame ihned (TBD vwap a min bar tick value)
if self.rectype == RecordType.CBAR:
@ -267,7 +324,7 @@ class TradeAggregator:
#pocatek noveho baru + Xs musi byt vetsi nez aktualni trade
if (self.newBar['time'] + timedelta(seconds=self.mintick)) > datetime.fromtimestamp(data['t']):
#print("waiting for mintick")
return 0
return []
else:
self.lastBarConfirmed = False
@ -276,12 +333,12 @@ class TradeAggregator:
#print(self.newBar)
#pro (nepotvrzeny) cbar vracime jen pri zmene ceny
if diff_price is True:
return self.newBar
if self.diff_price is True:
return [self.newBar]
else:
return 0
return []
else:
return 0
return []
class TradeAggregator2Queue(TradeAggregator):
@ -297,15 +354,17 @@ class TradeAggregator2Queue(TradeAggregator):
async def ingest_trade(self, data):
#print("ingest ve threadu:",current_thread().name)
res = await super().ingest_trade(data, self.symbol)
if res != 0:
#if len(res) > 0:
for obj in res:
#print(res)
#pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií
if isinstance(res, dict):
copy = res.copy()
if isinstance(obj, dict):
copy = obj.copy()
else:
copy = res
copy = obj
self.queue.put(copy)
res = {}
res = []
#print("po insertu",res)
class TradeAggregator2List(TradeAggregator):
@ -324,17 +383,17 @@ class TradeAggregator2List(TradeAggregator):
#print("ted vstoupil do tradeagg2list ingestu")
res1 = await super().ingest_trade(data, self.symbol)
#print("ted je po zpracovani", res1)
if res1 != 0:
for obj in res1:
#pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií
if isinstance(res1, dict):
copy = res1.copy()
if isinstance(obj, dict):
copy = obj.copy()
else:
copy = res1
if res1 == 'last': return 0
copy = obj
if obj == 'last': return []
self.btdata.append((copy['t'],copy['p']))
# with open(self.debugfile, "a") as output:
# output.write(str(copy['t']) + ' ' + str(datetime.fromtimestamp(copy['t']).astimezone(zoneNY)) + ' ' + str(copy['p']) + '\n')
res1 = {}
res1 = []
#print("po insertu",res)

View File

@ -0,0 +1,341 @@
"""
Aggregator mdoule containing main aggregator logic for TRADES, BARS and CBAR
"""
from v2realbot.enums.enums import RecordType, StartBarAlign
from datetime import datetime, timedelta
from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, Queue,is_open_hours,zoneNY
from queue import Queue
from rich import print
from v2realbot.enums.enums import Mode
import threading
from copy import deepcopy
from msgpack import unpackb
import os
from config import DATA_DIR
class TradeAggregator:
def __init__(self,
rectype: RecordType = RecordType.BAR,
timeframe: int = 5,
minsize: int = 100,
update_ltp: bool = False,
align: StartBarAlign = StartBarAlign.ROUND,
mintick: int = 0,
exthours: bool = False):
"""
Create trade agregator. Instance accepts trades one by one and process them and returns output type
Trade - return trade one by one (no change)
Bar - return finished bar in given timeframe
CBar - returns continuous bar, finished bar is marked by confirmed status
Args:
timeframe (number): Resolution of bar in seconds
update_ltp (bool): Whether to update global variable with price (usually only one instance does that)
align: Defines alignement of first bar. ROUND - according to timeframe( 5,10,15 - for 5s timeframe), RANDOM - according to timestamp of first trade
mintick: Applies for CBAR. Minimální mezera po potvrzeni baru a aktualizaci dalsiho nepotvrzeneho (např. pro 15s, muzeme chtit prvni tick po 5s). po teto dobe realtime.
"""
self.rectype: RecordType = rectype
self.timeframe = timeframe
self.minsize = minsize
self.update_ltp = update_ltp
self.exthours = exthours
if mintick >= timeframe:
print("Mintick musi byt mensi nez timeframe")
raise Exception
self.mintick = mintick
#class variables = starters
self.iterace = 1
self.lasttimestamp = 0
#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.bar_start = 0
self.align = align
self.tm: datetime = None
self.firstpass = True
self.vwaphelper = 0
self.returnBar = {}
self.lastBarConfirmed = False
#min trade size
self.minsize = minsize
#instance variable to hold last trade price
self.last_price = 0
self.barindex = 1
async def ingest_trade(self, indata, symbol):
"""
Aggregator logic for trade record
Args:
indata (dict): online or offline record
"""
data = unpackb(indata)
#last item signal
if data == "last": return data
#print(data)
##implementing fitlers - zatim natvrdo a jen tyto: size: 1, cond in [O,C,4] opening,closed a derivately priced,
## 22.3. - dal jsem pryc i contingency trades [' ', '7', 'V'] - nasel jsem obchod o 30c mimo
## dán pryč P - prior reference time + 25centu mimo, {'t': '2023-04-12T19:45:08.63257344Z', 'x': 'D', 'p': 28.68, 's': 1000, 'c': [' ', 'P'], 'i': 71693108525109, 'z': 'A'},
## Q - jsou v pohode, oteviraci trady, ale O jsou jejich duplikaty
## přidán W - average price trade, U - Extended hours - sold out of sequence
try:
for i in data['c']:
if i in ('C','O','4','B','7','V','P','W','U'): return 0
except KeyError:
pass
#EXPERIMENT zkusime vyhodit vsechny pod 50 #puv if int(data['s']) == 1: return 0
#zatim nechavame - výsledek je naprosto stejný jako v tradingview
if int(data['s']) < self.minsize: return 0
#{'t': 1678982075.242897, 'x': 'D', 'p': 29.1333, 's': 18000, 'c': [' ', '7', 'V'], 'i': 79372107591749, 'z': 'A', 'u': 'incorrect'}
if 'u' in data: return 0
#pokud projde TRADE s cenou 0.33% rozdilna oproti predchozi, pak vyhazujeme v ramci cisteni dat (cca 10ticku na 30USD)
pct_off = 0.33
##ic(ltp.price)
##ic(ltp.price[symbol])
try:
ltp.price[symbol]
except KeyError:
ltp.price[symbol]=data['p']
if float(data['p']) > float(ltp.price[symbol]) + (float(data['p'])/100*pct_off) or float(data['p']) < float(ltp.price[symbol])-(float(data['p'])/100*pct_off):
print("ZLO", data,ltp.price[symbol])
#nechavame zlo zatim projit
##return 0
# with open("cache/wrongtrades.txt", 'a') as fp:
# fp.write(str(data) + 'predchozi:'+str(ltp.price[symbol])+'\n')
#timestampy jsou v UTC
#TIMESTAMP format is different for online and offline trade streams
#offline trade
#{'t': '2023-02-17T14:30:00.16111744Z', 'x': 'J', 'p': 35.14, 's': 20, 'c': [' ', 'F', 'I'], 'i': 52983525027938, 'z': 'A'}
#websocket trade
#{'T': 't', 'S': 'MSFT', 'i': 372, 'x': 'V', 'p': 264.58, 's': 25, 'c': ['@', 'I'], 'z': 'C', 't': Timestamp(seconds=1678973696, nanoseconds=67312449), 'r': Timestamp(seconds=1678973696, nanoseconds=72865209)}
#parse alpaca timestamp
# tzn. na offline mohu pouzit >>> datetime.fromisoformat(d).timestamp() 1676644200.161117
#orizne sice nanosekundy ale to nevadi
#print("tady", self.mode, data['t'])
# if self.mode == Mode.BT:
# data['t'] = datetime.fromisoformat(str(data['t'])).timestamp()
# else:
data['t'] = parse_alpaca_timestamp(data['t'])
if not is_open_hours(datetime.fromtimestamp(data['t'])) and self.exthours is False:
#print("AGG: trade not in open hours skipping", datetime.fromtimestamp(data['t']).astimezone(zoneNY))
return 0
#tady bude vzdycky posledni cena a posledni cas
if self.update_ltp:
ltp.price[symbol] = data['p']
ltp.time[symbol] = data['t']
#if data['p'] < self.last_price - 0.02: print("zlo:",data)
if self.rectype == RecordType.TRADE: return data
#print("agr přišel trade", datetime.fromtimestamp(data['t']),data)
#OPIC pokud bude vadit, ze prvni bar neni kompletni - pak zapnout tuto opicarnu
#kddyz jde o prvni iteraci a pozadujeme align, cekame na kulaty cas (pro 5s 0,5,10..)
# if self.lasttimestamp ==0 and self.align:
# if self.firstpass:
# self.tm = datetime.fromtimestamp(data['t'])
# self.tm += timedelta(seconds=self.timeframe)
# self.tm = self.tm - timedelta(seconds=self.tm.second % self.timeframe,microseconds=self.tm.microsecond)
# self.firstpass = False
# print("trade: ",datetime.fromtimestamp(data['t']))
# print("required",self.tm)
# if self.tm > datetime.fromtimestamp(data['t']):
# return
# else: pass
#print("barstart",datetime.fromtimestamp(self.bar_start))
#print("oriznute data z tradu", datetime.fromtimestamp(int(data['t'])))
#print("timeframe",self.timeframe)
if int(data['t']) - self.bar_start < self.timeframe:
issamebar = True
else:
issamebar = False
##flush předchozí bar a incializace (krom prvni iterace)
if self.lasttimestamp ==0: pass
else:
self.newBar['confirmed'] = 1
self.newBar['vwap'] = self.vwaphelper / self.newBar['volume']
#updatujeme čas - obsahuje datum tradu, který confirm triggeroval
self.newBar['updated'] = data['t']
#ulozime datum akt.tradu pro mintick
self.lastBarConfirmed = True
#ukládám si předchozí (confirmed)bar k vrácení
self.returnBar = self.newBar
#print(self.returnBar)
#inicializuji pro nový bar
self.vwaphelper = 0
# return self.newBar
##flush CONFIRMED bar to queue
#self.q.put(self.newBar)
##TODO pridat prubezne odesilani pokud je pozadovano
self.barindex +=1
self.newBar = {
"close": 0,
"high": 0,
"low": 99999999,
"volume": 0,
"trades": 0,
"hlcc4": 0,
"confirmed": 0,
"updated": 0,
"vwap": 0,
"index": self.barindex
}
#je cena stejna od predchoziho tradu? pro nepotvrzeny cbar vracime jen pri zmene ceny
if self.last_price == data['p']:
diff_price = False
else:
diff_price = True
self.last_price = data['p']
#spočteme vwap - potřebujeme předchozí hodnoty
self.vwaphelper += (data['p'] * data['s'])
self.newBar['updated'] = data['t']
self.newBar['close'] = data['p']
self.newBar['high'] = max(self.newBar['high'],data['p'])
self.newBar['low'] = min(self.newBar['low'],data['p'])
self.newBar['volume'] = self.newBar['volume'] + data['s']
self.newBar['trades'] = self.newBar['trades'] + 1
#pohrat si s timto round
self.newBar['hlcc4'] = round((self.newBar['high']+self.newBar['low']+self.newBar['close']+self.newBar['close'])/4,3)
#predchozi bar byl v jine vterine, tzn. ukladame do noveho (aktualniho) pocatecni hodnoty
if (issamebar == False):
#zaciname novy bar
self.newBar['open'] = data['p']
#zarovname time prvniho baru podle timeframu kam patří (např. 5, 10, 15 ...) (ROUND)
if self.align:
t = datetime.fromtimestamp(data['t'])
t = t - timedelta(seconds=t.second % self.timeframe,microseconds=t.microsecond)
self.bar_start = datetime.timestamp(t)
#nebo pouzijeme datum tradu zaokrouhlene na vteriny (RANDOM)
else:
#ulozime si jeho timestamp (odtum pocitame timeframe)
t = datetime.fromtimestamp(int(data['t']))
#timestamp
self.bar_start = int(data['t'])
self.newBar['time'] = t
self.newBar['resolution'] = self.timeframe
self.newBar['confirmed'] = 0
#uložíme do předchozí hodnoty (poznáme tak open a close)
self.lasttimestamp = data['t']
self.iterace += 1
# print(self.iterace, data)
#je tu maly bug pro CBAR - kdy prvni trade, který potvrzuje predchozi bar
#odesle potvrzeni predchoziho baru a nikoliv open stávajícího, ten posle až druhý trade
#což asi nevadí
#pokud je pripraveny, vracíme předchozí confirmed bar
if len(self.returnBar) > 0:
self.tmp = self.returnBar
self.returnBar = {}
#print(self.tmp)
return self.tmp
#pro cont bar posilame ihned (TBD vwap a min bar tick value)
if self.rectype == RecordType.CBAR:
#pokud je mintick nastavený a předchozí bar byl potvrzený
if self.mintick != 0 and self.lastBarConfirmed:
#d zacatku noveho baru musi ubehnout x sekund nez posilame updazte
#pocatek noveho baru + Xs musi byt vetsi nez aktualni trade
if (self.newBar['time'] + timedelta(seconds=self.mintick)) > datetime.fromtimestamp(data['t']):
#print("waiting for mintick")
return 0
else:
self.lastBarConfirmed = False
#doplnime prubezny vwap
self.newBar['vwap'] = self.vwaphelper / self.newBar['volume']
#print(self.newBar)
#pro (nepotvrzeny) cbar vracime jen pri zmene ceny
if diff_price is True:
return self.newBar
else:
return 0
else:
return 0
class TradeAggregator2Queue(TradeAggregator):
"""
Child of TradeAggregator - sends items to given queue
In the future others will be added - TradeAggToTxT etc.
"""
def __init__(self, symbol: str, queue: Queue, rectype: RecordType = RecordType.BAR, timeframe: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, mintick: int = 0, exthours: bool = False):
super().__init__(rectype=rectype, timeframe=timeframe, minsize=minsize, update_ltp=update_ltp, align=align, mintick=mintick, exthours=exthours)
self.queue = queue
self.symbol = symbol
async def ingest_trade(self, data):
#print("ingest ve threadu:",current_thread().name)
res = await super().ingest_trade(data, self.symbol)
if res != 0:
#print(res)
#pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií
if isinstance(res, dict):
copy = res.copy()
else:
copy = res
self.queue.put(copy)
res = {}
#print("po insertu",res)
class TradeAggregator2List(TradeAggregator):
""""
stores records to the list
"""
def __init__(self, symbol: str, btdata: list, rectype: RecordType = RecordType.BAR, timeframe: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, mintick: int = 0, exthours: bool = False):
super().__init__(rectype=rectype, timeframe=timeframe, minsize=minsize, update_ltp=update_ltp, align=align, mintick=mintick, exthours=exthours)
self.btdata = btdata
self.symbol = symbol
# self.debugfile = DATA_DIR + "/BACprices.txt"
# if os.path.exists(self.debugfile):
# os.remove(self.debugfile)
async def ingest_trade(self, data):
#print("ted vstoupil do tradeagg2list ingestu")
res1 = await super().ingest_trade(data, self.symbol)
#print("ted je po zpracovani", res1)
if res1 != 0:
#pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií
if isinstance(res1, dict):
copy = res1.copy()
else:
copy = res1
if res1 == 'last': return 0
self.btdata.append((copy['t'],copy['p']))
# with open(self.debugfile, "a") as output:
# output.write(str(copy['t']) + ' ' + str(datetime.fromtimestamp(copy['t']).astimezone(zoneNY)) + ' ' + str(copy['p']) + '\n')
res1 = {}
#print("po insertu",res)

View File

@ -127,8 +127,6 @@ function transform_data(data) {
//pro jistotu jeste seradime podle casu
//v BT se muze predbehnout a lightweight to pak nezobrazi
const sorter = (a, b) => a.time > b.time ? 1 : -1;
markers.sort(sorter)
markers_line.sort(sorter)
avgp_buy_line.sort(sorter)
@ -287,118 +285,142 @@ function chart_archived_run(archRecord, data, oneMinuteBars) {
//vybereme barvu pro kazdy identifikator
//zjistime typ idenitfikatoru - zatim right vs left
function display_indicators(data) {
console.log("indikatory", JSON.stringify(data.indicators,null,2))
//console.log("indikatory", JSON.stringify(data.indicators,null,2))
//podobne v livewebsokcets.js - dat do jedne funkce
if (data.hasOwnProperty("indicators")) {
// console.log("jsme uvnitr indikatoru")
var indicators = data.indicators
//if there are indicators it means there must be at least two keys (time which is always present)
if (Object.keys(indicators).length > 1) {
for (const [key, value] of Object.entries(indicators)) {
if (key !== "time") {
//initialize indicator and store reference to array
var obj = {name: key, series: null}
//start
//console.log(key)
//get configuation of indicator to display
conf = get_ind_config(key)
//INIT INDICATOR BASED on CONFIGURATION
//vraci se pole indicatoru, kazdy se svoji casovou osou (time) - nyni standard indikatory a cbar indikatory
var indicatorList = data.indicators
//MOVE TO UTILS ro reuse??
if (conf && conf.display) {
indicatorList.forEach((indicators, index, array) => {
//tranform data do správného formátru
items = []
//var last = null
value.forEach((element, index, array) => {
item = {}
//debug
//TOTO odstranit po identifikovani chyby
//if (indicators.time[index] !== undefined) {
//{console.log("problem",key,last)}
item["time"] = indicators.time[index]
item["value"] = element
//console.log("objekt indicatoru",item)
items.push(item)
//var indicators = data.indicators
//if there are indicators it means there must be at least two keys (time which is always present)
if (Object.keys(indicators).length > 1) {
for (const [key, value] of Object.entries(indicators)) {
if (key !== "time") {
//initialize indicator and store reference to array
var obj = {name: key, series: null}
//start
//console.log(key)
//get configuation of indicator to display
conf = get_ind_config(key)
//INIT INDICATOR BASED on CONFIGURATION
//DO BUDOUCNA zde udelat sorter a pripadny handling duplicit jako
//funkci do ktere muzu zavolat vse co pujde jako data do chartu
//MOVE TO UTILS ro reuse??
if (conf && conf.display) {
//tranform data do správného formátru
items = []
//var last = null
var last_time = 0
var time = 0
value.forEach((element, index, array) => {
item = {}
//debug
//last = item
// }
// else
// {
// console.log("chybejici cas", key)
// }
});
//TOTO odstranit po identifikovani chyby
//if (indicators.time[index] !== undefined) {
//{console.log("problem",key,last)}
time = indicators.time[index]
if (time==last_time) {
//console.log(key, "problem v case - pousunuto o 0.001",time, last_time, element)
time += 0.000001
}
item["time"] = time
item["value"] = element
if (conf.embed) {
last_time = time
if (conf.histogram) {
if ((element == null) || (indicators.time[index] == null)) {
console.log("probelem u indikatoru",key, "nekonzistence", element, indicators.time[index])
}
//console.log("objekt indicatoru",item)
items.push(item)
//debug
//last = item
// }
// else
// {
// console.log("chybejici cas", key)
// }
});
if (conf.embed) {
if (conf.histogram) {
obj.series = chart.addHistogramSeries({
title: (conf.titlevisible?conf.name:""),
color: colors.shift(),
priceFormat: {type: 'volume'},
priceScaleId: conf.priceScaleId,
lastValueVisible: conf.lastValueVisible,
priceScaleId: conf.priceScaleId});
obj.series.priceScale().applyOptions({
// set the positioning of the volume series
scaleMargins: {
top: 0.7, // highest point of the series will be 70% away from the top
bottom: 0,
},
});
}
else {
obj.series = chart.addLineSeries({
color: colors.shift(),
priceScaleId: conf.priceScaleId,
title: (conf.titlevisible?conf.name:""),
lineWidth: 1
});
//toto nejak vymyslet konfiguracne, additional threshold lines
if (key == "slopeMA") {
//natvrdo nakreslime lajnu pro min angle
//TODO predelat na configuracne
const minSlopeLineOptopns = {
price: data.statinds.angle.minimum_slope,
color: '#b67de8',
lineWidth: 1,
lineStyle: 2, // LineStyle.Dotted
axisLabelVisible: true,
title: "max:",
};
const minSlopeLine = obj.series.createPriceLine(minSlopeLineOptopns);
}
}
obj.series = chart.addHistogramSeries({
title: (conf.titlevisible?conf.name:""),
color: colors.shift(),
priceFormat: {type: 'volume'},
priceScaleId: conf.priceScaleId,
lastValueVisible: conf.lastValueVisible,
priceScaleId: conf.priceScaleId});
obj.series.priceScale().applyOptions({
// set the positioning of the volume series
scaleMargins: {
top: 0.7, // highest point of the series will be 70% away from the top
bottom: 0,
},
});
}
else {
obj.series = chart.addLineSeries({
color: colors.shift(),
priceScaleId: conf.priceScaleId,
title: (conf.titlevisible?conf.name:""),
lineWidth: 1
});
//toto nejak vymyslet konfiguracne, additional threshold lines
if (key == "slopeMA") {
//natvrdo nakreslime lajnu pro min angle
//TODO predelat na configuracne
const minSlopeLineOptopns = {
price: data.statinds.angle.minimum_slope,
color: '#b67de8',
lineWidth: 1,
lineStyle: 2, // LineStyle.Dotted
axisLabelVisible: true,
title: "max:",
};
//INDICATOR on new pane
else { console.log("not implemented")}
const minSlopeLine = obj.series.createPriceLine(minSlopeLineOptopns);
}
}
//add options
obj.series.applyOptions({
lastValueVisible: false,
priceLineVisible: false,
});
//console.log("problem tu",JSON.stringify(items))
//add data
obj.series.setData(items)
}
//INDICATOR on new pane
else { console.log("not implemented")}
//add options
obj.series.applyOptions({
lastValueVisible: false,
priceLineVisible: false,
});
//console.log("problem tu",items)
//add data
obj.series.setData(items)
// add to indList array - pole zobrazovanych indikatoru
indList.push(obj);
// add to indList array - pole zobrazovanych indikatoru
indList.push(obj);
}
}
}
}
}
})
}
//display vwap and volume

View File

@ -5,6 +5,7 @@ var logcnt = 0
var positionsPriceLine = null
var limitkaPriceLine = null
var angleSeries = 1
var cbar = false
//get details of runner to populate chart status
//fetch necessary - it could be initiated by manually inserting runnerId
@ -53,35 +54,21 @@ function connect(event) {
ws.onmessage = function(event) {
var parsed_data = JSON.parse(event.data)
//console.log(JSON.stringify(parsed_data))
console.log(JSON.stringify(parsed_data))
//check received data and display lines
if (parsed_data.hasOwnProperty("bars")) {
var bar = parsed_data.bars
candlestickSeries.update(bar);
volumeSeries.update({
time: bar.time,
value: bar.volume
});
vwapSeries.update({
time: bar.time,
value: bar.vwap
});
}
if (parsed_data.hasOwnProperty("bars")) {
// console.log("mame bary")
var bar = parsed_data.bars
candlestickSeries.update(bar);
volumeSeries.update({
time: bar.time,
value: bar.volume
});
vwapSeries.update({
time: bar.time,
value: bar.vwap
});
}
// //check received data and display lines
// if (parsed_data.hasOwnProperty("bars")) {
// var bar = parsed_data.bars
// candlestickSeries.update(bar);
// volumeSeries.update({
// time: bar.time,
// value: bar.volume
// });
// vwapSeries.update({
// time: bar.time,
// value: bar.vwap
// });
// }
//loglist
if (parsed_data.hasOwnProperty("iter_log")) {
@ -344,6 +331,52 @@ function connect(event) {
}
}
}
if (parsed_data.hasOwnProperty("bars")) {
var bar = parsed_data.bars
//pokud jde o cbary, tak jako time bereme cas posledniho update
//aby se nam na grafu nepredbihaly cbar indikatory
//workaround pro identifikaci CBARU
//pokud se vyskytne unconfirmed bar = jde o CBARY - nastavena globalni promena
//standardni bar je vzdy potvrzeny
// if (bar.confirmed == 0) {
// cbar = true }
// //pozor CBARY zobrazujeme na konci platnosti baru, nikoliv dle TIME, ale UPDATED
// //kvuli navazovani prubeznych indikatoru na gui
// if (cbar) {
// // CBAR kreslime az po potvrzeni
// if (bar.confirmed == 1) {
// bar.time = bar.updated
// candlestickSeries.update(bar);
// volumeSeries.update({
// time: bar.time,
// value: bar.volume
// });
// vwapSeries.update({
// time: bar.time,
// value: bar.vwap
// });
// }
// }
// else {
// //time = bar.time
candlestickSeries.update(bar);
volumeSeries.update({
time: bar.time,
value: bar.volume
});
vwapSeries.update({
time: bar.time,
value: bar.vwap
});
//}
}
}
ws.onclose = function(event) {
document.getElementById("status").textContent = "Disconnected from" + runnerId.value

View File

@ -9,6 +9,8 @@ var candlestickSeries = null
var volumeSeries = null
var vwapSeries = null
const sorter = (a, b) => a.time > b.time ? 1 : -1;
indConfig = {}
settings = {}
settings
@ -16,6 +18,7 @@ settings
indConfig = [ {name: "ema", titlevisible: false, embed: true, display: true, priceScaleId: "right", lastValueVisible: false},
{name: "tick_volume", histogram: true, titlevisible: true, embed: true, display: true, priceScaleId: '', lastValueVisible: false},
{name: "tick_price", titlevisible: true, embed: true, display: true, priceScaleId: "right", lastValueVisible: false},
{name: "ivwap", titlevisible: true, embed: true, display: false, priceScaleId: "right", lastValueVisible: false},
{name: "slope", titlevisible: true, embed: true, display: false, priceScaleId: "middle", lastValueVisible: false},
{name: "slopeMA", titlevisible: true, embed: true, display: true, priceScaleId: "middle", lastValueVisible: false},
{name: "emaSlow", titlevisible: true, embed: true, display: true, priceScaleId: "right", lastValueVisible: false},

View File

@ -138,11 +138,12 @@ class Strategy:
def save_item_history(self,item):
if self.rectype == RecordType.BAR:
#jako cas indikatorů pridavame cas baru a inicialni hodnoty vsech indikatoru
self.state.indicators['time'].append(item['time'])
for key in self.state.indicators:
if key == 'time':
continue
self.state.indicators[key].append(0)
self.state.indicators['time'].append(item['updated'])
else:
self.state.indicators[key].append(0)
self.append_bar(self.state.bars,item)
elif self.rectype == RecordType.TRADE:
pass
@ -150,25 +151,42 @@ class Strategy:
#self.state.indicators['time'].append(datetime.fromtimestamp(self.state.last_trade_time))
#self.append_trade(self.state.trades,item)
elif self.rectype == RecordType.CBAR:
#novy vzdy pridame
if self.nextnew:
self.state.indicators['time'].append(item['updated'])
#standardni identifikatory - populace hist zaznamu pouze v novem baru (dale se deji jen udpaty)
for key in self.state.indicators:
if key == 'time':
continue
self.state.indicators[key].append(0)
self.state.indicators['time'].append(item['time'])
else:
self.state.indicators[key].append(0)
#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)
#populujeme i novy bar v historii
self.append_bar(self.state.bars,item)
self.nextnew = 0
#nasledujici updatneme, po potvrzeni, nasleduje novy bar
#nasledujici identifikatory v ramci cbaru take pridavame
# (udrzujeme historii prubehu identifikatoru v ramci cbaru)
else:
#bary updatujeme, pridavame jen prvni
self.replace_prev_bar(self.state.bars,item)
#u cbar indikatoru, pridavame kazdou zmenu ceny, krome potvrzeneho baru
if item['confirmed'] == 0:
self.state.indicators['time'][-1]=item['updated']
self.replace_prev_bar(self.state.bars,item)
#confirmed
#v naslednych updatech baru inicializujeme vzdy jen cbar indikatory
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)
else:
self.state.indicators['time'][-1]=item['updated']
self.replace_prev_bar(self.state.bars,item)
#pokud je potvrzeny, pak nenese nikdy zmenu ceny, nepridavame zaznam nic
self.nextnew = 1
""""refresh positions and avgp - for CBAR once per confirmed, for BARS each time"""
@ -395,11 +413,12 @@ class Strategy:
##posilame dict s objekty: bars, trades podle cbaru, a dale indicators naplnene time a pripadnymi identifikatory (EMA)
if self.rtqueue is not None:
rt_out = dict()
if self.rectype == RecordType.BAR or self.rectype == RecordType.CBAR:
rt_out["bars"] = item
else:
rt_out["trades"] = item
#get only last values from indicators, if there are any indicators present
if len(self.state.indicators) > 0:
rt_out["indicators"] = dict()
@ -410,6 +429,13 @@ class Strategy:
#zatim takto odchycene identifikatory, ktere nemaji list, ale dict - do budoucna predelat na samostatny typ "indicators_static"
except IndexError:
pass
#populate cbar indicators
if len(self.state.cbar_indicators) > 0:
for key, value in self.state.cbar_indicators.items():
try:
rt_out["indicators"][key]= value[-1]
except IndexError:
pass
#same for static indicators
if len(self.state.statinds) > 0:
@ -544,6 +570,7 @@ class StrategyState:
self.bars = AttributeDict(bars)
self.trades = AttributeDict(trades)
self.indicators = AttributeDict(time=[])
self.cbar_indicators = AttributeDict(time=[])
self.statinds = AttributeDict()
#these methods can be overrided by StrategyType (to add or alter its functionality)
self.buy = self.interface.buy

View File

@ -131,6 +131,14 @@ def price2dec(price: float, decimals: int = 2) -> float:
"""
return round(price,decimals) if count_decimals(price) > decimals else price
def round2five(price: float):
"""
zatim jen na 3 mista -pripadne predelat na dynamicky
z 23.342 - 23.340
z 23.346 - 23.345
"""
return (round(price*100*2)/2)/100
def count_decimals(number: float) -> int:
"""
Count the number of decimals in a given float: 1.4335 -> 4 or 3 -> 0