From 88bd8c30c273ffe053c501d6e80b6e9bc2d6c8eb Mon Sep 17 00:00:00 2001 From: David Brazda Date: Thu, 24 Aug 2023 21:12:45 +0200 Subject: [PATCH] draft nove classis strategie --- alpacawebsokcet.py | 14 + testy/archive/alpacawebsocketsimple.py | 2 +- testy/output_metriky_tradeList.py | 3 + testy/threadclassestest.py | 10 +- v2realbot/ENTRY_ClassicSL_v01.py | 1065 +++++++++++++++++ .../ENTRY_Vykladaci_RSI_MYSELL_type_NEW.py | 24 +- v2realbot/__pycache__/config.cpython-310.pyc | Bin 2952 -> 2952 bytes .../__pycache__/backtester.cpython-310.pyc | Bin 19107 -> 19991 bytes v2realbot/backtesting/backtester.py | 69 +- v2realbot/common/PrescribedTradeModel.py | 27 + .../common/__pycache__/model.cpython-310.pyc | Bin 6926 -> 6926 bytes v2realbot/common/model.py | 3 +- v2realbot/config.py | 4 +- .../enums/__pycache__/enums.cpython-311.pyc | Bin 2698 -> 3215 bytes .../__pycache__/aggregator.cpython-310.pyc | Bin 6994 -> 7004 bytes .../trade_offline_streamer.cpython-310.pyc | Bin 5007 -> 4969 bytes .../trade_ws_streamer.cpython-310.pyc | Bin 3553 -> 3553 bytes v2realbot/loader/aggregator.py | 4 +- v2realbot/loader/trade_offline_streamer.py | 30 +- v2realbot/static/js/archivechart.js | 14 +- v2realbot/static/js/utils.js | 1 + v2realbot/strategy/StrategyClassicSL.py | 165 +++ ...tegyOrderLimitVykladaciNormalizedMYSELL.py | 2 +- .../strategy/__pycache__/base.cpython-310.pyc | Bin 14407 -> 14370 bytes v2realbot/strategy/base.py | 2 +- .../utils/__pycache__/utils.cpython-310.pyc | Bin 11736 -> 11905 bytes v2realbot/utils/utils.py | 3 + 27 files changed, 1395 insertions(+), 47 deletions(-) create mode 100644 alpacawebsokcet.py create mode 100644 v2realbot/ENTRY_ClassicSL_v01.py create mode 100644 v2realbot/common/PrescribedTradeModel.py create mode 100644 v2realbot/strategy/StrategyClassicSL.py 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 6cad281c8350b17da4708a8b202f1ed373f3e843..4bef1514995e4b9268223f655c8122538ab0161f 100644 GIT binary patch delta 86 zcmeAW?-1wC=jG*M00IN|=Nq}_u-h2;2YN)g8yE+KMi~Y>o0=JfMQJ35q~~PhnrD{> oB}b*Xg&3Kq1%_7{8x}_-RwSlW1v(bExrCefB$jM`%)XQv00p}mX#fBK delta 86 zcmeAW?-1wC=jG*M0D{AsYd3PwVYdl%b#(L!33E0#3XbwOb`CKPiqgm}Oi3%La`(#d oN+}D?N+~W4tju!^4RZDfDtGiWNVLey@T#=PtE}4mn0+ZT05=93mH+?% diff --git a/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc b/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc index 372a6a87e59e5c463eba49a0a867962f13d7d4c3..5b56f54553583b4f643b95fb08fe7e8f7777a487 100644 GIT binary patch delta 2740 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 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 2f00b04cfba9ecd15dead1ef17fdf9d22588afb8..a1fbc4b674d7a6ff1575ba67fd9686deda877253 100644 GIT binary patch delta 137 zcmeA(>oeoc=jG*M0D|9}pQIe!$ZNyQC_UMW`5L3bW7LGD{(JnVvOH>g4czGv1~K1&|*f$37aiINI+ab3d~{^kpuvUJ0oTQ delta 137 zcmeA(>oeoc=jG*M00O0j%Tf+)#LXLpn;04APc{+V&bVgt8_`}S#*Le+ mB-9ugPixy)w5yQlzHVAB}1{)S^7}!V+HX_)yI-sL9=sH2ifUU2=MvaCUqBs7)Jaoxd(ssF^2mJ7) zV=!mNG?-sk*?Kvz>>m2FDrwA&hpR;@!ZR#@5GwrAD4dF*_G3R>p7A-kNy1Y`dY-XF(G7Bax zV&!F8D7z+ISn+6{**WTZY{_Ovhh3}q3=S_d4_@jkIc90?5-l(rEEI$)E&b@_Y%)b{ zw&a#f7g*ECd31?^Ac#85fUtS6%5>m3q==Ri0=|NYLgOG}M%SDUY(N5`6TlD7&l|=3 z{Jj5I86Ta=`pV42W!xaR@ZkLJFts-b;?5GB#(_0(Qn(|QVLMSzM1 ze^eH)pN#DEfQ-|Jpd(a;>_s0!g3qY~mAmQ%sa2}v7jllTdQQ$@89i84kkiAjXl;oq zwSH7X=;lwf3vozrFB)G@5Z1oC6_ljmnzGZA4>BoVn;y-iChu2^-b7J^LB1Cnh>F`lGo#0NQ~2UN z{PU=M&S!8(5k%KSu_7fQak3Q$q2WT&C|hjANx}&UmOI8*!d+d-&*Z;9z7y8(bcUqn zeR3K$$KJO5LjRv*vRRcn;iEVOFY}vM`OmN(h;JIaBcca}HWi+PCundh(y3sXe*v2L BA{YPw delta 872 zcmZvZ%WD%+6vpqJN9K`9YE8yw#z#_Vhyzhv1OyucsWXV8X^7Q=E<)`@5c0rDvvAce zT!V{u>HihFR*$g%WVrmG|7FmOReP{c-R8=6>g%Gq0_eX6|DwrV|#= z-d^&*Nre2w#Zk!P$=|R2T)Kbj^sX$B8^o8N6F<_RA4EdFurZ1U5w0mrQ)e`VYZ}w^ z8BOI{lxfC{runhE#EJjH6b=m)$!qsojsS}^wO^o3Dyd9>cY>LTvq`XkIDv>UIC>~` z8_mbT_#+$s2=T=(mYew>hgCQ!UIthEv1BsgGYL-N`6NQ&fNRp52;Wqoq+Nyg5pQ(~ zl@!8a2$g!>?`+rWK?b*$5oZy{R%u)!79gWs9NDFrodqp53w@=yX>51eJ5SoZPWRDP z5Xw8jmfxs2N-&u!IL|ynajjenrRzv}qgHmk>2xcY4q?I>HJh;Z+<}qU^Y9s6YGs`J zVQHSfTrF2Umf`lz`(7xlZA{ze(H?P**{30^B}NtH_e+{hi*qe&>-Q>d7^#&j?)sf+ z!vY#2F0l3_;gfC|ymz=&adh~u=Xd$v;C;2|Yc@*3DmzWt=xU5Z zky1J!=E3AUAXg6Ny+c8#xgj|Qyo#pmluYd*nZ*kqen%ae1j}$}0X#+*o*K4O>VpeyK`MwMkf<6!$ce3zG_e!gt!U*? zP}B>T7FHa%pi~GXj*tsy{sG*G!~r2doH=nR%tivL+M4lv-_Fc8>)p+hKYBw}Fz6@v zyuJEepH9CHT^EM{A%`}F=E&$g(j?mih)%8%JB36B+CoJunygz!nHV^lMm4sJ|z=H$Oh<$ed7sni4HpFLW7hz9U20le2I6W z^s_Gw2dPiG7#~|Cbq-l!n%wf$d5>%QJ$@U2)MjZ#>VX1%B+Un&V5dNXv3dGax&dG4 zT-OkMq7S+j0%1=qMVH7H*xmGN*8~jGlwXBOI^j>iJ-XmeLy@lg)lm~wf}OlKCI`ek z=gC;Jp~t<5encfJfT*D-cB3t*<>-6=__~{E_IXOP-yQHG4cNvPP_XvQ3+w7AsfSsj z+l!?!l{BCuBw)LDIQB6%Hp+1<2kk;Fk8F~$nCJ62T~LWiaQjMK`LCm={_0?M<~U2o z;tvRVSd3rLPwf7)>_9VVx*2G}ES-|?9v#J#*mHFDIC7U5KS4jrFU3g~o~0YXhm*NL z)i&&9XSrlljJ1TRl`T!TRy5N{t~f@rYB|NSRxy%g&2ifT^J_AaWzDpbR0$2kU~486 zgRD<;R?R}uvd>`ewp=t94adFh+L~_IF{Col&NXX8F()~zm3-AI8}=A|9$@^MQ6$9`N{*DfbIkqxS Iv-=nR0DRlh;Q#;t delta 899 zcmZ8fO-vI(6rQ)cY`5KZyKNJottdZ&DMbvHNGOP+98gY5qVi*!+Fh|gw`Fz#A&X!d zqVXUmV?3I2@o1vly_gs;o;cxbE@lEkS+~j>TZ{GWM=DqLz?Ake{l|!Kb z!EbqN)!6QMqur3%OI6vtj7N44f0>mJbgtj3e zC40WSy=E1PWlyur)ttFvT25}UT&{SUZ8}GNwLs0hW9FQ^z2Hfue9`h$x@_g{X3G?5 zG$qg!{?d~;n|9;&5Yqfm#7zD|TR~8S0B8_|^#uE)HnC4~8d7XWZqy~)DOWO_c@|+k zSr7O7BIy{qezf;!_6L!WhghQlhTuSz%nJroC9IXqi*}4zzD{Ul&wUe+W#4^qm|zFK z4!F*aD>u~_$Wp7=m!$&RQfA->`>OQ8H>UZgRNa;5qYLC2&?ZLxL(t1!`{Qt)ee}0L ziS7G4;T8)9;)7+pCsfaSYl(pQOEYc|S&xjQs~gASWChUMHd>B)Jd4c+vYT$CmU6mX zy^8W&K_SLb4h+`Zun`|18)2S^HojEAYkaO?NO2M;W8`*dBXYrD0>u( zYvskc(b{P)bG=1vM_Xhxpz^79HlpsqIQymEhha7vi3KiTU$3Iki|ldml|0I~mso%3 ziQq}>Ae4dLy|XWCBAp?Slk37*E~^7J{J6_nV*czt^45FV#uhZ6zCX~kYE z-KmrpO*+CJge%Ew7@n33=Hh}`^2CDWP=37!HXY;-3J;*i&3=j?Y_XI2y{>Lq7Fq>K zOo*DGNzI}H(w4L@{El`tgCZ!PKqJ&=CdoeQ=Q4R@Qklafhb9i~90U$$IP`JIa^TOU d18g|bFZzPgo*gj(GVDvVPtNhkEYo9Ce*s=l;l2O> diff --git a/v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc b/v2realbot/loader/__pycache__/trade_ws_streamer.cpython-310.pyc index 41cd1f0cef5f7a48580597a3d73f89494beb2122..5982bfca6b8b65595587602e91258e2316112173 100644 GIT binary patch delta 149 zcmaDT{ZN`apO=@50SFSipKs*8$I2MB`4y`u6Sopj=oWKMYF?4bWG)U3MvcYB9E^mnFMrzl10KGLKZ|QP4?%O6;J^&)j)(gh)|te!>z+;HhCp?9iz%*4IWupO^~oY fkhsO2oRe5woSRybk)HxlZ8mu>ulVLlo(x6+#fu@t delta 149 zcmaDT{ZN`apO=@50SM-w)7!{>kCpM(=2xtuOx(&qp 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 64d257503886762ca82a4b90c2c148f2e9820ba9..fb8ea99f5d97a0736d7e786e5c903b89b74a5dd6 100644 GIT binary patch delta 4262 zcmb7HYiwLs5%$?#+v~^r{fOVM*vUG!6Fc#PBuyR1&eKUO=S6SStk=HR_Tqc@ZqB_M zCn>P>p-NE^w44fPX%$4&LLjAr<)tEMDbSYkEC?Z8i2_0@62C!(KZyC}CRxX_M6sps z&Y77rXU@!=IcMKYeJK^OBaz}f`uoek-}G}&MOwt%Q`-hJB2O23$SksowPL38%@V6r zE43n8gvSMDnN_ZpTNPS`RjE~4Ra%u*tyQzE(5$g)wOXrAtF!90dTW!m$!gFVSQa)L zwMMdCWHwpNTC>%nwOFlME6a+_HmhB0Cpsha60^hV)H+#EYIa$hwarXN%xtVXg z++uCjwlZCAMy+10m+1;~o7JcFF1xs$)CR2~ZHUJ;=1yyu zw#ypUhDF{E@-S?UXe0EkHAk&6ZA|1H%G2xg`p5G0`bPuWhiJ4(Z=g{FjdrWgh@GN9 zy&(=~I|BLnV*?aX*-sD^5_GiNE3z*cQYLi6k-d;^1M~q{c{`}6x=^SO?EpDQ;Duw3 zD~-72uq{~yUU0>@;uT8wV%kU>au;-m)jtXsnub8`B#4%IA;&NmWfUANrk2B_p__T~ zgnB9bj@Y9zMK2#=Ck{hn58xEw4B#x_9N@!%(*)5XU+I1@ShEh)QNW~HE$$gV4w6mg ztYPLxCcOfGSMmfD7*47`7Eg%%+4hoKA~QzptA*!I^-oXE_EP|{jcCBPJ_T73$xfY~ zJxDm30Q-0YNNKPzQ=mluR0C502PsxkiNg`9;;tAsKKn1#-cQw<=EYc?-Sdp z%Twjm|2ne5^W124E*k#gFz&BgZpIxq=E}Hk#N32sZ~*+CQ3fR)P^o5W>c+=GBKFb% zECK=qUXV)AE5dBYja#Xkft=TC{ms5ulMrH5Rn(2`<@6qag$cHhNV;BrBAH6NUh&zu znKq`Rw55Cm8UuiwGvn&9y3PtNL3Sn$@k8o6b>j!QwH08D{>DAfD~~U_bOtF?GLFdJ z6Vu>A($#`4RSl<(;beAi{d)o7s;6bE z1IdmaCCUpf8Lq4b4}Hh0sLBfeSB<+TjJM9yoil7&ATs!6GVV~-2YOH-2 zqBZ_vcp-bfsZ7+elj~hmA8&3rGzl%<$ldd6vkO#3^kWxTxkIP#m)-UsP z_TA=vVql|Xa-07ImKq0ee(qOCTX#e|Y3zj#9+{q|N2eYdU09v7GJg zKNJ{@TARo~} ziF%N~0H7k|F9E*-pep3A3Fw&!OVJkjU9j*Jkp7`NL!=j;H|>R$`Sg`F4?{kAy!^+Y z&9CG&&V$E!%xy9Wo_`qC3xhl7UV-U?l(ZKUuDlN60710GKU@D_tT3BH&%l*7qsE4= ziD%U7L+{;wpy`GiPndVQidRU_11G*@$UkA(+pXq#SYk%VuXASn`3{!@5H0^8?U-Yu zW8(M>koy((%xBtH_AptM)lZ|zyNx@#))4s87v@H@;Z zjKy?&Ar@P6K5)4FEHs0{L6Yr&-vijIXF=s6mkZxA369Zua^Pb)FC-?zoAyX>qkH6E z>7(`x?^c=N`b-fGyqZfx^vlhhx7`8iXr?oepbjdFNyKf_tIf%cWZH5D7=2%HjQ#gi zIj9QQtH|l|OL|FGLRJN+2Gjs*2?Fx4svqen;h$*r;BHbEN9xoABlV(zsD9C?u8(XL zP3oiB4*Xj99hJ)&KGdbByiZJ`r3x(MiDuuPlM6UimN6Y*>4 z`;dgv>4feFbPLoGmyN>ch4<1=p|ol26Qtqbr(P(pG}tamMTKal|FEbDl+uX*Rqt5K Fe*vJos>J{R delta 4296 zcmb7HS#TU@5!Pr=NnYJ6S@&UC_FB?fmTXB5cFq7wQ)OaOMf_J$Bn}CfN;!rk2E+?h;R;8%0tt{xF%J|+kq2Jjh2o8(zaC2~3s({L z;oF|>{`>Fi@A>CO?u}f`j>XDL=%?*}ML+v=tW8;Zx^Gv(DpSj>a~jP(b}wTwwlx?t66Qf zTGSSnMa@>Vm28)pZC1P5Zgr>~R;Sv@vU0P~Wp%6F{7_+Tv3k@VrZ<^etzNa4>6p3A z+OBSAy3$NoJJcOaSDAfQzuM1qwK-rV)g;q3=1yx+9b~%J++_`^Lrm9^&agUc?N)d5 zbG^C88W~eZtWkB88Budg9V1WU=3eVQ^**KKP>J54H(oB$8=ojuKR{2L^(K01qNjcG zNo81x%I_)<)Ok@yTQE|KhU-Np&Kp8oG*-f;q0rbMWn1hdNGJkyVyjnXlY+?Th9mkR z9RMT&th@_UpPY*5Lqi~k3A|{^afP8-4%-s-;02bAi(W*y=kiA05F^kXmA{XqYj%U& zLy)NSf{tM>$kWl$$~zjwzEvV-YR8PX!o z06GDj{Bck%@*lC`0%zdv;04#O6*}xnH&I^9PeDGXYcBOBJKX`5M*&TMO6Y}7&Q2Vd z_PaSOxZd2jbI{NLX#o39HI1!S7R+NSw=4gpDEs8i>b4_$iIa%=bz#ea$%$EZ%`4Aj z^-NlGZQ+PVNX;wPaycWbr{*=`1qmEsf*qW~lKW+4O`-*!c)^ToSbi&TKQWwGnXTEP z^v#ok$O3rhJFSeJg$)=d@B%s85fk!DwefC;o)bYo!t; z6+`h2!hj!7m8m&y$`zV!q}+^Uu=oCXuZGeBpbmDbU6EN_q*ioooO#Xl3$YIhi-09S zDO3a0I9{16m+LFyTn0FKmwQ+1Gm5fT{xd#S!R45S!T~wf&>FuCau85-U{Yopx~sW2 zc)tq}e?new7(d9PD2(S}0A`j~r7gJRC}o`7%-GqxaurBtT{Yku`dtonHNID>oR`t& z)EGA`+T8bon|Kb7>`yV&40woFd2PTJ09 zjkLQqImAJ%!$w(eGv z_j00`n#Zx}B!H_oA$PV9Dm`+pJzmFWD8vVxqNaC`=#p33hkCeep2ZSQ?hQ~I;$N13 zX#ZGqQE`L#Md@@*4ZMVfw*X%SeC-}~ZW8Z%6pVOT{-wiw5X$s6c^?sRnGw+~#XpN1 zJa}F{+xbWr+f&5z*!dN}2CXZyq3e`#OIlrx_bl^O`ApY?9UKC$yqAq@D}nAuOD6@H z_riRb9g)Lc6xO=QBIcnGbzC|XS>2(b&-J(jrHg<|@$9((-B;S%B-MXdlMOcX8oTf~BF=b|G zuzUf$;91AcdLcb;g0=%U}41hN!y&cyEiy9Vg%FeqyT)ohF&7@n$b!c+F82G@v zO1yU{BhD-l<$vqY}h;$lh|3x+VV zX+wOUBpY`)C&SzSHu_1Dc*vMsSknfn2_ zsFQdd@I%0l06zx&1mJrN>J7k80dQUX3;@S{v%grQiaE&3*LGwJ)sQR!xB^jJEVOyk z5Vhb10G|f*0=5&-tIf7efyD8<)_;vuK@yrh{_wE_A_Y+?;26N4(4cspKLu(IfYD)y zEg+WxF9L7`{S$Hp)K$PW07~X<;)YFIhP!0zeq)}4@Hc=wpqRyCqzPHs?iaW zvny2thf0%u(B)gmE>Hq+8!!&wW=C`RqiS^J^3EZpP^<#yzE}z_)P_=U6}TQ;1=J%E z5$Ff;D*!q|{2K5U09_&8CZIbXEG62+x4{|*_@@$ASuZ+o+Ue!_{Ka+GIrf&fFF>2G zyz86?kLy?rG6$Z2HszDMhUZ>`>2OZi3mI2@8Nxw=M1_B{{=Zyd^~@Ta05vPyhCZWQ zmp6yrE5J`Lq8qN3F;~Zq7qJY-q3;p#Cz7ttq8G4~89~3#nepdUE+-&S^?s%^=Vk#5 zC)qO|Xo#85w4cc+J+6)to)~{5h8JGGpov9?_xA$AzOXU0=j55))2ZLW*6#rCU{xfQ z((QCAweEc2aQRhe2Ze(qx&iM3W&zIuikV}I(|7jTo_O>uNfZ0UUuD~#{RL45-Eso8 z{`?SqbeZ$Eo6NhE(xgk{)4w|WmnZ|#CM=B+c=6&=Bb&FJB%|*%&a;2ZsR30B{pwZy z<8G2}6API|z7G1A|2Whd0F8hn1ZvCb0yk;ebgdLgZE;Z;i?}lj{wQmLZnJD38BkgX z8|3Fk!g6V(Cfthk-SWgpW9$N5j`+ipzDV2TS4R4kcKH@9=#c*&Nwjv7qF1k(IW4Uv z@xIGs7w-hpC8tJvm2P=rWi$_i^?t6Hlj z3RP&HS!2~|wX&_6by^+B^38f{nYPSo&>E~pt#NKFqD5dVXf|2RTCuDJ+hwlbjK`Y_gOBr3lHqxbRW86!tXc%<+Xf^AnHEa{x3=C?y z0wcAwZrZDDq4jhbSZ$?o+CUqprir$VMrad=w$okAKTo!WZUxy8SZrhcK)Rhi4*dhr z-vRTrQ`+sKyFqqvo@{{b0oftCml3eqM+d?2VMuE~9h!o?h9Iva^Z-2wd1xR$L=S`X zFzY|((T?US^awjjHMS1oJ_>mqrB8tQ6Z9Ck%t*UIdYtMYJw{K^D6kx-!!!n8ovQnl zi18WLU4*!~Lx#niRMbiiK+m@?&C;UR^IDhJ^$qbTWp1JTY&JNHr3^da=F9&6R5X=# z+~T>8oiuI78JbM8*;^iqkZLjL>u#t47rYjs4nPZ-ES}O$HlpQnV`Nk|XB6HduKA*5 zsR;QSNSkP{42iw|S4o5Tr@v)o3mQvkg_fHavJWAhZJvZRIPly%HgdI(Uzo+NTn|sIUg1OVS++7;Cz)+llZf^PD;J z9iZU&0Ya*mcvap#`$eFE3{--|%}sJ6#yTjAMLc{RGWDR?$D=T@+3^HMSV~VBvGIMN z;{*T#0WS%7f`s?Z^1T15BqB~0)OBq{3XkQCxE{_@u4kN0V#~?qD`XQ91tK?xvUoIYeu*jIMB_|{G?>ND)zC-qbJn!P)7@ z>Ipd{t`_xGXDwEd!yu28t*0226OUR~M6h@X(PcV%MK1Y7Y%hMRH0Md>97z?W70#Yi z#&Y*7sbb+PxkXCE-%D!h7K3lT6=4YimMC8e;0AO#MY_(bM0;s}k= zDHpzlE1MD^ol#w{!_2s=#*^8+kgSm~Qo8<;7+(0oU>aSYM3?|@y);^d-=J{x(85Y8fe1>pg$Y~fz# z7Qmn2=im*Q2L)F|@<`CnE%CcW1AF&?5ZEi=l6&%N*nbd^v|A6dJ7|ZcU*Y5H=l0E|we=u~5Pm-M zkp%N?-8^om6SO04OtAmlqbT$9=ftLpH^^4;kBW-==RuzB@>!6nWX@%omp=;!Q9PW* zO_Bjr@q%s{DIr{lH!D9O7sRE-P2?qUWAVIQpoa=8hKNX=gMA zhkur5H)uGzon#3e6@p^#k_F_7h%b3x|2A@6K==;AcM-t7!oP>`eT0`0E+f2#kQHtL zFvsAcV3N8FZ21hTT}60(_TtifGPMlbIIEv|5HL0V{~Sj=Zr~JS<1{+y@Rv{%gD`IA zfMF1=%TR`7{0)TrDiI%l1qH7n_!01cb#qQJ5e^4B51;z%7PuBBsWcn@EdSa zZS0>ywWkoCMvyy3uE2xbWJ~V5cR*z~QlLgaf%}i}cjS%yuiDKG-$a53W@zF1QaqiA z?Yrl;g7A0ofSjGE`y1II4-I(OIO!zg{3e>8MHmKvD|r&z(+FP$n1Q#E4&@q!9iwO5 z$e7ULxVW;c;lTV<+#I+z>=@{xhFdUa8)K$<&tAs*9y*Xr7{JiO*@bfXO;OkIdGQHk z@Ztr*$5DN?sBSFl--^v3LLou{K*Z1A$My#Rt|!4xx#YA<#$7UzH8GNBRGe+BCFA1d z#^_$xvdc`!E0V&n1_XU+>qjHf=l zyKch=&id-iXDyn_42V2pU_ppMc)-QDG0doZU+=alGdWHwUx2U>VG)4qPfaG_ zDZu4HXP~6$VX{Qr7~$opU4c-Ea0Ioy>9j#PzJT0HFM}z}yn&XYLJXk_fr`~FHB%p< zmOmLy(5S;d#!mi&$DYH(Adwa1df*1mJgC}G)``%C(2ejmLN5Xq8En*a(qyi8*tShR zfZ{=fg9ti89KlqSsmxPAQ%|N{AG{k=XEMh&-pTwq1iUJ_K)8Z%72z5}GXkDf{00J^ zCj4Cl+!tHh;@` delta 3609 zcmZvedu&_P8Nhw6A8{NfIE|AyN!{2^Qpa|hrg=4O($a)JQ<}bCH?$YW=h$&`{ph)N z`Wg$R5gQtmmNOV-57^3>B5eg0ZA=VQI;3`+G}di^*oAH?Voa5gU}Dn#*i`m?H+>{- z6Zv<~{m%D1&wG5|2g9YA(o%;B{@#4^99?|6)GPg4I^FV0H8D{WmDGB<9-a=>m-5U0 zR6sucdCi2;IX1UpDm#WpNkQ`1$MfREOLF zeKG1{owS^F(Rr*pZle{{J!zGf(n?x2X_9+rHTA%A8LgqU@LWzQozEtEsh9P}Ep!2` z2jxEMV=Jhitz`Ft3#rBgJptM*s&VUpJ|=L_rUk{=Rz18t`rU{f|hTQqj!Mb2i8M#WUJ{K zkZlHwwQL3WTu0Z#`xbcLz$QBBpkcU?-Veg9hVZ6r6CDECHoBP+Fxx`6Lg4LC*EYHx z>e>!FHCn^R6@MstaVv%0jw-ukMIFxyUOA@MP&)O zm?smXMsv!Prn$gfe2wJ)$#xt76sFKfi!26eF07+_- zY{VuxiH%5K@piCo5toa;IS@hH21E;B$6CG!+lvteZk;#X(#)asN>o7ayi+`9e`skZ zHro+*GcCGdx&ah48^A4Dh(+RE(?i8UGE@x`y*SI0G1g95EM(?O(NhnKtvs4bW(sq$ zCs~;&Wo1KLBPQUHAE|Dhjo6^RfK6GJDX$jrTSxz^Pu_M^h3jLL_ z@u;fAGHIBuWGttM1!eb=L9xHAudNAOgotidwOpZ9QU5sO(FA)MlaK%|!iU7~%U)gH zibiJH5zDA5i$MWtz8*w+5oPgcUcJMUU5mz<0!2M5esNDd**5*&Jxzpc7yotj`wOPQ zr7_4dmr-&I76^}~ej@gjd&#gk2D~iJl)ph-Vszdoq+D#O2sC;iEFVDBAbf~gfNoc2 zN>zBB5EcHV^RTA^fn$mlXGRVbR$OvG%cK>J@nlq;1~n}#zNm;;JQ5!f4en=1M7-|q zCIjMM?%K*2`jQc|DiFTPs=7a-r3)aDGU9??8L>YsU3Z!JsMuQhb+STSu6%)1ii1@h z;RB#HCFvH8sc~J3XJoTmx1gH_!(=g*2W z@qNUP5CvJ5LZq0Q(U=jc5^VX8QSBwf*=ec1gfs@Q?FU>lBd{V~7+5UvRd$`h9t@iN zv8P#+biTOa+|Hw^HFhzl-%RLADx)_%(4Q z;9q2n>#tDn^}8r91lcN7{xxM1g%Ey0Y;N2WJUQ0{ zwRLNb=hLyFn_+7SzX)MH)1NkeLI#bUZqcn;KFc`2gyx43`vGvH<+1%R;y7T6=*0@G z%BZF(%1mZPK4>v4+`-_^xn<}@a9U?#pogX$PAr69#Uu8qNcn^j#yk_|BTVZG;_rw8zs}ewi;WqVBhwy3pAu-n7sEfTijt;W-$ ztff=YZMh@aWIDm!C}RNKHQvryR2|9WTA|2zGQlgcuL@C(*oj(}d_GAzo?5zv!3fNJ z8PsCGf~Z40C_ZWlOz5a&%SO{Qs&On|FS-3mqj4N^V}f;aGM$_G%m|~b9np#CLYznR zB5)1BKFViRrdvib8I=#BcnGl#F^m{NjG9anGuNvu9n9-i_&UlR<FLmw5)Kp P)`?JSgv=K^TU-7QzN%J> 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: