diff --git a/alpacawebsokcet.py b/alpacawebsokcet.py new file mode 100644 index 0000000..3015fc6 --- /dev/null +++ b/alpacawebsokcet.py @@ -0,0 +1,14 @@ +from alpaca.data.live.stock import StockDataStream +from v2realbot.config import ACCOUNT2_PAPER_API_KEY, ACCOUNT2_PAPER_SECRET_KEY, ACCOUNT2_PAPER_FEED + +# keys required +stock_stream = StockDataStream(ACCOUNT2_PAPER_API_KEY, ACCOUNT2_PAPER_SECRET_KEY, raw_data=True, websocket_params={}, feed=ACCOUNT2_PAPER_FEED) + +# async handler +async def trade_data_handler(data): + # quote data will arrive here + print(data) + +stock_stream.subscribe_trades(trade_data_handler, "BAC") + +stock_stream.run() \ No newline at end of file diff --git a/testy/archive/alpacawebsocketsimple.py b/testy/archive/alpacawebsocketsimple.py index 10eee3e..5fc574f 100644 --- a/testy/archive/alpacawebsocketsimple.py +++ b/testy/archive/alpacawebsocketsimple.py @@ -4,7 +4,7 @@ from alpaca.trading.stream import TradingStream from alpaca.data.enums import DataFeed from config import API_KEY, SECRET_KEY, MAX_BATCH_SIZE from datetime import datetime -import mplfinance as mpf +#import mplfinance as mpf import matplotlib.pyplot as plt import threading # pripadne parametry pro request diff --git a/testy/output_metriky_tradeList.py b/testy/output_metriky_tradeList.py index 99b18dd..58ecaa9 100644 --- a/testy/output_metriky_tradeList.py +++ b/testy/output_metriky_tradeList.py @@ -1039,6 +1039,9 @@ res = dict(zip(max_positions['qty'], max_positions['count'])) ouput_dict=dict(maxpos=str(res)) +all_summary = trade_df.describe(include='all') +print(all_summary) + print(ouput_dict) #a = trade_df.value_counts(subset=['position_qty']) #print(max_positions.to_dict('records')) diff --git a/testy/threadclassestest.py b/testy/threadclassestest.py index 5895a37..cc505f1 100644 --- a/testy/threadclassestest.py +++ b/testy/threadclassestest.py @@ -1,7 +1,7 @@ from threading import Thread, current_thread import threading from alpaca.data.live import StockDataStream, CryptoDataStream -from v2realbot.config import API_KEY, SECRET_KEY, MAX_BATCH_SIZE, PAPER +from v2realbot.config import ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, ACCOUNT1_PAPER_MAX_BATCH_SIZE import queue from alpaca.data.enums import DataFeed from typing_extensions import Any @@ -27,7 +27,7 @@ vlakno je vzdy pouze jedno, nicmene instancovani teto tridy je kvuli stejnemu ch s ostatnimi streamery (v budoucnu mozna predelat na dedicated streamer a shared streamer) """"" class WS_Stream(Thread): - client = CryptoDataStream(API_KEY, SECRET_KEY, raw_data=True, websocket_params={}) + client = CryptoDataStream(ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, raw_data=True, websocket_params={}) _streams = [] lock = threading.Lock() @@ -124,12 +124,12 @@ class WS_Stream(Thread): # novy ws stream - vždy jednom vláknu obj= WS_Stream(name="jednicka") q1 = queue.Queue() -stream1 = TradeAggregator2Queue(symbol="BTC/USD",queue=q1,rectype=RecordType.BAR,timeframe=15,update_ltp=False,align=StartBarAlign.ROUND,mintick = 0, mode = Mode.LIVE) +stream1 = TradeAggregator2Queue(symbol="BTC/USD",queue=q1,rectype=RecordType.BAR,timeframe=1,update_ltp=False,align=StartBarAlign.ROUND,mintick = 0) obj.add_stream(stream1) print("1", WS_Stream._streams) # novy ws stream - vždy jednom vláknu obj2= WS_Stream("dvojka") -stream2 = TradeAggregator2Queue(symbol="ETH/USD",queue=q1,rectype=RecordType.BAR,timeframe=5,update_ltp=False,align=StartBarAlign.ROUND,mintick = 0, mode = Mode.LIVE) +stream2 = TradeAggregator2Queue(symbol="ETH/USD",queue=q1,rectype=RecordType.BAR,timeframe=1,update_ltp=False,align=StartBarAlign.ROUND,mintick = 0) obj2.add_stream(stream2) print("2", WS_Stream._streams) obj.start() @@ -141,7 +141,7 @@ print("po startu druheho") time.sleep(2) print("pridavame treti") obj3 = WS_Stream(name="trojka") -stream3 = TradeAggregator2Queue(symbol="BTC/USD",queue=q1,rectype=RecordType.BAR,timeframe=1,update_ltp=False,align=StartBarAlign.ROUND,mintick = 0, mode = Mode.LIVE) +stream3 = TradeAggregator2Queue(symbol="BTC/USD",queue=q1,rectype=RecordType.BAR,timeframe=1,update_ltp=False,align=StartBarAlign.ROUND,mintick = 0) obj3.add_stream(stream3) obj3.start() print(WS_Stream._streams) diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py new file mode 100644 index 0000000..8c0c3c2 --- /dev/null +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -0,0 +1,1065 @@ +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.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL +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.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType +from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, get_tick, round2five, is_open_rush, is_close_rush, eval_cond_dict, Average, crossed_down, crossed_up, crossed, is_pivot +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__)))) +"""" +Využívá: StrategyClassicSL + +Klasická obousměrná multibuysignal strategie se stoplos. +Používá pouze market order, hlídá profit a stoploss. + +Ve dvou fázích: 1) search and create prescriptions 2) evaluate prescriptions + +list(prescribedTrade) + +prescribedTrade: +- validfrom +- status .READY, ACTIVE, finished) +- direction (long/short) +- entry price: +- stoploss: (fixed, trailing) + +Hlavní loop: +- indikátory + +- if empty positions (avgp=0): + - no prescribed trades + + - any prescribed trade? + - eval input + + - eval eligible entries (do buy/sell) + +- if positions (avgp <>0) + - eval exit (standard, forced by eod) + - if not exit - eval optimalizations + +""" + +def next(data, state: StrategyState): + print(10*"*","NEXT START",10*"*") + # important vars state.avgp, state.positions, state.vars, data + + # region Common Subfunction + def populate_cbar_rsi_indicator(): + #CBAR RSI indicator + options = safe_get(state.vars.indicators, 'crsi', None) + if options is None: + state.ilog(e="No options for crsi in stratvars") + return + + try: + crsi_length = int(safe_get(options, 'crsi_length', 14)) + source = state.cbar_indicators.tick_price #[-rsi_length:] #state.bars.vwap + crsi_res = rsi(source, crsi_length) + crsi_value = crsi_res[-1] + if str(crsi_value) == "nan": + crsi_value = 0 + state.cbar_indicators.CRSI[-1]=crsi_value + #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"CRSI {crsi_length=} necháváme 0", message=str(e)+format_exc()) + #state.indicators.RSI14[-1]=0 + + def value_or_indicator(value): + #preklad direktivy podle typu, pokud je int anebo float - je to primo hodnota + #pokud je str, jde o indikator a dotahujeme posledni hodnotu z nej + if isinstance(value, (int, float)): + return value + elif isinstance(value, str): + try: + #pokud existuje MA bereme MA jinak standard + ret = get_source_or_MA(indicator=value)[-1] + state.ilog(e=f"Pro porovnani bereme posledni hodnotu {ret} z indikatoru {value}") + except Exception as e : + ret = 0 + state.ilog(e=f"Neexistuje indikator s nazvem {value} vracime 0" + str(e) + format_exc()) + return ret + + #funkce vytvori podminky (bud pro AND/OR) z pracovniho dict + def create_conditions_from_directives(work_dict, cond_type): + cond = {} + cond[cond_type] = {} + for indname, directive, value in work_dict[cond_type]: + #direktivy zobecnime ve tvaru prefix_ACTION + # ACTIONS = is_above, is_below, is_falling, is_rising, crossed_up, crossed_down, is_pivot_a, is_pivot_v + + #OBECNE DIREKTIVY - REUSOVATELNE + if directive.endswith("above"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = get_source_or_MA(indname)[-1] > value_or_indicator(value) + elif directive.endswith("below"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = get_source_or_MA(indname)[-1] < value_or_indicator(value) + elif directive.endswith("falling"): + if directive.endswith("not_falling"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = not isfalling(get_source_or_MA(indname),value) + else: + cond[cond_type][directive+"_"+indname+"_"+str(value)] = isfalling(get_source_or_MA(indname),value) + elif directive.endswith("rising"): + if directive.endswith("not_rising"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = not isrising(get_source_or_MA(indname),value) + else: + cond[cond_type][directive+"_"+indname+"_"+str(value)] = isrising(get_source_or_MA(indname),value) + elif directive.endswith("crossed_down"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = buy_if_crossed_down(indname, value_or_indicator(value)) + elif directive.endswith("crossed_up"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = buy_if_crossed_up(indname, value_or_indicator(value)) + #nefunguje moc dobre + elif directive.endswith("crossed"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = buy_if_crossed_down(indname, value_or_indicator(value)) or buy_if_crossed_up(indname, value_or_indicator(value)) + elif directive.endswith("pivot_a"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = is_pivot(source=get_source_or_MA(indname), leg_number=value, type="A") + elif directive.endswith("pivot_v"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = is_pivot(source=get_source_or_MA(indname), leg_number=value, type="V") + + #PRIPADNE DALSI SPECIFICKE ZDE + # elif directive == "buy_if_necospecifckeho": + # pass + + return cond + + #tato funkce vytvori dictionary typu podminek (OR/AND) + # z indikatoru, ktere obsahuji direktivami daneho typu(buy_if, dont_buy_when) + # v tuplu (nazevind,direktiva,hodnota) + # do OR jsou dane i bez prefixu + # {'AND': [('nazev indikatoru', 'nazev direktivy', 'hodnotadirektivy')], 'OR': []} + def get_work_dict_with_directive(starts_with: str): + reslist = dict(AND=[], OR=[]) + + for indname, indsettings in state.vars.indicators.items(): + for option,value in indsettings.items(): + if option.startswith(starts_with): + reslist["OR"].append((indname, option, value)) + if option == "AND": + #vsechny buy direktivy, ktere jsou pod AND + for key, val in value.items(): + if key.startswith(starts_with): + reslist["AND"].append((indname, key, val)) + if option == "OR" : + #vsechny buy direktivy, ktere jsou pod OR + for key, val in value.items(): + if key.startswith(starts_with): + reslist["OR"].append((indname, key, val)) + return reslist + + def get_source_or_MA(indicator): + #pokud ma, pouzije MAcko, pokud ne tak standardni indikator + try: + return state.indicators[indicator+"MA"] + except KeyError: + return state.indicators[indicator] + # #vrati true pokud dany indikator krosnul obema smery + # def buy_if_crossed(indicator, value): + # res = crossed(threshold=value, list=get_source_or_MA(indicator)) + # state.ilog(e=f"buy_if_crossed {indicator} {value} {res}") + # return res + + #vrati true pokud dany indikator prekrocil threshold dolu + def buy_if_crossed_down(indicator, value): + res = crossed_down(threshold=value, list=get_source_or_MA(indicator)) + state.ilog(e=f"buy_if_crossed_down {indicator} {value} {res}") + return res + + #vrati true pokud dany indikator prekrocil threshold nahoru + def buy_if_crossed_up(indicator, value): + res = crossed_up(threshold=value, list=get_source_or_MA(indicator)) + state.ilog(e=f"buy_if_crossed_up {indicator} {value} {res}") + return res + + def populate_cbar_tick_price_indicator(): + 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: + + #tick_price = round2five(data['close']) + tick_price = data['close'] + tick_delta_volume = data['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=}", prev_price=state.vars.last_tick_price, prev_volume=state.vars.last_tick_volume) + + state.vars.last_tick_price = tick_price + state.vars.last_tick_volume = data['volume'] + + def get_last_ind_vals(): + last_ind_vals = {} + #print(state.indicators.items()) + for key in state.indicators: + if key != 'time': + last_ind_vals[key] = state.indicators[key][-6:] + + for key in state.cbar_indicators: + if key != 'time': + last_ind_vals[key] = state.cbar_indicators[key][-6:] + + # for key in state.secondary_indicators: + # if key != 'time': + # last_ind_vals[key] = state.secondary_indicators[key][-5:] + + return last_ind_vals + + def populate_dynamic_indicators(): + #pro vsechny indikatory, ktere maji ve svych stratvars TYPE, poustime populaci daneho typu indikaotru + for indname, indsettings in state.vars.indicators.items(): + for option,value in indsettings.items(): + if option == "type": + populate_dynamic_indicator_hub(type=value, name=indname) + + def populate_dynamic_indicator_hub(type, name): + if type == "slope": + populate_dynamic_slope_indicator(name = name) + #slope variant with continuous Left Point + elif type == "slopeLP": + populate_dynamic_slopeLP_indicator(name = name) + elif type == "RSI": + populate_dynamic_RSI_indicator(name = name) + elif type == "EMA": + populate_dynamic_ema_indicator(name = name) + else: + return + + #EMA INDICATOR + # type = EMA, source = [close, vwap, hlcc4], length = [14], on_confirmed_only = [true, false] + def populate_dynamic_ema_indicator(name): + ind_type = "EMA" + options = safe_get(state.vars.indicators, name, None) + if options is None: + state.ilog(e=f"No options for {name} in stratvars") + return + + if safe_get(options, "type", False) is False or safe_get(options, "type", False) != ind_type: + state.ilog(e="Type error") + return + + #poustet kazdy tick nebo jenom na confirmed baru (on_confirmed_only = true) + on_confirmed_only = safe_get(options, 'on_confirmed_only', False) + req_source = safe_get(options, 'source', 'vwap') + if req_source not in ["close", "vwap","hlcc4"]: + state.ilog(e=f"Unknown source error {req_source} for {name}") + return + ema_length = int(safe_get(options, "length",14)) + if on_confirmed_only is False or (on_confirmed_only is True and data['confirmed']==1): + try: + source = state.bars[req_source][-ema_length:] + #if len(source) > ema_length: + ema_value = ema(source, ema_length) + val = round(ema_value[-1],4) + state.indicators[name][-1]= val + #state.indicators[name][-1]= round2five(val) + state.ilog(e=f"IND {name} EMA {val} {ema_length=}") + #else: + # state.ilog(e=f"IND {name} EMA necháváme 0", message="not enough source data", source=source, ema_length=ema_length) + except Exception as e: + state.ilog(e=f"IND ERROR {name} EMA necháváme 0", message=str(e)+format_exc()) + + #RSI INDICATOR + # type = RSI, source = [close, vwap, hlcc4], rsi_length = [14], MA_length = int (optional), on_confirmed_only = [true, false] + # pokud existuje MA, vytvarime i stejnojnojmenny MAcko + def populate_dynamic_RSI_indicator(name): + ind_type = "RSI" + options = safe_get(state.vars.indicators, name, None) + if options is None: + state.ilog(e=f"No options for {name} in stratvars") + return + + if safe_get(options, "type", False) is False or safe_get(options, "type", False) != ind_type: + state.ilog(e="Type error") + return + + #poustet kazdy tick nebo jenom na confirmed baru (on_confirmed_only = true) + on_confirmed_only = safe_get(options, 'on_confirmed_only', False) + req_source = safe_get(options, 'source', 'vwap') + if req_source not in ["close", "vwap","hlcc4"]: + state.ilog(e=f"Unknown source error {req_source} for {name}") + return + rsi_length = int(safe_get(options, "RSI_length",14)) + rsi_MA_length = safe_get(options, "MA_length", None) + + if on_confirmed_only is False or (on_confirmed_only is True and data['confirmed']==1): + try: + source = state.bars[req_source] + #cekame na dostatek dat + if len(source) > rsi_length: + rsi_res = rsi(source, rsi_length) + rsi_value = round(rsi_res[-1],4) + state.indicators[name][-1]=rsi_value + state.ilog(e=f"IND {name} RSI {rsi_value}") + + if rsi_MA_length is not None: + src = state.indicators[name][-rsi_MA_length:] + rsi_MA_res = ema(src, rsi_MA_length) + rsi_MA_value = round(rsi_MA_res[-1],4) + state.indicators[name+"MA"][-1]=rsi_MA_value + state.ilog(e=f"IND {name} RSIMA {rsi_MA_value}") + + else: + state.ilog(e=f"IND {name} RSI necháváme 0", message="not enough source data", source=source, rsi_length=rsi_length) + except Exception as e: + state.ilog(e=f"IND ERROR {name} RSI necháváme 0", message=str(e)+format_exc()) + + #SLOPE LP + def populate_dynamic_slopeLP_indicator(name): + ind_type = "slopeLP" + options = safe_get(state.vars.indicators, name, None) + if options is None: + state.ilog(e=f"No options for {name} in stratvars") + return + + if safe_get(options, "type", False) is False or safe_get(options, "type", False) != ind_type: + state.ilog(e="Type error") + return + + #poustet kazdy tick nebo jenom na confirmed baru (on_confirmed_only = true) + on_confirmed_only = safe_get(options, 'on_confirmed_only', False) + + #pocet baru po kterých se levy bod z BUY prepne opet na standadni vypocet (prumer) + #kdyz se dlouho neprodává a cena nejde dolu, tak aby se nezastavilo nakupovani + back_to_standard_after = int(safe_get(options, 'back_to_standard_after', 0)) + + #slopeLP INDIKATOR + #levy bod je nejdrive standardne automaticky vypočtený podle hodnoty lookbacku (např. -8, offset 4) + #při nákupu se BUY POINT se stává levým bodem (až do doby kdy není lookbackprice nižší, pak pokračuje lookbackprice) + #při prodeji se SELL POINT se stává novým levým bodem (až do doby kdy není lookbackprice vyšší, pak pokračuje lookbackprice) + #zatím implementovat prvni část (mimo části ..až do doby) - tu pak dodelat podle vysledku, pripadne ji neimplementovat vubec a misto toho + #udelat slope RESET pri dosazeni urciteho pozitivniho nebo negativni slopu + + #zkusime nejdriv: levy bod automat, po nakupu je levy bod cena nakupu + + #VYSTUPY: state.indicators[name], + # state.indicators[nameMA] + # statický indikátor (angle) - stejneho jmena pro vizualizaci uhlu + + if on_confirmed_only is False or (on_confirmed_only is True and data['confirmed']==1): + try: + #slow_slope = 99 + slope_lookback = safe_get(options, 'slope_lookback', 100) + minimum_slope = safe_get(options, 'minimum_slope', 25) + maximum_slope = safe_get(options, "maximum_slope",0.9) + lookback_offset = safe_get(options, 'lookback_offset', 25) + + #typ leveho bodu [lastbuy - cena posledniho nakupu, baropen - cena otevreni baru] + leftpoint = safe_get(options, 'leftpoint', "lastbuy") + + #lookback has to be even + if lookback_offset % 2 != 0: + lookback_offset += 1 + + if leftpoint == "lastbuy": + if len(state.bars.close) > (slope_lookback + lookback_offset): + #test prumer nejvyssi a nejnizsi hodnoty + # if name == "slope": + + #levy bod bude vzdy vzdaleny o slope_lookback + #ten bude prumerem hodnot lookback_offset a to tak ze polovina offsetu z kazde strany + array_od = slope_lookback + int(lookback_offset/2) + array_do = slope_lookback - int(lookback_offset/2) + lookbackprice_array = state.bars.vwap[-array_od:-array_do] + #cas nastavujeme vzdy podle nastaveni (zatim) + lookbacktime = state.bars.time[-slope_lookback] + + #pokud mame aktivni pozice, nastavime lookbackprice a time podle posledniho tradu + #pokud se ale dlouho nenakupuje (uplynulo od posledniho nakupu vic nez back_to_standard_after baru), tak se vracime k prumeru + if state.avgp > 0 and state.bars.index[-1] < int(state.vars.lastbuyindex)+back_to_standard_after: + lb_index = -1 - (state.bars.index[-1] - int(state.vars.lastbuyindex)) + lookbackprice = state.bars.vwap[lb_index] + state.ilog(e=f"IND {name} slope {leftpoint}- LEFT POINT OVERRIDE bereme ajko cenu lastbuy {lookbackprice=} {lookbacktime=} {lb_index=}") + else: + #dame na porovnani jen prumer + lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3) + #lookbackprice = round((min(lookbackprice_array)+max(lookbackprice_array))/2,3) + # else: + # #puvodni lookback a od te doby dozadu 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) + + lookbacktime = state.bars.time[-slope_lookback] + state.ilog(e=f"IND {name} slope {leftpoint} - LEFT POINT STANDARD {lookbackprice=} {lookbacktime=}") + else: + #kdyz neni dostatek hodnot, pouzivame jako levy bod open hodnotu close[0] + lookbackprice = state.bars.close[0] + lookbacktime = state.bars.time[0] + state.ilog(e=f"IND {name} slope - not enough data bereme left bod open", slope_lookback=slope_lookback) + elif leftpoint == "baropen": + lookbackprice = state.bars.open[-1] + lookbacktime = state.bars.time[-1] + state.ilog(e=f"IND {name} slope {leftpoint}- bereme cenu bar OPENu ", lookbackprice=lookbackprice, lookbacktime=lookbacktime) + else: + state.ilog(e=f"IND {name} UNKNOW LEFT POINT TYPE {leftpoint=}") + + #výpočet úhlu - a jeho normalizace + slope = ((state.bars.close[-1] - lookbackprice)/lookbackprice)*100 + slope = round(slope, 4) + state.indicators[name][-1]=slope + + #angle ze slope + state.statinds[name] = dict(time=state.bars.updated[-1], price=state.bars.close[-1], lookbacktime=lookbacktime, lookbackprice=lookbackprice, minimum_slope=minimum_slope, maximum_slope=maximum_slope) + + #slope MA vyrovna vykyvy ve slope + slope_MA_length = safe_get(options, 'MA_length', None) + slopeMA = None + last_slopesMA = None + #pokud je nastavena MA_length tak vytvarime i MAcko dane delky na tento slope + if slope_MA_length is not None: + source = state.indicators[name][-slope_MA_length:] + slopeMAseries = ema(source, slope_MA_length) #state.bars.vwap + slopeMA = round(slopeMAseries[-1],5) + state.indicators[name+"MA"][-1]=slopeMA + last_slopesMA = state.indicators[name+"MA"][-10:] + + state.ilog(e=f"{name=} {slope=} {slopeMA=}", msg=f"{lookbackprice=}", lookbackoffset=lookback_offset, minimum_slope=minimum_slope, last_slopes=state.indicators[name][-10:], last_slopesMA=last_slopesMA) + #dale pracujeme s timto MAckovanym slope + #slope = slopeMA + + except Exception as e: + print(f"Exception in {name} slope Indicator section", str(e)) + state.ilog(e=f"EXCEPTION in {name}", msg="Exception in slope Indicator section" + str(e) + format_exc()) + + def populate_dynamic_slope_indicator(name): + options = safe_get(state.vars.indicators, name, None) + if options is None: + state.ilog(e="No options for slow slope in stratvars") + return + + if safe_get(options, "type", False) is False or safe_get(options, "type", False) != "slope": + state.ilog(e="Type error") + return + + #poustet kazdy tick nebo jenom na confirmed baru (on_confirmed_only = true) + on_confirmed_only = safe_get(options, 'on_confirmed_only', False) + + #SLOW 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. + #VYSTUPY: state.indicators[name], + # state.indicators[nameMA] + # statický indikátor (angle) - stejneho jmena pro vizualizaci uhlu + + if on_confirmed_only is False or (on_confirmed_only is True and data['confirmed']==1): + try: + #slow_slope = 99 + slope_lookback = safe_get(options, 'slope_lookback', 100) + minimum_slope = safe_get(options, 'minimum_slope', 25) + maximum_slope = safe_get(options, "maximum_slope",0.9) + lookback_offset = safe_get(options, 'lookback_offset', 25) + + #lookback has to be even + if lookback_offset % 2 != 0: + lookback_offset += 1 + + #TBD pripdadne /2 + if len(state.bars.close) > (slope_lookback + lookback_offset): + #test prumer nejvyssi a nejnizsi hodnoty + # if name == "slope": + + #levy bod bude vzdy vzdaleny o slope_lookback + #ten bude prumerem hodnot lookback_offset a to tak ze polovina offsetu z kazde strany + array_od = slope_lookback + int(lookback_offset/2) + array_do = slope_lookback - int(lookback_offset/2) + lookbackprice_array = state.bars.vwap[-array_od:-array_do] + + #dame na porovnani jen prumer + lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3) + #lookbackprice = round((min(lookbackprice_array)+max(lookbackprice_array))/2,3) + # else: + # #puvodni lookback a od te doby dozadu 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) + + lookbacktime = state.bars.time[-slope_lookback] + else: + #kdyz neni dostatek hodnot, pouzivame jako levy bod open hodnotu close[0] + lookbackprice = state.bars.close[0] + lookbacktime = state.bars.time[0] + state.ilog(e=f"IND {name} slope - not enough data bereme left bod open", slope_lookback=slope_lookback) + + #výpočet úhlu - a jeho normalizace + slope = ((state.bars.close[-1] - lookbackprice)/lookbackprice)*100 + slope = round(slope, 4) + state.indicators[name][-1]=slope + + #angle je ze slope, ale pojmenovavame ho podle MA + state.statinds[name] = dict(time=state.bars.time[-1], price=state.bars.close[-1], lookbacktime=lookbacktime, lookbackprice=lookbackprice, minimum_slope=minimum_slope, maximum_slope=maximum_slope) + + #slope MA vyrovna vykyvy ve slope + slope_MA_length = safe_get(options, 'MA_length', None) + slopeMA = None + last_slopesMA = None + #pokud je nastavena MA_length tak vytvarime i MAcko dane delky na tento slope + if slope_MA_length is not None: + source = state.indicators[name][-slope_MA_length:] + slopeMAseries = ema(source, slope_MA_length) #state.bars.vwap + slopeMA = round(slopeMAseries[-1],4) + state.indicators[name+"MA"][-1]=slopeMA + last_slopesMA = state.indicators[name+"MA"][-10:] + + state.ilog(e=f"{name=} {slope=} {slopeMA=}", msg=f"{lookbackprice=}", lookbackoffset=lookback_offset, minimum_slope=minimum_slope, last_slopes=state.indicators[name][-10:], last_slopesMA=last_slopesMA) + #dale pracujeme s timto MAckovanym slope + #slope = slopeMA + + except Exception as e: + print(f"Exception in {name} slope Indicator section", str(e)) + state.ilog(e=f"EXCEPTION in {name}", msg="Exception in slope Indicator section" + str(e) + format_exc()) + + def process_delta(): + #PROCESs DELTAS - to function + last_update_delta = round((float(data['updated']) - state.vars.last_update_time),6) if state.vars.last_update_time != 0 else 0 + state.vars.last_update_time = float(data['updated']) + + if len(state.vars.last_50_deltas) >=50: + state.vars.last_50_deltas.pop(0) + state.vars.last_50_deltas.append(last_update_delta) + avg_delta = Average(state.vars.last_50_deltas) + + state.ilog(e=f"---{data['index']}-{conf_bar}--delta:{last_update_delta}---AVGdelta:{avg_delta}") + + conf_bar = data['confirmed'] + process_delta() + #kroky pro CONFIRMED BAR only + if conf_bar == 1: + #logika pouze pro potvrzeny bar + state.ilog(e="BAR potvrzeny") + + #pri potvrzem CBARu nulujeme counter volume pro tick based indicator + state.vars.last_tick_volume = 0 + state.vars.next_new = 1 + #kroky pro CONTINOUS TICKS only + else: + #CBAR INDICATOR pro tick price a deltu VOLUME + populate_cbar_tick_price_indicator() + #TBD nize predelat na typizovane RSI (a to jak na urovni CBAR tak confirmed) + populate_cbar_rsi_indicator() + + #populate indicators, that have type in stratvars.indicators + populate_dynamic_indicators() + # endregion + + # region Subfunction + #toto upravit na take profit + def sell_protection_enabled(): + options = safe_get(state.vars, 'sell_protection', None) + if options is None: + state.ilog(e="No options for sell protection in stratvars") + return False + + disable_sell_proteciton_when = dict(AND=dict(), OR=dict()) + + #preconditions + disable_sell_proteciton_when['disabled_in_config'] = safe_get(options, 'enabled', False) is False + #too good to be true (maximum profit) + #disable_sell_proteciton_when['tgtbt_reached'] = safe_get(options, 'tgtbt', False) is False + disable_sell_proteciton_when['disable_if_positions_above'] = int(safe_get(options, 'disable_if_positions_above', 0)) < state.positions + + #testing preconditions + result, conditions_met = eval_cond_dict(disable_sell_proteciton_when) + if result: + state.ilog(e=f"SELL_PROTECTION DISABLED by {conditions_met}", **conditions_met) + return False + + work_dict_dont_sell_if = get_work_dict_with_directive(starts_with="dont_sell_if") + state.ilog(e=f"SELL PROTECTION work_dict", message=work_dict_dont_sell_if) + + or_cond = create_conditions_from_directives(work_dict_dont_sell_if, "OR") + result, conditions_met = eval_cond_dict(or_cond) + state.ilog(e=f"SELL PROTECTION =OR= {result}", **conditions_met) + if result: + return True + + #OR neprosly testujeme AND + and_cond = create_conditions_from_directives(work_dict_dont_sell_if, "AND") + result, conditions_met = eval_cond_dict(and_cond) + state.ilog(e=f"SELL PROTECTION =AND= {result}", **conditions_met) + return result + + #PUVODNI NASTAVENI - IDENTIFIKOVAce rustoveho MOMENTA - pokud je momentum, tak prodávat později + + # #pokud je slope too high, pak prodavame jakmile slopeMA zacne klesat, napr. 4MA (TODO 3) + + # #TODO zkusit pro pevny profit, jednoduse pozdrzet prodej - dokud tick_price roste nebo se drzi tak neprodavat, pokud klesne prodat + # #mozna mit dva mody - pri vetsi volatilite pouzivat momentum, pri mensi nebo kdyz potrebuju pryc, tak prodat hned + + #puvodni nastaveni + #slopeMA_rising = 2 + #rsi_not_falling = 3 + + # #toto docasne pryc dont_sell_when['slope_too_high'] = slope_too_high() and not isfalling(state.indicators.slopeMA,4) + # dont_sell_when['AND']['slopeMA_rising'] = isrising(state.indicators.slopeMA,safe_get(options, 'slopeMA_rising', 2)) + # dont_sell_when['AND']['rsi_not_falling'] = not isfalling(state.indicators.RSI14,safe_get(options, 'rsi_not_falling',3)) + # #dont_sell_when['rsi_dont_buy'] = state.indicators.RSI14[-1] > safe_get(state.vars, "rsi_dont_buy_above",50) + + # result, conditions_met = eval_cond_dict(dont_sell_when) + # if result: + # state.ilog(e=f"SELL_PROTECTION {conditions_met} enabled") + # return result + + def get_profit_target_price(): + def_profit = safe_get(state.vars, "def_profit",state.vars.profit) + cena = float(state.avgp) + return price2dec(cena+get_tick(cena,float(state.vars.profit)),3) if state.positions > 0 else price2dec(cena-get_tick(cena,float(state.vars.profit)),3) + + def get_max_profit_price(): + max_profit = float(safe_get(state.vars, "max_profit",0.03)) + cena = float(state.avgp) + return price2dec(cena+get_tick(cena,max_profit),3) if state.positions > 0 else price2dec(cena-get_tick(cena,max_profit),3) + + def eval_close_position(): + curr_price = float(data['close']) + state.ilog(e="Eval CLOSE", price=curr_price, pos=state.positions, avgp=state.avgp, pending=state.vars.pending, activeTrade=str(state.vars.activeTrade)) + + if int(state.positions) != 0 and float(state.avgp)>0 and state.vars.sell_in_progress is False: + goal_price = get_profit_target_price() + max_price = get_max_profit_price() + state.ilog(e=f"Goal price {goal_price} max price {max_price}") + + #close position handlign + + #mame short pozice + if int(state.positions) < 0: + #EOD - TBD + + #SL + if curr_price > state.vars.activeTrade.stoploss_value: + state.ilog(e=f"STOPLOSS reached on SHORT", curr_price=curr_price, trade=state.vars.activeTrade) + res = state.buy(size=abs(state.positions)) + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation STOPLOSS BUY {res}") + state.vars.pending = True + state.vars.activeTrade = None + return + #PROFIT + if curr_price<=goal_price: + #TODO cekat az slope prestane intenzivn erust, necekat az na klesani + #TODO mozna cekat na nejaky signal RSI + #TODO pripadne pokud dosahne TGTBB prodat ihned + max_price_signal = curr_price<=max_price + #OPTIMALIZACE pri stoupajícím angle + if max_price_signal or sell_protection_enabled() is False: + res = state.buy(size=abs(state.positions)) + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation PROFIT BUY {res}") + state.vars.pending = True + state.vars.activeTrade = None + state.ilog(e=f"market SELL was sent {curr_price=} {max_price_signal=}", positions=state.positions, avgp=state.avgp, sellinprogress=state.vars.sell_in_progress) + return + #mame long + elif int(state.positions) > 0: + #EOD - TBD + + #SL + if curr_price > state.vars.activeTrade.stoploss_value: + state.ilog(e=f"STOPLOSS reached on LONG", curr_price=curr_price, trade=state.vars.activeTrade) + res = state.sell(size=state.positions) + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation STOPLOSS SELL {res}") + state.vars.pending = True + return + + #PROFTI + if curr_price>=goal_price: + #TODO cekat az slope prestane intenzivn erust, necekat az na klesani + #TODO mozna cekat na nejaky signal RSI + #TODO pripadne pokud dosahne TGTBB prodat ihned + max_price_signal = curr_price>=max_price + #OPTIMALIZACE pri stoupajícím angle + if max_price_signal or sell_protection_enabled() is False: + res = state.sell(size=state.positions) + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation PROFIT SELL {res}") + state.vars.pending = True + state.ilog(e=f"market SELL was sent {curr_price=} {max_price_signal=}", positions=state.positions, avgp=state.avgp, sellinprogress=state.vars.sell_in_progress) + return + + def execute_prescribed_trades(): + ##evaluate prescribed trade, prvni eligible presuneme do activeTrade, zmenime stav and vytvorime objednavky + + if state.vars.activeTrade is not None or len(state.vars.prescribedTrades) == 0: + return + #evaluate long (price/market) + state.ilog(e="evaluating prescr trades", msg=state.vars.prescribedTrades) + for trade in state.vars.prescribedTrades: + if trade.status == TradeStatus.READY and trade.direction == TradeDirection.LONG and (trade.entry_price is None or trade.entry_price >= data['close']): + trade.status = TradeStatus.ACTIVATED + state.ilog(e=f"evaluaed SHORT {str(trade)}", msg=state.vars.prescribedTrades) + state.vars.activeTrade = trade + break + #evaluate shorts + if not state.vars.activeTrade: + for trade in state.vars.prescribedTrades: + if trade.status == TradeStatus.READY and trade.direction == TradeDirection.SHORT and (trade.entry_price is None or trade.entry_price <= data['close']): + state.ilog(e=f"evaluaed LONG {str(trade)}", msg=state.vars.prescribedTrades) + trade.status = TradeStatus.ACTIVATED + state.vars.activeTrade = trade + break + + #odeslani ORDER + NASTAVENI STOPLOSS (zatim hardcoded) + if state.vars.activeTrade: + if state.vars.activeTrade.direction == TradeDirection.LONG: + state.ilog(e="odesilame LONG ORDER", msg=state.vars.activeTrade) + res = state.buy(size=state.vars.chunk) + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation LONG {res}") + if state.vars.activeTrade.stoploss_value is None: + state.vars.activeTrade.stoploss_value = data['close'] - 0.09 + state.vars.pending = True + elif state.vars.activeTrade.direction == TradeDirection.SHORT: + state.ilog(e="odesilame SHORT ORDER", msg=state.vars.activeTrade) + res = state.sell(size=state.vars.chunk) + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation SHORT {res}") + if state.vars.activeTrade.stoploss_value is None: + state.vars.activeTrade.stoploss_value = float(data['close']) + 0.09 + state.vars.pending = True + else: + state.ilog(e="unknow direction") + state.vars.activeTrade = None + + def execute_signal_generator_plugin(name): + if name == "asr": + execute_asr() + + #vstupni signal pro asr + def execute_asr(): + pass + + #preconditions and conditions of BUY SIGNAL + def conditions_met(signalname: str, direction: TradeDirection): + if direction == TradeDirection.LONG: + smer = "long" + else: + smer = "short" + #preconditiony dle smeru + #dont_long_when, dont_short_when + #signal direktivy + #nazevgeneratoru_long_if_above + #nazevgeneratoru_short_if_above + + + # #preconditiony zatim preskoceny + dont_do_when = dict(AND=dict(), OR=dict()) + + # #OBECNE DONT BUYS + if safe_get(state.vars, "signal_only_on_confirmed",True): + dont_do_when['bar_not_confirmed'] = (data['confirmed'] == 0) + # #od posledniho vylozeni musi ubehnout N baru + # dont_buy_when['last_buy_offset_too_soon'] = data['index'] < (int(state.vars.lastbuyindex) + int(safe_get(state.vars, "lastbuy_offset",3))) + # dont_buy_when['blockbuy_active'] = (state.vars.blockbuy == 1) + # dont_buy_when['jevylozeno_active'] = (state.vars.jevylozeno == 1) + dont_do_when['open_rush'] = is_open_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), safe_get(state.vars, "open_rush",0)) + dont_do_when['close_rush'] = is_close_rush(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), safe_get(state.vars, "close_rush",0)) + + # #testing preconditions + result, cond_met = eval_cond_dict(dont_do_when) + if result: + state.ilog(e=f"{smer} PRECOND GENERAL not met {cond_met}", message=cond_met) + return False + + #SPECIFICKE DONT BUYS - direktivy zacinajici dont_buy + #dont_buy_below = value nebo nazev indikatoru + #dont_buy_above = value nebo hazev indikatoru + + #do INITU + #work_dict_dont_do = get_work_dict_with_directive(starts_with=signalname+"_dont_"+ smer +"_if") + + #z initu + work_dict_dont_do = state.vars.work_dict_dont_do[signalname+"_"+ smer] + + state.ilog(e=f"{smer} PRECOND DONT{smer} work_dict for {signalname}", message=work_dict_dont_do) + #u techto ma smysl pouze OR + precond = create_conditions_from_directives(work_dict_dont_do, "OR") + result, conditions_met = eval_cond_dict(precond) + state.ilog(e=f"{smer} PRECOND DONT{smer} =OR= {result}", **conditions_met) + if result: + return False + + #tyto timto nahrazeny - dat do konfigurace (dont_short_when, dont_long_when) + #dont_buy_when['rsi_too_high'] = state.indicators.RSI14[-1] > safe_get(state.vars, "rsi_dont_buy_above",50) + #dont_buy_when['slope_too_low'] = slope_too_low() + #dont_buy_when['slope_too_high'] = slope_too_high() + #dont_buy_when['rsi_is_zero'] = (state.indicators.RSI14[-1] == 0) + #dont_buy_when['reverse_position_waiting_amount_not_0'] = (state.vars.reverse_position_waiting_amount != 0) + + #u indikatoru muzoun byt tyto directivy pro generovani signaliu long/short + # long_if_crossed_down - kdyz prekrocil dolu, VALUE: hodnota nebo nazev indikatoru + # long_if_crossed_up - kdyz prekrocil nahoru, VALUE: hodnota nebo nazev indikatoru + # long_if_crossed - kdyz krosne obema smery, VALUE: hodnota nebo nazev indikatoru + # long_if_falling - kdyz je klesajici po N, VALUE: hodnota + # long_if_rising - kdyz je rostouci po N, VALUE: hodnota + # long_if_below - kdyz je pod prahem, VALUE: hodnota nebo nazev indikatoru + # long_if_above - kdyz je nad prahem, VALUE: hodnota nebo nazev indikatoru + # long_if_pivot_a - kdyz je pivot A. VALUE: delka nohou + # long_if_pivot_v - kdyz je pivot V. VALUE: delka nohou + + # direktivy se mohou nachazet v podsekci AND nebo OR - daneho indikatoru (nebo na volno, pak = OR) + # OR - staci kdyz plati jedna takova podminka a buysignal je aktivni + # AND - musi platit vsechny podminky ze vsech indikatoru, aby byl buysignal aktivni + + #populate work dict - muze byt i jen jednou v INIT nebo 1x za cas + #dict oindexovane podminkou (OR/AND) obsahuje vsechny buy_if direktivy v tuplu (nazevind,direktiva,hodnota + # {'AND': [('nazev indikatoru', 'nazev direktivy', 'hodnotadirektivy')], 'OR': []} + #work_dict_signal_if = get_work_dict_with_directive(starts_with=signalname+"_"+smer+"_if") + work_dict_signal_if = state.vars.work_dict_signal_if[signalname+"_"+ smer] + + state.ilog(e=f"{smer} SIGNAL work_dict {signalname}", message=work_dict_signal_if) + + buy_or_cond = create_conditions_from_directives(work_dict_signal_if, "OR") + result, conditions_met = eval_cond_dict(buy_or_cond) + state.ilog(e=f"{smer} SIGNAL =OR= {result}", **conditions_met) + if result: + return True + + #OR neprosly testujeme AND + buy_and_cond = create_conditions_from_directives(work_dict_signal_if, "AND") + result, conditions_met = eval_cond_dict(buy_and_cond) + state.ilog(e=f"{smer} SIGNAL =AND= {result}", **conditions_met) + return result + + def execute_signal_generator(name): + options = safe_get(state.vars.signals, name, None) + + if options is None: + state.ilog(e="No options for {name} in stratvars") + return + + validfrom = safe_get(options, "validfrom", 0) + validto = safe_get(options, "validto", 0) + recurring = safe_get(options, "reccurring", False) + on_confirmed_only = safe_get(options, 'on_confirmed_only', False) + plugin = safe_get(options, 'plugin', None) + + #pokud je plugin True, spusti se kod + if plugin: + execute_signal_generator_plugin(name) + else: + #common signals based on 1) configured signals in stratvars + #toto umoznuje jednoduchy prescribed trade bez ceny + if conditions_met(signalname=name, direction=TradeDirection.LONG): + state.vars.prescribedTrades.append(Trade(validfrom=datetime.now(tz=zoneNY), + status=TradeStatus.READY, + direction=TradeDirection.LONG, + entry_price=None, + stoploss_value = None)) + elif conditions_met(signalname=name, direction=TradeDirection.SHORT): + state.vars.prescribedTrades.append(Trade(validfrom=datetime.now(tz=zoneNY), + status=TradeStatus.READY, + direction=TradeDirection.SHORT, + entry_price=None, + stoploss_value = None)) + + def signal_search(): + # SIGNAL sekce ve stratvars obsahuje signal generator (obsahujici obecne veci jako name,validfrom, validto, validfrom, recurring) + specificke + #jako plugin + + #spoustime kazdy nakonfigurovany signal generator (signal sekce v startvars) + + #ZAMYSLET SE JAK PRACOVAT S MULTI SIGNAL SEKCEMI ve stratvars + for signalname, signalsettings in state.vars.signals.items(): + state.ilog(e=f"reading {signalname}", message=str(signalsettings)) + execute_signal_generator(signalname) + + # #vysledek je vložení Trade Prescription a to bud s cenou nebo immediate + # trade = Trade(validfrom=datetime.now(tz=zoneNY), + # status=TradeStatus.READY, + # direction=TradeDirection.LONG, + # entry_price=None, + # stoploss_value = None) + + # ##add prescribed trade to list + # state.vars.prescribedTrades.append(trade) + + def manage_active_trade(): + trade = state.vars.activeTrade + if trade is None: + return -1 + + eval_close_position() + #SELL STOPLOSS + #SELL PROFIT + #OPTIMIZE ADD TO PROFIT + + #zatim dynamicky profit + # endregion + + #MAIN LOOP + lp = data['close'] + 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)}", activeTrade=str(state.vars.activeTrade), prescribedTrades=str(state.vars.prescribedTrades), pending=str(state.vars.pending), last_price=lp, data=data, stratvars=str(state.vars)) + inds = get_last_ind_vals() + state.ilog(e="Indikatory", **inds) + + #TODO dat do initu inciializaci work directory pro directivy + + #pokud mame prazdne pozice a neceka se na nic + if state.positions == 0 and not state.vars.pending: + execute_prescribed_trades() + #pokud se neaktivoval nejaky trade, poustime signal search - ale jen jednou za bar? + if conf_bar == 1: + if not state.vars.pending: + signal_search() + #pro jistotu ihned zpracujeme + execute_prescribed_trades() + + #mame aktivni trade a neceka se nani + elif state.vars.activeTrade and not state.vars.pending: + manage_active_trade() #optimalize, close + # - close means change status in prescribed Trends,update profit, delete from activeTrade + + +def init(state: StrategyState): + #place to declare new vars + print("INIT v main",state.name) + + #pomocna funkce pro inicializaci + def get_work_dict_with_directive(starts_with: str): + reslist = dict(AND=[], OR=[]) + + for indname, indsettings in state.vars.indicators.items(): + for option,value in indsettings.items(): + if option.startswith(starts_with): + reslist["OR"].append((indname, option, value)) + if option == "AND": + #vsechny buy direktivy, ktere jsou pod AND + for key, val in value.items(): + if key.startswith(starts_with): + reslist["AND"].append((indname, key, val)) + if option == "OR" : + #vsechny buy direktivy, ktere jsou pod OR + for key, val in value.items(): + if key.startswith(starts_with): + reslist["OR"].append((indname, key, val)) + return reslist + + def initialize_dynamic_indicators(): + #pro vsechny indikatory, ktere maji ve svych stratvars TYPE inicializujeme + for indname, indsettings in state.vars.indicators.items(): + for option,value in indsettings.items(): + #inicializujeme nejenom typizovane + #if option == "type": + state.indicators[indname] = [] + #pokud ma MA_length incializujeme i MA variantu + if safe_get(indsettings, 'MA_length', False): + state.indicators[indname+"MA"] = [] + #specifika pro slope + if value == "slope": + #inicializujeme statinds (pro uhel na FE) + state.statinds[indname] = dict(minimum_slope=safe_get(indsettings, 'minimum_slope', -1), maximum_slope=safe_get(indsettings, 'maximum_slope', 1)) + + def intialize_work_dict(): + state.vars.work_dict_dont_do = {} + state.vars.work_dict_signal_if = {} + for signalname, signalsettings in state.vars.signals.items(): + smer = TradeDirection.LONG + state.vars.work_dict_dont_do[signalname+"_"+ smer] = get_work_dict_with_directive(starts_with=signalname+"_dont_"+ smer +"_if") + state.vars.work_dict_signal_if[signalname+"_"+ smer] = get_work_dict_with_directive(starts_with=signalname+"_"+smer+"_if") + smer = TradeDirection.SHORT + state.vars.work_dict_dont_do[signalname+"_"+ smer] = get_work_dict_with_directive(starts_with=signalname+"_dont_"+ smer +"_if") + state.vars.work_dict_signal_if[signalname+"_"+ smer] = get_work_dict_with_directive(starts_with=signalname+"_"+smer+"_if") + + #nove atributy na rizeni tradu + + #identifikuje provedenou změnu na Tradu (neděláme změny dokud nepřijde potvrzeni z notifikace) + state.vars.pending = None + #obsahuje aktivni Trade a jeho nastaveni + state.vars.activeTrade = None #pending/Trade + #obsahuje pripravene Trady ve frontě + state.vars.prescribedTrades = [] + + #TODO presunout inicializaci work_dict u podminek - sice hodnoty nepujdou zmenit, ale zlepsi se performance + #pripadne udelat refresh kazdych x-iterací + state.vars['sell_in_progress'] = False + state.vars.mode = None + state.vars.last_tick_price = 0 + state.vars.last_50_deltas = [] + state.vars.last_tick_volume = 0 + state.vars.next_new = 0 + state.vars.lastbuyindex = 0 + state.vars.last_update_time = 0 + state.vars.reverse_position_waiting_amount = 0 + #INIT promenne, ktere byly zbytecne ve stratvars + state.vars.pendingbuys={} + state.vars.limitka = None + state.vars.limitka_price=0 + state.vars.jevylozeno=0 + state.vars.blockbuy = 0 + + #state.cbar_indicators['ivwap'] = [] + state.cbar_indicators['tick_price'] = [] + state.cbar_indicators['tick_volume'] = [] + state.cbar_indicators['CRSI'] = [] + #state.secondary_indicators['SRSI'] = [] + #state.indicators['ema'] = [] + #state.indicators['RSI14'] = [] + + initialize_dynamic_indicators() + intialize_work_dict() + + #TODO - predelat tuto cas, aby dynamicky inicializovala indikatory na zaklade stratvars a type + # vsechno nize vytvorit volana funkce + # to jestli inicializovat i MA variantu pozna podle pritomnosti MA_length + # # + # state.indicators['slope'] = [] + # state.indicators['slopeNEW'] = [] + # state.indicators['slopeNEWMA'] = [] + # state.indicators['slope10'] = [] + # state.indicators['slope10puv'] = [] + # state.indicators['slope30'] = [] + # state.indicators['slopeMA'] = [] + # state.indicators['slow_slope'] = [] + # state.indicators['slow_slopeMA'] = [] + # #static indicators - those not series based + # state.statinds['slope'] = dict(minimum_slope=state.vars['indicators']['slope']["minimum_slope"], maximum_slope=safe_get(state.vars['indicators']['slope'], "maximum_slope",0.20)) + # #state.statinds['angle_slow'] = dict(minimum_slope=safe_get(state.vars.indicators.slow_slope, "minimum_slope",-2), maximum_slope=safe_get(state.vars.indicators.slow_slope, "maximum_slope",2)) + # state.statinds['slow_slope'] = dict(minimum_slope=state.vars['indicators']['slow_slope']["minimum_slope"], maximum_slope=state.vars['indicators']['slow_slope']["maximum_slope"]) + + + +def main(): + name = os.path.basename(__file__) + se = Event() + pe = Event() + s = StrategyOrderLimitVykladaciNormalizedMYSELL(name = name, symbol = "BAC", account=Account.ACCOUNT1, next=next, init=init, stratvars=None, 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() + + + + + \ No newline at end of file diff --git a/v2realbot/ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py b/v2realbot/ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py index 4599b1c..961e3a0 100644 --- a/v2realbot/ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py +++ b/v2realbot/ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py @@ -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, round2five, is_open_rush, is_close_rush, eval_cond_dict, Average, crossed_down, crossed_up, is_pivot +from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, get_tick, round2five, is_open_rush, is_close_rush, eval_cond_dict, Average, crossed_down, crossed_up, crossed, is_pivot from datetime import datetime #from icecream import install, ic #from rich import print @@ -536,6 +536,7 @@ def next(data, state: StrategyState): #u indikatoru muzoun byt tyto directivy pro generovani buysignalu # buy_if_crossed_down - kdyz prekrocil dolu, VALUE: hodnota nebo nazev indikatoru # buy_if_crossed_up - kdyz prekrocil nahoru, VALUE: hodnota nebo nazev indikatoru + # buy_if_crossed - kdyz krosne obema smery, VALUE: hodnota nebo nazev indikatoru # buy_if_falling - kdyz je klesajici po N, VALUE: hodnota # buy_if_rising - kdyz je rostouci po N, VALUE: hodnota # buy_if_below - kdyz je pod prahem, VALUE: hodnota nebo nazev indikatoru @@ -607,6 +608,9 @@ def next(data, state: StrategyState): cond[cond_type][directive+"_"+indname+"_"+str(value)] = buy_if_crossed_down(indname, value_or_indicator(value)) elif directive.endswith("crossed_up"): cond[cond_type][directive+"_"+indname+"_"+str(value)] = buy_if_crossed_up(indname, value_or_indicator(value)) + #nefunguje moc dobre + elif directive.endswith("crossed"): + cond[cond_type][directive+"_"+indname+"_"+str(value)] = buy_if_crossed_down(indname, value_or_indicator(value)) or buy_if_crossed_up(indname, value_or_indicator(value)) elif directive.endswith("pivot_a"): cond[cond_type][directive+"_"+indname+"_"+str(value)] = is_pivot(source=get_source_or_MA(indname), leg_number=value, type="A") elif directive.endswith("pivot_v"): @@ -618,7 +622,6 @@ def next(data, state: StrategyState): return cond - #tato funkce vytvori dictionary typu podminek (OR/AND) # z indikatoru, ktere obsahuji direktivami daneho typu(buy_if, dont_buy_when) # v tuplu (nazevind,direktiva,hodnota) @@ -650,6 +653,12 @@ def next(data, state: StrategyState): except KeyError: return state.indicators[indicator] + # #vrati true pokud dany indikator krosnul obema smery + # def buy_if_crossed(indicator, value): + # res = crossed(threshold=value, list=get_source_or_MA(indicator)) + # state.ilog(e=f"buy_if_crossed {indicator} {value} {res}") + # return res + #vrati true pokud dany indikator prekrocil threshold dolu def buy_if_crossed_down(indicator, value): res = crossed_down(threshold=value, list=get_source_or_MA(indicator)) @@ -820,6 +829,10 @@ def next(data, state: StrategyState): #poustet kazdy tick nebo jenom na confirmed baru (on_confirmed_only = true) on_confirmed_only = safe_get(options, 'on_confirmed_only', False) + #pocet baru po kterých se levy bod z BUY prepne opet na standadni vypocet (prumer) + #kdyz se dlouho neprodává a cena nejde dolu, tak aby se nezastavilo nakupovani + back_to_standard_after = int(safe_get(options, 'back_to_standard_after', 0)) + #slopeLP INDIKATOR #levy bod je nejdrive standardne automaticky vypočtený podle hodnoty lookbacku (např. -8, offset 4) #při nákupu se BUY POINT se stává levým bodem (až do doby kdy není lookbackprice nižší, pak pokračuje lookbackprice) @@ -862,10 +875,11 @@ def next(data, state: StrategyState): lookbacktime = state.bars.time[-slope_lookback] #pokud mame aktivni pozice, nastavime lookbackprice a time podle posledniho tradu - if state.avgp > 0: + #pokud se ale dlouho nenakupuje (uplynulo od posledniho nakupu vic nez back_to_standard_after baru), tak se vracime k prumeru + if state.avgp > 0 and state.bars.index[-1] < int(state.vars.lastbuyindex)+back_to_standard_after: lb_index = -1 - (state.bars.index[-1] - int(state.vars.lastbuyindex)) lookbackprice = state.bars.vwap[lb_index] - state.ilog(e=f"IND {name} slope {leftpoint}- LEFT POINT OVERRIDE bereme buy {state.avgp=} {lookbackprice=} {lookbacktime=} {lb_index=}") + state.ilog(e=f"IND {name} slope {leftpoint}- LEFT POINT OVERRIDE bereme ajko cenu lastbuy {lookbackprice=} {lookbacktime=} {lb_index=}") else: #dame na porovnani jen prumer lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3) @@ -879,7 +893,7 @@ def next(data, state: StrategyState): # lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3) lookbacktime = state.bars.time[-slope_lookback] - state.ilog(e=f"IND {name} slope {leftpoint} - LEFT POINT STANDARD AVGP {state.avgp} {lookbackprice=} {lookbacktime=}") + state.ilog(e=f"IND {name} slope {leftpoint} - LEFT POINT STANDARD {lookbackprice=} {lookbacktime=}") else: #kdyz neni dostatek hodnot, pouzivame jako levy bod open hodnotu close[0] lookbackprice = state.bars.close[0] diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index 6cad281..4bef151 100644 Binary files a/v2realbot/__pycache__/config.cpython-310.pyc and b/v2realbot/__pycache__/config.cpython-310.pyc differ diff --git a/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc b/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc index 372a6a8..5b56f54 100644 Binary files a/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc and b/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc differ diff --git a/v2realbot/backtesting/backtester.py b/v2realbot/backtesting/backtester.py index aa373d5..fbb4273 100644 --- a/v2realbot/backtesting/backtester.py +++ b/v2realbot/backtesting/backtester.py @@ -87,6 +87,7 @@ class Backtester: self.bp_from = bp_from self.bp_to = bp_to self.cash = cash + self.cash_reserved_for_shorting = 0 self.trades = [] self.account = { "BAC": [0, 0] } # { "BAC": [avgp, size] } @@ -372,23 +373,43 @@ class Backtester: if o.side == OrderSide.BUY: #[size, avgp] - if (self.account[o.symbol][0] + o.qty) == 0: newavgp = 0 + newsize = (self.account[o.symbol][0] + o.qty) + #JPLNE UZAVRENI SHORT (avgp 0) + if newsize == 0: newavgp = 0 + #CASTECNE UZAVRENI SHORT (avgp puvodni) + elif newsize < 0: newavgp = self.account[o.symbol][1] + #JDE O LONG (avgp nove) else: newavgp = ((self.account[o.symbol][0] * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (self.account[o.symbol][0] + o.qty) - self.account[o.symbol] = [self.account[o.symbol][0]+o.qty, newavgp] + + self.account[o.symbol] = [newsize, newavgp] self.cash = self.cash - (o.qty * o.filled_avg_price) return 1 #sell elif o.side == OrderSide.SELL: newsize = self.account[o.symbol][0]-o.qty + #UPLNE UZAVRENI LONGU (avgp 0) if newsize == 0: newavgp = 0 + #CASTECNE UZAVRENI LONGU (avgp puvodni) + elif newsize > 0: newavgp = self.account[o.symbol][1] + #jde o SHORT (avgp nove) else: + #pokud je predchozi 0 - tzn. jde o prvni short if self.account[o.symbol][1] == 0: newavgp = o.filled_avg_price else: - newavgp = self.account[o.symbol][1] + newavgp = ((abs(self.account[o.symbol][0]) * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (abs(self.account[o.symbol][0]) + o.qty) + self.account[o.symbol] = [newsize, newavgp] - self.cash = float(self.cash + (o.qty * o.filled_avg_price)) + + #pokud jde o prodej longu(nova pozice je>=0) upravujeme cash + if self.account[o.symbol][0] >= 0: + self.cash = float(self.cash + (o.qty * o.filled_avg_price)) + print("uprava cashe, jde o prodej longu") + else: + self.cash = float(self.cash + (o.qty * o.filled_avg_price)) + #self.cash_reserved_for_shorting += float(o.qty * o.filled_avg_price) + print("jde o short, upravujeme cash zatim stejne") return 1 else: print("neznaama side", o.side) @@ -467,31 +488,53 @@ class Backtester: #check for available quantity if side == OrderSide.SELL: reserved = 0 + reserved_price = 0 #with lock: for o in self.open_orders: if o.side == OrderSide.SELL and o.symbol == symbol and o.canceled_at is None: reserved += o.qty - print("blokovano v open orders pro sell: ", reserved) + cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol) + reserved_price += o.qty * cena + print("blokovano v open orders pro sell qty: ", reserved, "celkem:", reserved_price) - if int(self.account[symbol][0]) - reserved - int(size) < 0: - print("not enough shares having",self.account[symbol][0],"reserved",reserved,"available",int(self.account[symbol][0]) - reserved,"selling",size) + actual_minus_reserved = int(self.account[symbol][0]) - reserved + if actual_minus_reserved > 0 and actual_minus_reserved - int(size) < 0: + print("not enough shares available to sell or shorting while long position",self.account[symbol][0],"reserved",reserved,"available",int(self.account[symbol][0]) - reserved,"selling",size) return -1 + + #if is shorting - check available cash to short + if actual_minus_reserved <= 0: + cena = price if price else self.get_last_price(time, self.symbol) + if (self.cash - reserved_price - float(int(size)*float(cena))) < 0: + print("not enough cash for shorting. cash",self.cash,"reserved",reserved,"available",self.cash-reserved,"needed",float(int(size)*float(cena))) + return -1 #check for available cash if side == OrderSide.BUY: - reserved = 0 + reserved_qty = 0 + reserved_price = 0 #with lock: for o in self.open_orders: if o.side == OrderSide.BUY and o.canceled_at is None: cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol) - reserved += o.qty * cena - print("blokovano v open orders: ", reserved) + reserved_price += o.qty * cena + reserved_qty += o.qty + print("blokovano v open orders for buy: qty, price", reserved_qty, reserved_price) - cena = price if price else self.get_last_price(time, self.symbol) - if (self.cash - reserved - float(int(size)*float(cena))) < 0: - print("not enough cash. cash",self.cash,"reserved",reserved,"available",self.cash-reserved,"needed",float(int(size)*float(cena))) + actual_plus_reserved_qty = int(self.account[symbol][0]) + reserved_qty + + #jde o uzavreni shortu + if actual_plus_reserved_qty < 0 and (actual_plus_reserved_qty + int(size)) > 0: + print("nejprve je treba uzavrit short pozici pro buy res_qty, size", actual_plus_reserved_qty, size) return -1 + #jde o standardni long, kontroluju cash + if actual_plus_reserved_qty >= 0: + cena = price if price else self.get_last_price(time, self.symbol) + if (self.cash - reserved_price - float(int(size)*float(cena))) < 0: + print("not enough cash to buy long. cash",self.cash,"reserved_qty",reserved_qty,"reserved_price",reserved_price, "available",self.cash-reserved_price,"needed",float(int(size)*float(cena))) + return -1 + id = str(uuid4()) order = Order(id=id, submitted_at = datetime.fromtimestamp(float(time)), diff --git a/v2realbot/common/PrescribedTradeModel.py b/v2realbot/common/PrescribedTradeModel.py new file mode 100644 index 0000000..84de826 --- /dev/null +++ b/v2realbot/common/PrescribedTradeModel.py @@ -0,0 +1,27 @@ +from enum import Enum +from datetime import datetime +from pydantic import BaseModel +from typing import Any, Optional, List, Union + +class TradeStatus(str, Enum): + READY = "ready" + ACTIVATED = "activated" + #FINISHED = "finished" + +class TradeDirection(str, Enum): + LONG = "long" + SHORT = "short" + +class TradeStoplossType(str, Enum): + FIXED = "fixed" + TRAILING = "trailing" + +class Trade(BaseModel): + validfrom: datetime + status: TradeStatus + direction: TradeDirection + entry_price: Optional[float] = None + # stoploss_type: TradeStoplossType + stoploss_value: Optional[float] = None + profit: Optional[float] = None + diff --git a/v2realbot/common/__pycache__/model.cpython-310.pyc b/v2realbot/common/__pycache__/model.cpython-310.pyc index 2f00b04..a1fbc4b 100644 Binary files a/v2realbot/common/__pycache__/model.cpython-310.pyc and b/v2realbot/common/__pycache__/model.cpython-310.pyc differ diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index a04e80b..400b882 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -18,13 +18,12 @@ from alpaca.data.enums import Exchange # if user_update.first_name is not None: # user.first_name = user_update.first_name # if user_update.last_name is not None: -# user.last_name = user_update.last_name +# userbase.last_name = user_update.last_name # if user_update.roles is not None: # user.roles = user_update.roles # return user.id # raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}") - class Intervals(BaseModel): start: str end: str diff --git a/v2realbot/config.py b/v2realbot/config.py index 971b687..87e78a4 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -92,8 +92,8 @@ ACCOUNT1_LIVE_PAPER = False ACCOUNT1_LIVE_FEED = DataFeed.SIP #SECONDARY PAPER -ACCOUNT2_PAPER_API_KEY = 'PKQEAAJTVC72SZO3CT3R' -ACCOUNT2_PAPER_SECRET_KEY = 'mqdftzGJlJdvUjdsuQynAURCHRwAI0a8nhJy8nyz' +ACCOUNT2_PAPER_API_KEY = 'PK0OQHZG03PUZ1SC560V' +ACCOUNT2_PAPER_SECRET_KEY = 'cTglhm7kwRcZfFT27fQWz31sXaxadzQApFDW6Lat' ACCOUNT2_PAPER_MAX_BATCH_SIZE = 1 ACCOUNT2_PAPER_PAPER = True ACCOUNT2_PAPER_FEED = DataFeed.IEX diff --git a/v2realbot/enums/__pycache__/enums.cpython-311.pyc b/v2realbot/enums/__pycache__/enums.cpython-311.pyc index 30d7684..a002fae 100644 Binary files a/v2realbot/enums/__pycache__/enums.cpython-311.pyc and b/v2realbot/enums/__pycache__/enums.cpython-311.pyc differ diff --git a/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc b/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc index 8602c0e..2d13f6f 100644 Binary files a/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc and b/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc differ diff --git a/v2realbot/loader/__pycache__/trade_offline_streamer.cpython-310.pyc b/v2realbot/loader/__pycache__/trade_offline_streamer.cpython-310.pyc index 9059ea5..7660575 100644 Binary files a/v2realbot/loader/__pycache__/trade_offline_streamer.cpython-310.pyc and b/v2realbot/loader/__pycache__/trade_offline_streamer.cpython-310.pyc differ diff --git a/v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc b/v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc index 41cd1f0..5982bfc 100644 Binary files a/v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc and b/v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc differ diff --git a/v2realbot/loader/aggregator.py b/v2realbot/loader/aggregator.py index 1403f37..ba52dc8 100644 --- a/v2realbot/loader/aggregator.py +++ b/v2realbot/loader/aggregator.py @@ -11,7 +11,7 @@ import threading from copy import deepcopy from msgpack import unpackb import os -from config import DATA_DIR, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN +from v2realbot.config import DATA_DIR, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN class TradeAggregator: def __init__(self, @@ -383,7 +383,7 @@ class TradeAggregator2Queue(TradeAggregator): copy = obj ##populate secondary resolution if required - + #print("inserted to queue") self.queue.put(copy) res = [] #print("po insertu",res) diff --git a/v2realbot/loader/trade_offline_streamer.py b/v2realbot/loader/trade_offline_streamer.py index b980a6b..2155828 100644 --- a/v2realbot/loader/trade_offline_streamer.py +++ b/v2realbot/loader/trade_offline_streamer.py @@ -42,17 +42,19 @@ class Trade_Offline_Streamer(Thread): pass def run(self): - #create new asyncio loop in the thread - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.create_task(self.main()) - loop.run_forever() + self.main() + # #create new asyncio loop in the thread + # loop = asyncio.new_event_loop() + # asyncio.set_event_loop(loop) + # loop.create_task(self.main()) + # loop.run_forever() def stop(self): pass # Override the run() function of Thread class - async def main(self): + #odebrano async + def main(self): print(10*"*","Trade OFFLINE streamer STARTED", current_thread().name,10*"*") if not self.streams: @@ -194,16 +196,24 @@ class Trade_Offline_Streamer(Thread): for s in self.to_run[symbol]: #print("zaznam",t) #print("Ingest", s, "zaznam", t) - await s.ingest_trade(packb(t)) + #await s.ingest_trade(packb(t)) + asyncio.run(s.ingest_trade(packb(t))) cnt += 1 + #protoze jsou serazene, tak prvni ktery je vetsi muze prerusit + elif to_datetime(t['t']) > self.time_to: + print("prerusujeme") + break #vsem streamum posleme last TODO: (tuto celou cast prepsat a zjednodusit) #po loadovani vsech dnu + print("naloadovane vse posilame last") for s in self.to_run[symbpole[0]]: - await s.ingest_trade(packb("last")) + #zde bylo await + asyncio.run(s.ingest_trade(packb("last"))) + print("poslano last") - loop = asyncio.get_running_loop() + #loop = asyncio.get_running_loop() print("stoping loop") - loop.stop() + #loop.stop() print(10*"*","Trade OFFLINE streamer STOPPED", current_thread().name,10*"*") diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index 16f6248..8a55e54 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -72,10 +72,11 @@ function transform_data(data) { last_timestamp = timestamp iterator = 0.002 } - - if (trade.order.side == "buy") { + //puvodne bylo pro buy + //pro sell muzeme teoreticky taky mit buyline (pri shortu) + if ((trade.order.side == "buy") || (trade.order.side == "sell")) { //avgp lajnu vytvarime jen pokud je v tradeventu prumerna cena - if (trade.pos_avg_price !== null) { + if ((trade.pos_avg_price !== null) && (trade.pos_avg_price !== 0)) { //line pro avgp markers obj["time"] = timestamp; obj["value"] = trade.pos_avg_price; @@ -87,12 +88,14 @@ function transform_data(data) { a_markers["color"] = "#e8c76d" a_markers["shape"] = "arrowDown" //if (CHART_SHOW_TEXT) - // a_markers["text"] = trade.position_qty + " " + parseFloat(trade.pos_avg_price).toFixed(3) - a_markers["text"] = CHART_SHOW_TEXT ? trade.position_qty + "/" + parseFloat(trade.pos_avg_price).toFixed(3) :trade.position_qty + //a_markers["text"] = trade.position_qty + " " + parseFloat(trade.pos_avg_price).toFixed(3) + //a_markers["text"] = CHART_SHOW_TEXT ? trade.position_qty + "/" + parseFloat(trade.pos_avg_price).toFixed(3) :trade.position_qty avgp_markers.push(a_markers) } } + + //buy sell markery marker = {} marker["time"] = timestamp; @@ -103,6 +106,7 @@ function transform_data(data) { marker["shape"] = (trade.order.side == "buy") ? "circle" : "arrowDown" //marker["text"] = trade.qty + "/" + trade.price marker["text"] = CHART_SHOW_TEXT ? trade.qty + "/" + trade.price : trade.qty + marker["text"] = (trade.position_qty == 0) ? "c": marker["text"] markers.push(marker) //prevedeme iso data na timestampy diff --git a/v2realbot/static/js/utils.js b/v2realbot/static/js/utils.js index 4bcd3bb..f41a27a 100644 --- a/v2realbot/static/js/utils.js +++ b/v2realbot/static/js/utils.js @@ -34,6 +34,7 @@ indConfig = [ {name: "ema", titlevisible: false, embed: true, display: true, pri {name: "slopeS", titlevisible: true, embed: true, display: true, priceScaleId: "left", lastValueVisible: false}, {name: "slopeLP", titlevisible: true, embed: true, display: true, priceScaleId: "left", lastValueVisible: false}, {name: "slopeMA", titlevisible: true, embed: true, display: true, priceScaleId: "left", lastValueVisible: false}, + {name: "slopeLPMA", titlevisible: true, embed: true, display: true, priceScaleId: "left", lastValueVisible: false}, {name: "slow_slope", titlevisible: true, embed: true, display: false, priceScaleId: "left", lastValueVisible: false}, {name: "slow_slopeMA", titlevisible: true, embed: true, display: true, priceScaleId: "left", lastValueVisible: false}, {name: "emaSlow", titlevisible: true, embed: true, display: true, priceScaleId: "right", lastValueVisible: false}, diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py new file mode 100644 index 0000000..71bdf5d --- /dev/null +++ b/v2realbot/strategy/StrategyClassicSL.py @@ -0,0 +1,165 @@ +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, RecordType +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 StrategyClassicSL(Strategy): + """ + Base override file for Classic Stop-Loss startegy + """ + 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) + + #todo dodelat profit, podle toho jestli jde o short nebo buy + + 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: + #davame pryc pending + self.state.vars.pending = None + + if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL: + + #jde o uzavření short pozice - počítáme PROFIT + if self.state.positions < 0: + #PROFIT pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena + #naklady vypocteme z prumerne ceny, kterou mame v pozicich + bought_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 = bought_amount + + trade_profit = (avg_costs-bought_amount) + self.state.profit += trade_profit + self.state.ilog(e=f"BUY notif - SHORT PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)}", msg=str(data.event), bought_amount=bought_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id)) + else: + self.state.ilog(e="BUY: Jde o LONG nakuú nepocitame profit zatim") + + #ic("vstupujeme do orderupdatebuy") + print(data) + #dostavame zde i celkové akutální množství - ukládáme + self.state.positions = data.position_qty + self.state.avgp, self.state.positions = self.state.interface.pos() + + 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))) + #naklady vypocteme z prumerne ceny, kterou mame v pozicich + if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL: + if data.event == TradeEvent.FILL: + self.state.vars.pending = None + + #jde o uzavření long pozice - počítáme PROFIT + if self.state.positions > 0: + #PROFIT pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena + #naklady vypocteme z prumerne ceny, kterou mame v pozicich + 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)) + else: + self.state.ilog(e="SELL: Jde o SHORT nepocitame profit zatim") + + #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) + a,p = self.interface.pos() + #pri chybe api nechavame puvodni hodnoty + if a != -1: + self.state.avgp, self.state.positions = a,p + #ic(self.state.avgp, self.state.positions) + + #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.sell = self.sell + + #overidden methods + # pouziva se pri vstupu long nebo exitu short + # osetrit uzavreni s vice nez mam + def buy(self, size = None, repeat: bool = False): + print("overriden buy method") + if size is None: + sizer = self.state.vars.chunk + else: + sizer = size + #jde o uzavreni short pozice + if int(self.state.positions) < 0 and (int(self.state.positions) + sizer) > 0: + self.state.ilog(e="buy nelze nakoupit vic nez shortuji", positions=self.state.positions, size=size) + print("buy nelze nakoupit vic nez shortuji") + return -2 + + 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 + + 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) + + #overidden methods + # pouziva se pri vstupu short nebo exitu long + def sell(self, size = None, repeat: bool = False): + print("overriden sell method") + if size is None: + size = abs(int(self.state.positions)) + + #jde o uzavreni long pozice + if int(self.state.positions) > 0 and (int(self.state.positions) - size) < 0: + self.state.ilog(e="nelze prodat vic nez longuji", positions=self.state.positions, size=size) + print("nelze prodat vic nez longuji") + return -2 + + #pokud shortuji a mam max pozic + if int(self.state.positions) < 0 and abs(int(self.state.positions)) >= self.state.vars.maxpozic: + self.state.ilog(e="short - Maxim mnozstvi naplneno", positions=self.state.positions, size=size) + print("max mnostvi naplneno") + return 0 + + #self.state.blocksell = 1 + #self.state.vars.lastbuyindex = self.state.bars['index'][-1] + self.state.ilog(e="send MARKET SELL to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol)) + return self.state.interface.sell(size=size) + + 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))) \ No newline at end of file diff --git a/v2realbot/strategy/StrategyOrderLimitVykladaciNormalizedMYSELL.py b/v2realbot/strategy/StrategyOrderLimitVykladaciNormalizedMYSELL.py index 8217f33..aa779de 100644 --- a/v2realbot/strategy/StrategyOrderLimitVykladaciNormalizedMYSELL.py +++ b/v2realbot/strategy/StrategyOrderLimitVykladaciNormalizedMYSELL.py @@ -1,5 +1,5 @@ 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.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, json_serial, safe_get, get_tick,print from v2realbot.utils.tlog import tlog, tlog_exception from v2realbot.enums.enums import Mode, Order, Account, RecordType from alpaca.trading.models import TradeUpdate diff --git a/v2realbot/strategy/__pycache__/base.cpython-310.pyc b/v2realbot/strategy/__pycache__/base.cpython-310.pyc index 64d2575..fb8ea99 100644 Binary files a/v2realbot/strategy/__pycache__/base.cpython-310.pyc and b/v2realbot/strategy/__pycache__/base.cpython-310.pyc differ diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 1135ed3..c61e190 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -2,7 +2,7 @@ Strategy base class """ from datetime import datetime -from v2realbot.utils.utils import AttributeDict, zoneNY, is_open_rush, is_close_rush, json_serial, print, safe_get, Average +from v2realbot.utils.utils import AttributeDict, zoneNY, is_open_rush, is_close_rush, json_serial, print from v2realbot.utils.tlog import tlog from v2realbot.utils.ilog import insert_log, insert_log_multiple_queue from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Order, Account diff --git a/v2realbot/utils/__pycache__/utils.cpython-310.pyc b/v2realbot/utils/__pycache__/utils.cpython-310.pyc index 74fa137..673d241 100644 Binary files a/v2realbot/utils/__pycache__/utils.cpython-310.pyc and b/v2realbot/utils/__pycache__/utils.cpython-310.pyc differ diff --git a/v2realbot/utils/utils.py b/v2realbot/utils/utils.py index 1a66ac5..ceb8735 100644 --- a/v2realbot/utils/utils.py +++ b/v2realbot/utils/utils.py @@ -10,6 +10,7 @@ from v2realbot.enums.enums import RecordType, Mode, StartBarAlign import pickle import os from v2realbot.common.model import StrategyInstance, Runner, RunArchive, RunArchiveDetail, Intervals +from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType from typing import List import tomli from v2realbot.config import DATA_DIR, QUIET_MODE,NORMALIZED_TICK_BASE_PRICE @@ -197,6 +198,8 @@ def json_serial(obj): return obj.__dict__ if type(obj) is RunArchive: return obj.__dict__ + if type(obj) is Trade: + return obj.__dict__ if type(obj) is RunArchiveDetail: return obj.__dict__ if type(obj) is Intervals: