This commit is contained in:
David Brazda
2023-05-15 15:36:35 +02:00
parent 6e775a33d9
commit 2e8f4ffd0e
11 changed files with 804 additions and 4 deletions

43
testy/normalizePrices.py Normal file
View File

@ -0,0 +1,43 @@
from v2realbot.utils.utils import price2dec
bacprice = 28.90
cprice = 45.00
bacma = [28.90,28.95,28.96]
bactick = [0.1, 0.1, 0.1]
cma = [45, 45.50, 45.90]
baseprice = 28.90
basetick = 0.01
baseratio = cprice/baseprice
ctick = baseratio*basetick
#print(ctick)
#normalized price for tick 0.01
NORMALIZED_TICK_BASE_PRICE = 30.00
# prevede normalizovany tick na tick relevantni dane cene
# u cen pod 30, vrací 0.01. U cen nad 30 vrací pomerne zvetsene,
def get_tick(price: float, normalized_ticks: float = 0.01):
"""
prevede normalizovany tick na tick odpovidajici vstupni cene
vysledek je zaokoruhleny na 2 des.mista
u cen pod 30, vrací 0.01. U cen nad 30 vrací pomerne zvetsene,
"""
if price<NORMALIZED_TICK_BASE_PRICE:
return normalized_ticks
else:
#ratio of price vs base price
ratio = price/NORMALIZED_TICK_BASE_PRICE
return price2dec(ratio*normalized_ticks)
print(get_tick(40,0.04))

View File

@ -0,0 +1,522 @@
import os,sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from v2realbot.strategy.base import StrategyState
from v2realbot.strategy.StrategyOrderLimitVykladaciNormalized import StrategyOrderLimitVykladaciNormalized
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 datetime import datetime
#from icecream import install, ic
#from rich import print
from threading import Event
from msgpack import packb, unpackb
import asyncio
import os
from traceback import format_exc
print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
""""
Kope Vykladaci strategie s navic pridanym RSI a normalizovaným tickem.
- pokud RSI > 50
- rusime pendingbuye
- netriggerujeme buy
"""
stratvars = AttributeDict(maxpozic = 400,
def_mode_from = 200,
chunk = 10,
MA = 2,
Trend = 2,
profit = 0.02,
def_profit = 0.01,
lastbuyindex=-6,
pendingbuys={},
limitka = None,
limitka_price = None,
jevylozeno=0,
vykladka=5,
curve = [0.01, 0.01, 0.01, 0, 0.02, 0.02, 0.01,0.01, 0.01,0.03, 0.01, 0.01, 0.01,0.04, 0.01,0.01, 0.01,0.05, 0.01,0.01, 0.01,0.01, 0.06,0.01, 0.01,0.01, 0.01],
curve_def = [0.02, 0.02, 0.02, 0, 0, 0.02, 0, 0, 0, 0.02],
blockbuy = 0,
ticks2reset = 0.04,
consolidation_bar_count = 10,
slope_lookback = 300,
lookback_offset = 20,
minimum_slope = -0.05,
first_buy_market = False
)
##toto rozparsovat a strategii spustit stejne jako v main
toml_string = """
[[strategies]]
name = "V1 na BAC"
symbol = "BAC"
script = "ENTRY_backtest_strategyVykladaci"
class = "StrategyOrderLimitVykladaci"
open_rush = 0
close_rush = 0
[strategies.stratvars]
maxpozic = 200
chunk = 10
MA = 6
Trend = 5
profit = 0.02
lastbuyindex=-6
pendingbuys={}
limitka = "None"
jevylozeno=0
vykladka=5
curve = [0.01, 0.01, 0.01,0.01, 0.02, 0.01,0.01, 0.01,0.03, 0.01, 0.01, 0.01,0.04, 0.01,0.01, 0.01,0.05, 0.01,0.01, 0.01,0.01, 0.06,0.01, 0.01,0.01, 0.01]
blockbuy = 0
ticks2reset = 0.04
[[strategies.add_data]]
symbol="BAC"
rectype="bar"
timeframe=5
update_ltp=true
align="round"
mintick=0
minsize=100
exthours=false
"""
def next(data, state: StrategyState):
print(10*"*","NEXT START",10*"*")
#ic(state.avgp, state.positions)
#ic(state.vars)
#ic(data)
#
def is_defensive_mode():
akt_pozic = int(state.positions)
max_pozic = int(state.vars.maxpozic)
def_mode_from = safe_get(state.vars, "def_mode_from")
if def_mode_from == None: def_mode_from = max_pozic/2
if akt_pozic >= int(def_mode_from):
state.ilog(e=f"DEFENSIVE mode ACTIVE {state.vars.def_mode_from=}", msg=state.positions)
return True
else:
state.ilog(e=f"STANDARD mode ACTIVE {state.vars.def_mode_from=}", msg=state.positions)
return False
def get_limitka_price():
def_profit = safe_get(state.vars, "def_profit")
if def_profit == None: def_profit = state.vars.profit
cena = float(state.avgp)
if is_defensive_mode():
return price2dec(cena+get_tick(cena,float(def_profit)))
else:
return price2dec(cena+get_tick(cena,float(state.vars.profit)))
def consolidation():
##CONSOLIDATION PART - moved here, musí být před nákupem, jinak to dělalo nepořádek v pendingbuys
#docasne zkusime konzolidovat i kdyz neni vylozeno (aby se srovnala limitka ve vsech situacich)
if state.vars.jevylozeno == 1 or 1==1:
##CONSOLIDATION PART kazdy Nty bar dle nastaveni
if int(data["index"])%int(state.vars.consolidation_bar_count) == 0:
print("***CONSOLIDATION ENTRY***")
state.ilog(e="CONSOLIDATION ENTRY ***")
orderlist = state.interface.get_open_orders(symbol=state.symbol, side=None)
#pro jistotu jeste dotahneme aktualni pozice
state.avgp, state.positions = state.interface.pos()
#print(orderlist)
pendingbuys_new = {}
limitka_old = state.vars.limitka
#print("Puvodni LIMITKA", limitka_old)
#zaciname s cistym stitem
state.vars.limitka = None
state.vars.limitka_price = None
limitka_found = False
limitka_qty = 0
limitka_filled_qty = 0
for o in orderlist:
if o.side == OrderSide.SELL:
if limitka_found:
state.ilog(e="nalezeno vicero sell objednavek, bereme prvni, ostatni - rusime")
result=state.interface.cancel(o.id)
state.ilog(e="zrusena objednavka"+str(o.id), message=result)
continue
#print("Nalezena LIMITKA")
limitka_found = True
state.vars.limitka = o.id
state.vars.limitka_price = o.limit_price
limitka_qty = int(o.qty)
limitka_filled_qty = int(o.filled_qty)
#aktualni mnozstvi = puvodni minus filled
if limitka_filled_qty is not None:
print("prepocitavam filledmnozstvi od limitka_qty a filled_qty", limitka_qty, limitka_filled_qty)
limitka_qty = int(limitka_qty) - int(limitka_filled_qty)
##TODO sem pridat upravu ceny
if o.side == OrderSide.BUY and o.order_type == OrderType.LIMIT:
pendingbuys_new[str(o.id)]=float(o.limit_price)
state.ilog(e="Konzolidace limitky", msg=f"stejna:{(str(limitka_old)==str(state.vars.limitka))}", limitka_old=str(limitka_old), limitka_new=str(state.vars.limitka), limitka_new_price=state.vars.limitka_price, limitka_qty=limitka_qty, limitka_filled_qty=limitka_filled_qty)
#pokud mame
#neni limitka, ale mela by byt - vytváříme ji
if int(state.positions) > 0 and state.vars.limitka is None:
state.ilog(e="Limitka neni, ale mela by být.", msg=f"{state.positions=}")
price=get_limitka_price()
state.vars.limitka = asyncio.run(state.interface.sell_l(price=price, size=int(state.positions)))
state.vars.limitka_price = price
if state.vars.limitka == -1:
state.ilog(e="Vytvoreni limitky neprobehlo, vracime None", msg=f"{state.vars.limitka=}")
state.vars.limitka = None
state.vars.limitka_price = None
else:
state.ilog(e="Vytvořena nová limitka", limitka=str(state.vars.limitka), limtka_price=state.vars.limitka_price, qty=state.positions)
#existuje a nesedi mnozstvi nebo cena
elif state.vars.limitka is not None and int(state.positions) > 0 and ((int(state.positions) != int(limitka_qty)) or float(state.vars.limitka_price) != float(get_limitka_price())):
#limitka existuje, ale spatne mnostvi - updatujeme
state.ilog(e=f"Limitka existuje, ale spatne mnozstvi nebo CENA - updatujeme", msg=f"{state.positions=} {limitka_qty=} {state.vars.limitka_price=}", nastavenacena=state.vars.limitka_price, spravna_cena=get_limitka_price(), pos=state.positions, limitka_qty=limitka_qty)
#snad to nespadne, kdyztak pridat exception handling
puvodni = state.vars.limitka
#TBD zde odchytit nejak result
state.vars.limitka = asyncio.run(state.interface.repl(price=get_limitka_price(), orderid=state.vars.limitka, size=int(state.positions)))
if state.vars.limitka == -1:
state.ilog(e="Replace limitky neprobehl, vracime puvodni", msg=f"{state.vars.limitka=}", puvodni=puvodni)
state.vars.limitka = puvodni
else:
limitka_qty = int(state.positions)
state.ilog(e="Změněna limitka", limitka=str(state.vars.limitka), limitka_price=state.vars.limitka_price, limitka_qty=limitka_qty)
#tbd pokud se bude vyskytovat pak pridat ještě konzolidaci ceny limitky
if pendingbuys_new != state.vars.pendingbuys:
state.ilog(e="Rozdilna PB prepsana", pb_new=pendingbuys_new, pb_old = state.vars.pendingbuys)
print("ROZDILNA PENDINGBUYS přepsána")
print("OLD",state.vars.pendingbuys)
state.vars.pendingbuys = unpackb(packb(pendingbuys_new))
print("NEW", state.vars.pendingbuys)
else:
print("PENDINGBUYS sedí - necháváme", state.vars.pendingbuys)
state.ilog(e="PB sedi nechavame", pb_new=pendingbuys_new, pb_old = state.vars.pendingbuys)
print("OLD jevylozeno", state.vars.jevylozeno)
if len(state.vars.pendingbuys) > 0:
state.vars.jevylozeno = 1
else:
state.vars.jevylozeno = 0
print("NEW jevylozeno", state.vars.jevylozeno)
state.ilog(e="Nove jevylozeno", msg=state.vars.jevylozeno)
#print(limitka)
#print(pendingbuys_new)
#print(pendingbuys)
#print(len(pendingbuys))
#print(len(pendingbuys_new))
#print(jevylozeno)
print("***CONSOLIDATION EXIT***")
state.ilog(e="CONSOLIDATION EXIT ***")
else:
state.ilog(e="No time for consolidation", msg=data["index"])
print("no time for consolidation", data["index"])
#mozna presunout o level vys
def vyloz():
##prvni se vyklada na aktualni cenu, další jdou podle krivky, nula v krivce zvyšuje množství pro následující iteraci
#curve = [0.01, 0.01, 0, 0, 0.01, 0, 0, 0, 0.02, 0, 0, 0, 0.03, 0,0,0,0,0, 0.02, 0,0,0,0,0,0, 0.02]
curve = state.vars.curve
##defenzivni krivka pro
curve_def = state.vars.curve_def
#vykladani po 5ti kusech, když zbývají 2 a méně, tak děláme nový výklad
vykladka = state.vars.vykladka
#kolik muzu max vylozit
kolikmuzu = int((int(state.vars.maxpozic) - int(state.positions))/int(state.vars.chunk))
akt_pozic = int(state.positions)
max_pozic = int(state.vars.maxpozic)
#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)
curve = curve_def
#zaroven docasne menime ticks2reset na defenzivni 0.06
state.vars.ticks2reset = 0.06
state.ilog(e="DEF: Menime tick2reset na 0.06", ticks2reset=state.vars.ticks2reset, ticks2reset_backup=state.vars.ticks2reset_backup)
else:
#vracime zpet, pokud bylo zmeneno
if state.vars.ticks2reset != state.vars.ticks2reset_backup:
state.vars.ticks2reset = state.vars.ticks2reset_backup
state.ilog(e="DEF: Menime tick2reset zpet na"+str(state.vars.ticks2reset), ticks2reset=state.vars.ticks2reset, ticks2reset_backup=state.vars.ticks2reset_backup)
if kolikmuzu < vykladka: vykladka = kolikmuzu
if len(curve) < vykladka:
vykladka = len(curve)
qty = int(state.vars.chunk)
last_price = price2dec(state.interface.get_last_price(state.symbol))
#profit = float(state.vars.profit)
price = last_price
state.ilog(e="BUY Vykladame", msg=f"first price {price=} {vykladka=}", curve=curve, ema=state.indicators.ema[-1], trend=state.vars.Trend, price=price, vykladka=vykladka)
##prvni se vyklada na aktualni cenu, další jdou podle krivky, nula v krivce zvyšuje množství pro následující iteraci
##VAR - na zaklade conf. muzeme jako prvni posilat MARKET order
if safe_get(state.vars, "first_buy_market") == True:
#pri defenzivnim rezimu pouzivame vzdy LIMIT order
if is_defensive_mode():
state.ilog(e="DEF mode on, odesilame jako prvni limitku")
state.buy_l(price=price, size=qty)
else:
state.ilog(e="Posilame jako prvni MARKET order")
state.buy(size=qty)
else:
state.buy_l(price=price, size=qty)
print("prvni limitka na aktuální cenu. Další podle křivky", price, qty)
for i in range(0,vykladka-1):
price = price2dec(float(price - get_tick(price, curve[i])))
if price == last_price:
qty = qty + int(state.vars.chunk)
else:
state.buy_l(price=price, size=qty)
#print(i,"BUY limitka - delta",curve[i]," cena:", price, "mnozstvi:", qty)
qty = int(state.vars.chunk)
last_price = price
state.vars.blockbuy = 1
state.vars.jevylozeno = 1
#CBAR protection, only 1x order per CBAR - then wait until another confirmed bar
if state.vars.blockbuy == 1 and state.rectype == RecordType.CBAR:
if state.bars.confirmed[-1] == 0:
print("OCHR: multibuy protection. waiting for next bar")
return 0
# pop potvrzeni jeste jednou vratime (aby se nekoupilo znova, je stale ten stejny bar)
# a pak dalsi vejde az po minticku
else:
# pro vykladaci
state.vars.blockbuy = 0
return 0
state.ilog(e="-----")
#EMA INDICATOR -
#plnime MAcko - nyni posilame jen N poslednich hodnot
#zaroven osetrujeme pripady, kdy je malo dat a ukladame nulu
try:
ma = int(state.vars.MA)
#poslednich ma hodnot
source = state.bars.close[-ma:] #state.bars.vwap
ema_value = ema(source, ma)
state.indicators.ema.append(trunc(ema_value[-1],3))
except Exception as e:
state.ilog(e="EMA ukladame 0", message=str(e)+format_exc())
state.indicators.ema.append(0)
#RSI14 INDICATOR
try:
##mame v atributech nastaveni?
rsi_dont_buy_above = safe_get(state.vars, "rsi_dont_buy_above")
if rsi_dont_buy_above == None:
rsi_dont_buy_above = 50
rsi_buy_signal = False
rsi_dont_buy = False
rsi_length = 14
source = state.bars.close #[-rsi_length:] #state.bars.vwap
rsi_res = rsi(source, rsi_length)
rsi_value = trunc(rsi_res[-1],3)
state.indicators.RSI14.append(rsi_value)
rsi_dont_buy = rsi_value > rsi_dont_buy_above
rsi_buy_signal = rsi_value < 40
state.ilog(e=f"RSI {rsi_length=} {rsi_value=} {rsi_dont_buy=} {rsi_buy_signal=}", rsi_indicator=state.indicators.RSI14[-5:])
except Exception as e:
state.ilog(e=f"RSI {rsi_length=} ukladame 0", message=str(e)+format_exc())
state.indicators.RSI14.append(0)
#SLOPE INDICATOR
#úhel stoupání a klesání vyjádřený mezi -1 až 1
#pravý bod přímky je aktuální cena, levý je průměr X(lookback offset) starších hodnot od slope_lookback.
#obsahuje statický indikátor (angle) pro vizualizaci
try:
slope = 99
slope_lookback = int(state.vars.slope_lookback)
minimum_slope = float(state.vars.minimum_slope)
lookback_offset = int(state.vars.lookback_offset)
if len(state.bars.close) > (slope_lookback + lookback_offset):
array_od = slope_lookback + lookback_offset
array_do = slope_lookback
lookbackprice_array = state.bars.vwap[-array_od:-array_do]
#obycejný prumer hodnot
lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3)
#výpočet úhlu
slope = ((state.bars.close[-1] - lookbackprice)/lookbackprice)*100
slope = round(slope, 4)
state.indicators.slope.append(slope)
#angle je ze slope
state.statinds.angle = dict(time=state.bars.time[-1], price=state.bars.close[-1], lookbacktime=state.bars.time[-slope_lookback], lookbackprice=lookbackprice, minimum_slope=minimum_slope)
#slope MA vyrovna vykyvy ve slope, dále pracujeme se slopeMA
slope_MA_length = 5
source = state.indicators.slope[-slope_MA_length:]
slopeMAseries = ema(source, slope_MA_length) #state.bars.vwap
slopeMA = slopeMAseries[-1]
state.indicators.slopeMA.append(slopeMA)
state.ilog(e=f"{slope=} {slopeMA=}", msg=f"{lookbackprice=}", lookbackoffset=lookback_offset, minimum_slope=minimum_slope, last_slopes=state.indicators.slope[-10:], last_slopesMA=state.indicators.slopeMA[-10:])
#dale pracujeme s timto MAckovanym slope
slope = slopeMA
else:
#pokud plnime historii musime ji plnit od zacatku, vsehcny idenitifkatory maji spolecny time
#kvuli spravnemu zobrazovani na gui
state.indicators.slope.append(0)
state.indicators.slopeMA.append(0)
state.ilog(e="Slope - not enough data", slope_lookback=slope_lookback, slope=state.indicators.slope, slopeMA=state.indicators.slopeMA)
except Exception as e:
print("Exception in NEXT Slope Indicator section", str(e))
state.ilog(e="EXCEPTION", msg="Exception in Slope Indicator section" + str(e) + format_exc())
print("is falling",isfalling(state.indicators.ema,state.vars.Trend))
print("is rising",isrising(state.indicators.ema,state.vars.Trend))
consolidation()
#HLAVNI ITERACNI LOG JESTE PRED AKCI - obsahuje aktualni hodnoty vetsiny parametru
#TODO sem pridat aktualni hodnoty vsech indikatoru
lp = state.interface.get_last_price(symbol=state.symbol)
state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)} DEF:{str(is_defensive_mode())}", last_price=lp, data=data, stratvars=state.vars)
#SLOPE ANGLE PROTECTIONs
#slope zachycuje rychle sestupy, pripadne zrusi nakupni objednavky
if slope < minimum_slope or rsi_dont_buy: # or slopeMA<maxSlopeMA:
print("OCHRANA SLOPE or RSI TOO HIGH")
# if slopeMA<maxSlopeMA:
# state.ilog(e="Slope MA too high "+str(slopeMA)+" max:"+str(maxSlopeMA))
state.ilog(e=f"Slope or RSI too high {slope=} {rsi_value=}")
if len(state.vars.pendingbuys)>0:
print("CANCEL PENDINGBUYS")
#ic(state.vars.pendingbuys)
res = asyncio.run(state.cancel_pending_buys())
#ic(state.vars.pendingbuys)
state.ilog(e="Rusime pendingbuyes", pb=state.vars.pendingbuys, res=res)
print("slope", slope)
print("min slope", minimum_slope)
if state.vars.jevylozeno == 0:
print("Neni vylozeno, muzeme testovat nakup")
#pokud je defenziva, buy triggeruje defenzivni def_trend
#TBD
#NOVY BUY SIGNAL z RSI < 35
#buy_signal = isfalling(state.indicators.ema,state.vars.Trend)
buy_signal = rsi_buy_signal
if buy_signal and slope > minimum_slope and not rsi_dont_buy:
vyloz()
## testuje aktualni cenu od nejvyssi visici limitky
##toto spoustet jednou za X iterací - ted to jede pokazdé
#pokud to ujede o vic, rusime limitky
#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:
#pokud mame vylozeno a cena je vetsi nez tick2reset
if len(state.vars.pendingbuys)>0:
maxprice = max(state.vars.pendingbuys.values())
print("max cena v orderbuys", maxprice)
if state.interface.get_last_price(state.symbol) > float(maxprice) + get_tick(maxprice, float(state.vars.ticks2reset)):
##TODO toto nejak vymyslet - duplikovat?
res = asyncio.run(state.cancel_pending_buys())
state.ilog(e=f"UJELO to. Rusime PB", msg=f"{state.vars.ticks2reset=}", pb=state.vars.pendingbuys)
#PENDING BUYS SPENT - PART
#pokud mame vylozeno a pendingbuys se vyklepou a
# 1 vykladame idned znovu
# vyloz()
# 2 nebo - počkat zase na signál a pokračovat dál
# state.vars.blockbuy = 0
# state.vars.jevylozeno = 0
# 3 nebo - počkat na signál s enablovaným lastbuy indexem (tzn. počká nutně ještě pár barů)
#podle BT vyhodnejsi vylozit ihned
if len(state.vars.pendingbuys) == 0:
state.vars.blockbuy = 0
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):
#place to declare new vars
print("INIT v main",state.name)
state.indicators['ema'] = []
state.indicators['slope'] = []
state.indicators['slopeMA'] = []
state.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
def main():
# try:
# strat_settings = tomli.loads("]] this is invalid TOML [[")
# except tomli.TOMLDecodeError:
# print("Yep, definitely not valid.")
#strat_settings = dict_replace_value(strat_settings, "None", None)
name = os.path.basename(__file__)
se = Event()
pe = Event()
s = StrategyOrderLimitVykladaciNormalized(name = name, symbol = "BAC", account=Account.ACCOUNT1, next=next, init=init, stratvars=stratvars, open_rush=10, close_rush=0, pe=pe, se=se, ilog_save=True)
s.set_mode(mode = Mode.BT,
debug = False,
start = datetime(2023, 4, 14, 10, 42, 0, 0, tzinfo=zoneNY),
end = datetime(2023, 4, 14, 14, 35, 0, 0, tzinfo=zoneNY),
cash=100000)
#na sekundovem baru nezaokrouhlovat MAcko
s.add_data(symbol="BAC",rectype=RecordType.BAR,timeframe=2,minsize=100,update_ltp=True,align=StartBarAlign.ROUND,mintick=0, exthours=False)
#s.add_data(symbol="C",rectype=RecordType.BAR,timeframe=1,filters=None,update_ltp=True,align=StartBarAlign.ROUND,mintick=0)
s.start()
print("zastavujeme")
if __name__ == "__main__":
main()

View File

@ -2,7 +2,8 @@ from alpaca.data.enums import DataFeed
from v2realbot.enums.enums import Mode, Account, FillCondition
from appdirs import user_data_dir
#normalized price for tick 0.01
NORMALIZED_TICK_BASE_PRICE = 30.00
LOG_RUNNER_EVENTS = False
#no print in console
QUIET_MODE = True

View File

@ -105,6 +105,7 @@
<button id="button_stop" class="btn btn-outline-success btn-sm">Stop</button>
<button id="button_stopall" class="btn btn-outline-success btn-sm">Stop All</button>
<button id="button_refresh" class="refresh btn btn-outline-success btn-sm">Refresh</button>
<button id="button_connect" class="btn btn-outline-success btn-sm">Connect</button>
</div>
<table id="runnerTable" class="table-striped table dataTable" style="width:100%; border-color: #dce1dc;">
<thead>

View File

@ -583,7 +583,7 @@ function chart_archived_run(archRecord, data, oneMinuteBars) {
$("#statusMode").text(archRecord.mode)
$("#statusAccount").text(archRecord.account)
$("#statusIlog").text("Logged:" + archRecord.ilog_save)
$("#statusStratvars").text(JSON.stringify(archRecord.stratvars,null,2))
$("#statusStratvars").text(((archRecord.strat_json)?archRecord.strat_json:archRecord.stratvars),null,2)
$("#statusSettings").text(JSON.stringify(archRecord.settings,null,2))
//TBD other dynamically created indicators

View File

@ -331,7 +331,9 @@ function connect(event) {
event.preventDefault()
}
function disconnect(event) {
if (ws) {
ws.close()
}
document.getElementById("bt-disc").style.display = "none"
document.getElementById("bt-conn").style.display = "block"
event.preventDefault()

View File

@ -54,6 +54,7 @@ $(document).ready(function () {
//disable buttons (enable on row selection)
$('#button_pause').attr('disabled','disabled');
$('#button_stop').attr('disabled','disabled');
$('#button_connect').attr('disabled','disabled');
$('#button_edit').attr('disabled','disabled');
$('#button_dup').attr('disabled','disabled');
$('#button_copy').attr('disabled','disabled');
@ -86,11 +87,13 @@ $(document).ready(function () {
$(this).removeClass('selected');
$('#button_pause').attr('disabled', 'disabled');
$('#button_stop').attr('disabled', 'disabled');
$('#button_connect').attr('disabled', 'disabled');
} else {
stratinRecords.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
$('#button_pause').attr('disabled', false);
$('#button_stop').attr('disabled', false);
$('#button_connect').attr('disabled', false);
}
});
@ -274,6 +277,18 @@ $(document).ready(function () {
})
});
//button connect
$('#button_connect').click(function () {
row = runnerRecords.row('.selected').data();
event.preventDefault();
$('#button_connect').attr('disabled','disabled');
$('#runnerId').val(row.id);
disconnect(event)
connect(event)
// $( "#bt-conn" ).trigger( "click" );
$('#button_connect').attr('disabled',false);
});
//button stop
$('#button_stop').click(function () {
row = runnerRecords.row('.selected').data();
@ -546,7 +561,7 @@ $("#runModal").on('submit','#runForm', function(event){
rec.add_data_conf = row.add_data_conf;
rec.note = row.note;
rec.history = "";
strat_json = JSON.stringify(rec);
strat_json = JSON.stringify(rec, null, 2);
formData.strat_json = strat_json
jsonString = JSON.stringify(formData);

View File

@ -0,0 +1,201 @@
from v2realbot.strategy.base import Strategy
from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, print, json_serial, safe_get, get_tick
from v2realbot.utils.tlog import tlog, tlog_exception
from v2realbot.enums.enums import Mode, Order, Account
from alpaca.trading.models import TradeUpdate
from alpaca.trading.enums import TradeEvent, OrderStatus
from v2realbot.indicators.indicators import ema
import json
#from rich import print
from random import randrange
from alpaca.common.exceptions import APIError
import copy
from threading import Event
from uuid import UUID
class StrategyOrderLimitVykladaciNormalized(Strategy):
def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: Mode = Mode.PAPER, stratvars: AttributeDict = None, open_rush: int = 30, close_rush: int = 30, pe: Event = None, se: Event = None, runner_id: UUID = None, ilog_save: bool = False) -> None:
super().__init__(name, symbol, next, init, account, mode, stratvars, open_rush, close_rush, pe, se, runner_id, ilog_save)
async def orderUpdateBuy(self, data: TradeUpdate):
o: Order = data.order
##nejak to vymyslet, aby se dal poslat cely Trade a serializoval se
self.state.ilog(e="Příchozí BUY notif", msg=o.status, trade=json.loads(json.dumps(data, default=json_serial)))
if o.status == OrderStatus.FILLED or o.status == OrderStatus.CANCELED:
#pokud existuje objednavka v pendingbuys - vyhodime ji
if self.state.vars.pendingbuys.pop(str(o.id), False):
self.state.ilog(e="Příchozí BUY notif - mazeme ji z pb", msg=o.status, status=o.status, orderid=str(o.id), pb=self.state.vars.pendingbuys)
print("limit buy filled or cancelled. Vyhazujeme z pendingbuys.")
#ic(self.state.vars.pendingbuys)
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
#ic("vstupujeme do orderupdatebuy")
print(data)
#dostavame zde i celkové akutální množství - ukládáme
self.state.positions = data.position_qty
if self.state.vars.limitka is None:
self.state.avgp = float(data.price)
#price=price2dec(float(o.filled_avg_price)+self.state.vars.profit)
price = await self.get_limitka_price()
self.state.vars.limitka = await self.interface.sell_l(price=price, size=o.filled_qty)
#obcas live vrati "held for orders", odchytime chybu a limitku nevytvarime - spravi to dalsi notifikace nebo konzolidace
if self.state.vars.limitka == -1:
self.state.ilog(e="Vytvoreni limitky neprobehlo, vracime None", msg=str(self.state.vars.limitka))
self.state.vars.limitka = None
else:
self.state.vars.limitka_price = price
self.state.ilog(e="Příchozí BUY notif - vytvarime limitku", msg=o.status, status=o.status, orderid=str(o.id), limitka=str(self.state.vars.limitka), limtka_price=self.state.vars.limitka_price)
else:
#avgp, pos
self.state.avgp, self.state.positions = self.state.interface.pos()
#cena = price2dec(float(self.state.avgp) + float(self.state.vars.profit))
cena = await self.get_limitka_price()
try:
puvodni = self.state.vars.limitka
self.state.vars.limitka = await self.interface.repl(price=cena,orderid=self.state.vars.limitka,size=int(self.state.positions))
#odchyceni pripadne chyby na live
if self.state.vars.limitka == -1:
self.state.ilog(e="Zmena limitky neprobehla, vracime puvodni", msg=str(self.state.vars.limitka))
self.state.vars.limitka = puvodni
else:
self.state.vars.limitka_price = cena
self.state.ilog(e="Příchozí BUY notif - menime limitku", msg=o.status, status=o.status, orderid=str(o.id), limitka=str(self.state.vars.limitka), limtka_price=self.state.vars.limitka_price, size=int(self.state.positions), puvodni_limitka=str(puvodni))
except APIError as e:
self.state.ilog(e="API ERROR pri zmene limitky", msg=str(e), orderid=str(o.id), limitka=str(self.state.vars.limitka), limitka_price=self.state.vars.limitka_price, puvodni_limitka=str(puvodni))
#stejne parametry - stava se pri rychle obratce, nevadi
if e.code == 42210000: return 0,0
else:
print("Neslo nahradit profitku. Problem",str(e))
raise Exception(e)
async def orderUpdateSell(self, data: TradeUpdate):
self.state.ilog(e="Příchozí SELL notif", msg=data.order.status, trade=json.loads(json.dumps(data, default=json_serial)))
#PROFIT
#profit pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena
#naklady vypocteme z prumerne ceny, kterou mame v pozicich
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
sold_amount = data.qty * data.price
#podle prumerne ceny, kolik stalo toto mnozstvi
avg_costs = float(self.state.avgp) * float(data.qty)
if avg_costs == 0:
self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.")
avg_costs = sold_amount
trade_profit = (sold_amount - avg_costs)
self.state.profit += trade_profit
self.state.ilog(e=f"SELL notif - PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)}", msg=str(data.event), sold_amount=sold_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id))
#update pozic, v trade update je i pocet zbylych pozic
old_avgp = self.state.avgp
old_pos = self.state.positions
self.state.positions = int(data.position_qty)
if int(data.position_qty) == 0:
self.state.avgp = 0
self.state.ilog(e="SELL notifikace "+str(data.order.status), msg="update pozic", old_avgp=old_avgp, old_pos=old_pos, avgp=self.state.avgp, pos=self.state.positions, orderid=str(data.order.id))
#self.state.avgp, self.state.positions = self.interface.pos()
if data.event == TradeEvent.FILL or data.event == TradeEvent.CANCELED:
print("Příchozí SELL notifikace - complete FILL nebo CANCEL", data.event)
#muzeme znovu nakupovat, mazeme limitku, blockbuy a pendingbuys
#self.state.blockbuy = 0
#ic("notifikace sell mazeme limitku a update pozic")
#updatujeme pozice
self.state.avgp, self.state.positions = self.interface.pos()
#ic(self.state.avgp, self.state.positions)
self.state.vars.limitka = None
self.state.vars.limitka_price = None
self.state.vars.lastbuyindex = -5
self.state.vars.jevylozeno = 0
await self.state.cancel_pending_buys()
self.state.ilog(e="Příchozí SELL - FILL nebo CANCEL - mazeme limitku a pb", msg=data.order.status, orderid=str(data.order.id), pb=self.state.vars.pendingbuys)
#this parent method is called by strategy just once before waiting for first data
def strat_init(self):
#ic("strat INI function")
#lets connect method overrides
self.state.buy = self.buy
self.state.buy_l = self.buy_l
self.state.cancel_pending_buys = self.cancel_pending_buys
#overidden methods
def buy(self, size = None, repeat: bool = False):
print("overriden method to size&check maximum ")
if int(self.state.positions) >= self.state.vars.maxpozic:
self.state.ilog(e="buy Maxim mnozstvi naplneno", positions=self.state.positions)
print("max mnostvi naplneno")
return 0
if size is None:
sizer = self.state.vars.chunk
else:
sizer = size
self.state.blockbuy = 1
self.state.vars.lastbuyindex = self.state.bars['index'][-1]
self.state.ilog(e="send MARKET buy to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol))
return self.state.interface.buy(size=sizer)
def buy_l(self, price: float = None, size = None, repeat: bool = False):
print("entering overriden BUY")
if int(self.state.positions) >= self.state.vars.maxpozic:
self.state.ilog(e="buyl Maxim mnozstvi naplneno", price=price, size=size, curr_positions=self.state.positions)
return 0
if size is None: size=self.state.vars.chunk
if price is None: price=price2dec((self.state.interface.get_last_price(self.symbol)))
#ic(price)
print("odesilame LIMIT s cenou/qty", price, size)
self.state.ilog(e="send LIMIT buy to if", msg="S:"+str(size)+" P:"+str(price), price=price, size=size)
order = self.state.interface.buy_l(price=price, size=size)
print("ukladame pendingbuys")
self.state.vars.pendingbuys[str(order)]=price
self.state.blockbuy = 1
self.state.vars.lastbuyindex = self.state.bars['index'][-1]
#ic(self.state.blockbuy)
#ic(self.state.vars.lastbuyindex)
self.state.ilog(e="Odeslan buy_l a ulozeno do pb", order=str(order), pb=self.state.vars.pendingbuys)
async def cancel_pending_buys(self):
print("cancel pending buys called.")
self.state.ilog(e="Rusime pendingy", pb=self.state.vars.pendingbuys)
##proto v pendingbuys pridano str(), protoze UUIN nejde serializovat
##padalo na variable changed during iteration, pridano
if len(self.state.vars.pendingbuys)>0:
tmp = copy.deepcopy(self.state.vars.pendingbuys)
for key in tmp:
#ic(key)
#nejprve vyhodime z pendingbuys
self.state.vars.pendingbuys.pop(key, False)
res = self.interface.cancel(key)
self.state.ilog(e=f"Pendingy zrusen pro {key=}", orderid=str(key), res=str(res))
print("CANCEL PENDING BUYS RETURN", res)
self.state.vars.pendingbuys={}
self.state.vars.jevylozeno = 0
print("cancel pending buys end")
self.state.ilog(e="Dokončeno zruseni vsech pb", pb=self.state.vars.pendingbuys)
#kopie funkci co jsou v next jen async, nejak vymyslet, aby byly jen jedny
async def is_defensive_mode(self):
akt_pozic = int(self.state.positions)
max_pozic = int(self.state.vars.maxpozic)
def_mode_from = safe_get(self.state.vars, "def_mode_from")
if def_mode_from == None: def_mode_from = max_pozic/2
if akt_pozic >= int(def_mode_from):
self.state.ilog(e=f"DEFENSIVE MODE active {self.state.vars.def_mode_from=}", msg=self.state.positions)
return True
else:
self.state.ilog(e=f"STANDARD MODE active {self.state.vars.def_mode_from=}", msg=self.state.positions)
return False
async def get_limitka_price(self):
def_profit = safe_get(self.state.vars, "def_profit")
if def_profit == None: def_profit = self.state.vars.profit
cena = float(self.state.avgp)
if await self.is_defensive_mode():
return price2dec(cena+get_tick(cena,float(def_profit)))
else:
return price2dec(cena+get_tick(cena,float(self.state.vars.profit)))

View File

@ -12,7 +12,7 @@ import os
from v2realbot.common.model import StrategyInstance, Runner, RunArchive, RunArchiveDetail
from typing import List
import tomli
from v2realbot.config import DATA_DIR, QUIET_MODE
from v2realbot.config import DATA_DIR, QUIET_MODE,NORMALIZED_TICK_BASE_PRICE
import requests
from uuid import UUID
from enum import Enum
@ -23,6 +23,21 @@ import numpy as np
import pandas as pd
from collections import deque
def get_tick(price: float, normalized_ticks: float = 0.01):
"""
prevede normalizovany tick na tick odpovidajici vstupni cene
vysledek je zaokoruhleny na 2 des.mista
u cen pod 30, vrací 0.01. U cen nad 30 vrací pomerne zvetsene,
"""
if price<NORMALIZED_TICK_BASE_PRICE:
return normalized_ticks
else:
#ratio of price vs base price
ratio = price/NORMALIZED_TICK_BASE_PRICE
return price2dec(ratio*normalized_ticks)
def safe_get(collection, key, default=None):
"""Get values from a collection without raising errors"""