diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index be49901..17fbd42 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -97,9 +97,7 @@ def init(state: StrategyState): #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.last_buy_index = None state.vars.last_exit_index = None @@ -114,9 +112,15 @@ def init(state: StrategyState): state.vars.blockbuy = 0 #models state.vars.loaded_models = {} + + #INITIALIZE CBAR INDICATORS - do vlastni funkce #state.cbar_indicators['ivwap'] = [] + state.vars.last_tick_price = 0 + state.vars.last_tick_volume = 0 + state.vars.last_tick_trades = 0 state.cbar_indicators['tick_price'] = [] state.cbar_indicators['tick_volume'] = [] + state.cbar_indicators['tick_trades'] = [] state.cbar_indicators['CRSI'] = [] initialize_dynamic_indicators(state) diff --git a/v2realbot/config.py b/v2realbot/config.py index 4a7a668..991a15b 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -5,6 +5,7 @@ from pathlib import Path #directory for generated images and basic reports MEDIA_DIRECTORY = Path(__file__).parent.parent.parent / "media" +RUNNER_DETAIL_DIRECTORY = Path(__file__).parent.parent.parent / "runner_detail" #location of strat.log - it is used to fetch by gui LOG_FILE = Path(__file__).parent.parent / "strat.log" diff --git a/v2realbot/controller/runner_details.py b/v2realbot/controller/runner_details.py new file mode 100644 index 0000000..c99935d --- /dev/null +++ b/v2realbot/controller/runner_details.py @@ -0,0 +1 @@ +#PLACEHOLDER TO RUNNER_DETAILS SERVICES - refactored \ No newline at end of file diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 43c5f7e..cef6965 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -14,7 +14,7 @@ from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeSt from datetime import datetime from v2realbot.loader.trade_offline_streamer import Trade_Offline_Streamer from threading import Thread, current_thread, Event, enumerate -from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR,BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_LOG_SURROUNDING_TRADES,BT_FILL_CONDITION_BUY_LIMIT,BT_FILL_CONDITION_SELL_LIMIT, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN, MEDIA_DIRECTORY +from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR,BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_LOG_SURROUNDING_TRADES,BT_FILL_CONDITION_BUY_LIMIT,BT_FILL_CONDITION_SELL_LIMIT, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN, MEDIA_DIRECTORY, RUNNER_DETAIL_DIRECTORY import importlib from alpaca.trading.requests import GetCalendarRequest from alpaca.trading.client import TradingClient @@ -38,6 +38,9 @@ from v2realbot.strategyblocks.indicators.indicators_hub import populate_dynamic_ from v2realbot.interfaces.backtest_interface import BacktestInterface import os from v2realbot.reporting.metricstoolsimage import generate_trading_report_image +import msgpack +import gzip +import os #import gc #from pyinstrument import Profiler #adding lock to ensure thread safety of TinyDB (in future will be migrated to proper db) @@ -1227,6 +1230,7 @@ def delete_archived_runners_byBatchID(batch_id: str): print(f"error delete") return res, f"ERROR deleting {batch_id} : {val}" + #delete runner in archive and archive detail and runner logs #predelano do JEDNE TRANSAKCE def delete_archived_runners_byIDs(ids: list[UUID]): @@ -1297,6 +1301,32 @@ def delete_archive_header_byID(id: UUID): # region ARCHIVE DETAIL +#FILE SERVICES +#during runner_detail refactoring to file here https://chat.openai.com/c/b18eab9d-1e1c-413b-b25c-ccc180f3b3c2 +#vcetne migrace +def get_rd_fn(runner_id): + return str(RUNNER_DETAIL_DIRECTORY / f"{runner_id}.msgpack.gz") + +# Helper functions for file operations +def save_to_file(runner_id, data): + file_path = get_rd_fn(runner_id) + with gzip.open(file_path, "wb") as f: + f.write(msgpack.packb(data)) + +def read_from_file(runner_id): + file_path = get_rd_fn(runner_id) + if os.path.exists(file_path): + with gzip.open(file_path, "rb") as f: + return msgpack.unpackb(f.read()) + return None + +def delete_file(runner_id): + file_path = get_rd_fn(runner_id) + if os.path.exists(file_path): + os.remove(file_path) + return True + return False + #returns number of deleted elements def delete_archive_detail_byID(id: UUID): conn = pool.get_connection() diff --git a/v2realbot/endpoints/__init__.py b/v2realbot/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/v2realbot/endpoints/archived_runners.py b/v2realbot/endpoints/archived_runners.py new file mode 100644 index 0000000..e69de29 diff --git a/v2realbot/endpoints/batches.py b/v2realbot/endpoints/batches.py new file mode 100644 index 0000000..e69de29 diff --git a/v2realbot/endpoints/configs.py b/v2realbot/endpoints/configs.py new file mode 100644 index 0000000..e69de29 diff --git a/v2realbot/endpoints/models.py b/v2realbot/endpoints/models.py new file mode 100644 index 0000000..e69de29 diff --git a/v2realbot/endpoints/runners.py b/v2realbot/endpoints/runners.py new file mode 100644 index 0000000..e69de29 diff --git a/v2realbot/endpoints/stratins.py b/v2realbot/endpoints/stratins.py new file mode 100644 index 0000000..e69de29 diff --git a/v2realbot/endpoints/testlists.py b/v2realbot/endpoints/testlists.py new file mode 100644 index 0000000..e69de29 diff --git a/v2realbot/reporting/metricstoolsimage.py b/v2realbot/reporting/metricstoolsimage.py index 14a9089..516b769 100644 --- a/v2realbot/reporting/metricstoolsimage.py +++ b/v2realbot/reporting/metricstoolsimage.py @@ -348,7 +348,7 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, #Plot 8 Cumulative profit - bud 1 den nebo vice dni + pridame pod to vyvoj ceny # Extract the closing prices and times - closing_prices = bars.get('close',[]) + closing_prices = bars.get('close',[]) if bars is not None else [] #times = bars['time'] # Assuming this is a list of pandas Timestamp objects times = pd.to_datetime(bars['time']) if bars is not None else [] # Ensure this is a Pandas datetime series # # Plot the closing prices over time @@ -372,7 +372,8 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, ax2.tick_params(axis='y', labelcolor='orange') # Set the limits for the x-axis to cover the full range of 'times' - axs[1, 3].set_xlim(times.min(), times.max()) + if isinstance(times, pd.DatetimeIndex): + axs[1, 3].set_xlim(times.min(), times.max()) sns.lineplot(x=exit_times, y=cumulative_profits, ax=axs[1, 3], color='limegreen') axs[1, 3].scatter(max_profit_time, max_profit, color='green', label='Max Profit') axs[1, 3].scatter(min_profit_time, min_profit, color='red', label='Min Profit') diff --git a/v2realbot/static/js/ml.js b/v2realbot/static/js/ml.js index 5894c59..968f011 100644 --- a/v2realbot/static/js/ml.js +++ b/v2realbot/static/js/ml.js @@ -125,7 +125,7 @@ $(document).ready(function() { require(["vs/editor/editor.main"], () => { model_editor_json = monaco.editor.create(document.getElementById('toml-editor-container'), { - value: response.cfg_toml ? response.cfg_toml : JSON.stringify(response,null,4), + value: response.cfg_toml ? response.cfg_toml + ((response.history) ? "\nHISTORY:\n" + JSON.stringify(response.history,null,4) : "") : JSON.stringify(response,null,4), language: 'toml', theme: 'tomlTheme-dark', automaticLayout: true, diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index c38e53c..65bd77a 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -744,6 +744,7 @@ class StrategyState: self.ilog_save = ilog_save self.sl_optimizer_short = optimsl.SLOptimizer(ptm.TradeDirection.SHORT) self.sl_optimizer_long = optimsl.SLOptimizer(ptm.TradeDirection.LONG) + self.cache = defaultdict(dict) bars = {'high': [], 'low': [], diff --git a/v2realbot/strategyblocks/indicators/cbar_price.py b/v2realbot/strategyblocks/indicators/cbar_price.py index a15400c..a7ce790 100644 --- a/v2realbot/strategyblocks/indicators/cbar_price.py +++ b/v2realbot/strategyblocks/indicators/cbar_price.py @@ -9,20 +9,24 @@ def populate_cbar_tick_price_indicator(data, state: StrategyState): try: tick_price = data['close'] tick_delta_volume = data['volume'] - state.vars.last_tick_volume + tick_delta_trades = data['trades'] - state.vars.last_tick_trades state.cbar_indicators.tick_price[-1] = tick_price state.cbar_indicators.tick_volume[-1] = tick_delta_volume + state.cbar_indicators.tick_trades[-1] = tick_delta_trades except: pass - state.ilog(lvl=0,e=f"TICK PRICE CBARV {tick_price} VOLUME {tick_delta_volume} {data['confirmed']=}", prev_price=state.vars.last_tick_price, prev_volume=state.vars.last_tick_volume) + state.ilog(lvl=0,e=f"TICK PRICE CBARV {tick_price} VOLUME {tick_delta_volume} TRADES {tick_delta_trades} {data['confirmed']=}", prev_price=state.vars.last_tick_price, prev_volume=state.vars.last_tick_volume, prev_trades=state.vars.last_tick_trades) state.vars.last_tick_price = tick_price state.vars.last_tick_volume = data['volume'] + state.vars.last_tick_trades = data['trades'] if conf_bar == 1: #pri potvrzem CBARu nulujeme counter volume pro tick based indicator state.vars.last_tick_volume = 0 + state.vars.last_tick_trades = 0 state.vars.next_new = 1 #pro standardní CBARy @@ -30,6 +34,7 @@ def populate_cbar_tick_price_indicator(data, state: StrategyState): if conf_bar == 1: #pri potvrzem CBARu nulujeme counter volume pro tick based indicator state.vars.last_tick_volume = 0 + state.vars.last_tick_trades = 0 state.vars.next_new = 1 @@ -45,13 +50,16 @@ def populate_cbar_tick_price_indicator(data, state: StrategyState): #tick_price = round2five(data['close']) tick_price = data['close'] tick_delta_volume = data['volume'] - state.vars.last_tick_volume + tick_delta_trades = data['trades'] - state.vars.last_tick_trades state.cbar_indicators.tick_price[-1] = tick_price state.cbar_indicators.tick_volume[-1] = tick_delta_volume + state.cbar_indicators.tick_trades[-1] = tick_delta_trades except: pass - state.ilog(lvl=0,e=f"TICK PRICE CBAR {tick_price} VOLUME {tick_delta_volume} {data['confirmed']=}", prev_price=state.vars.last_tick_price, prev_volume=state.vars.last_tick_volume) + state.ilog(lvl=0,e=f"TICK PRICE CBAR {tick_price} VOLUME {tick_delta_volume} {data['confirmed']=}", prev_price=state.vars.last_tick_price, prev_volume=state.vars.last_tick_volume, prev_trades=state.vars.last_tick_trades) state.vars.last_tick_price = tick_price - state.vars.last_tick_volume = data['volume'] \ No newline at end of file + state.vars.last_tick_volume = data['volume'] + state.vars.last_tick_trades = data['trades'] \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/classed.py b/v2realbot/strategyblocks/indicators/custom/classed.py index ecb3587..cf4b83f 100644 --- a/v2realbot/strategyblocks/indicators/custom/classed.py +++ b/v2realbot/strategyblocks/indicators/custom/classed.py @@ -23,49 +23,49 @@ import importlib #OBECNA trida pro statefull indicators - realized by class with the same name, deriving from parent IndicatorBase class #todo v initu inicializovat state.classed_indicators a ve stopu uklidit - resetovat def classed(state, params, name): - funcName = "classed" - if params is None: - return -2, "params required" - - init_params = safe_get(params, "init", None) #napr sekce obcahuje threshold = 1222, ktere jdou kwargs do initu fce - #next_params = safe_get(params, "next", None) - - #List of sources, ktere jde do nextu (muze jit i vice serie) - #Do nextu jde ve stejnojmenném parametru - next_sources = safe_get(params, "next", []) #this will map to the sources_dict - next_mapping = safe_get(params, "next_mapping", next_sources) #this will dictate the final name of the key in sources_dict - - #ukládáme si do cache incializaci - cache = safe_get(params, "CACHE", None) - if cache is None: - if len(next_sources) != len(next_mapping): - return -2, "next and next_mapping length must be the same" - # Vytvorime dictionary pro kazdy source a priradime serii - #source_dict = {name: get_source_series(state, name) for name in next_sources} - #TBD toto optimalizovat aby se nevolalo pri kazde iteraci - source_dict = {new_key: get_source_series(state, name) - for name, new_key in zip(next_sources, next_mapping)} - params["CACHE"] = {} - params["CACHE"]["source_dict"] = source_dict - else: - source_dict = params["CACHE"]["source_dict"] - try: + funcName = "classed" + if params is None: + return -2, "params required" + + init_params = safe_get(params, "init", {}) #napr sekce obcahuje threshold = 1222, ktere jdou kwargs do initu fce + #next_params = safe_get(params, "next", None) + + #List of sources, ktere jde do nextu (muze jit i vice serie) + #Do nextu jde ve stejnojmenném parametru + class_name = safe_get(params, "class_name", None) + if class_name is None: + return -2, "class_name required" + next_sources = safe_get(params, "next", []) #this will map to the sources_dict + next_mapping = safe_get(params, "next_mapping", next_sources) #this will dictate the final name of the key in sources_dict + + if state.cache.get(name, None) is None: + if len(next_sources) != len(next_mapping): + return -2, "next and next_mapping length must be the same" + # Vytvorime dictionary pro kazdy source a priradime serii + #source_dict = {name: get_source_series(state, name) for name in next_sources} + #TBD toto optimalizovat aby se nevolalo pri kazde iteraci + source_dict = {new_key: get_source_series(state, name) + for name, new_key in zip(next_sources, next_mapping)} + state.cache[name]["source_dict"] = source_dict + else: + source_dict = state.cache[name]["source_dict"] + + if name not in state.classed_indicators: - classname = name - class_module = importlib.import_module("v2realbot.strategyblocks.indicators.custom.classes."+classname) - indicatorClass = getattr(class_module, classname) + class_module = importlib.import_module("v2realbot.strategyblocks.indicators.custom.classes."+class_name) + indicatorClass = getattr(class_module, class_name) instance = indicatorClass(state=state, **init_params) print("instance vytvorena", instance) state.classed_indicators[name] = instance - state.ilog(lvl=1,e=f"IND CLASS {name} INITIALIZED", **params) + #state.ilog(lvl=1,e=f"IND CLASS {name} INITIALIZED", **params) if len(source_dict) >0: val = state.classed_indicators[name].next(**source_dict) else: val = state.classed_indicators[name].next() - state.ilog(lvl=1,e=f"IND CLASS {name} NEXT {val}", **params) + #state.ilog(lvl=1,e=f"IND CLASS {name} NEXT {val}", **params) return 0, val except Exception as e: diff --git a/v2realbot/strategyblocks/indicators/custom/classes/BarBasedOpenCloseOIO.py b/v2realbot/strategyblocks/indicators/custom/classes/BarBasedOpenCloseOIO.py new file mode 100644 index 0000000..1466a9e --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/classes/BarBasedOpenCloseOIO.py @@ -0,0 +1,60 @@ +from collections import deque +#import time +from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +#WIP +class BarBasedOpenCloseOIO(IndicatorBase): + """ + Bar Based Order Imbalance Oscillator (OIO) focusing on the imbalance between buying and selling + pressure in the market inside each bar. Is is reset when new bar arrives. + + Accept continous cbar, stores on the level of bar. + + Průběžně počítá imbalanci předchozích cen (od open do close) v rámci daného baru. + """ + def __init__(self, state=None): + super().__init__(state) + self.old_data = deque() # Stores tuples of (price, volume) + self.prev_index = None + + def next(self, index, close): + index, new_price = index[-1], close[-1] + + # Remove old data when a new bar comes + if self.prev_index is not None and self.prev_index != index: + self.old_data.clear() + + self.prev_index = index + + upward_pressure = 0 + downward_pressure = 0 + + # Calculate price changes and pressures, optionally weighted by volume + for old_price in self.old_data: + price_change = new_price - old_price + if price_change > 0: + upward_pressure += price_change + elif price_change < 0: + downward_pressure += abs(price_change) + + # Add new tick data with volume + self.old_data.append(new_price) + + # Calculate OIO + total_pressure = upward_pressure + downward_pressure + if total_pressure > 0: + oio = (upward_pressure - downward_pressure) / total_pressure + else: + oio = 0 # OIO is zero if there's no pressure in either direction + + return oio + +# Example usage +# oio_indicator_weighted = BarBasedOpenCloseOIO(use_volume_weighting=True) +# oio_indicator_unweighted = BarBasedOpenCloseOIO(use_volume_weighting=False) +# Example tick data: [(index, close, volume)] +# old_data = [(1, 100, 500), (1, 101, 600), ..., (2, 102, 550), ...] +# for data in old_data: +# oio_value_weighted = oio_indicator_weighted.next(*data) +# oio_value_unweighted = oio_indicator_unweighted.next(*data) +# print(f"Weighted Bar-Based OIO: {oio_value_weighted:.2f}, Unweighted Bar-Based OIO: {oio_value_unweighted:.2f}") \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/classes/TickBasedOIO.py b/v2realbot/strategyblocks/indicators/custom/classes/TickBasedOIO.py new file mode 100644 index 0000000..a9924c3 --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/classes/TickBasedOIO.py @@ -0,0 +1,116 @@ +from collections import deque +#import time +from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +#WIP +class TickBasedOIO(IndicatorBase): + """ + POZOR NIZE JE WEIGHTED VARIANTA - zakomentovana, mozna v budoucnu vyuzit + + tick-based Order Imbalance Oscillator (OIO) focusing on the imbalance between buying and selling + pressure in the market, calculates the difference in cumulative price changes in both directions + over a specified window of time. This approach assumes that larger price movements in one direction + indicate stronger market pressure from either buyers or sellers. + """ + def __init__(self, window_size_seconds=5, state=None): + super().__init__(state) + self.window_size_seconds = window_size_seconds + self.tick_data = deque() + + def next(self, time, close): + new_time, new_price = time[-1], close[-1] + + # Remove old data outside the time window + while self.tick_data and new_time - self.tick_data[0][0] > self.window_size_seconds: + self.tick_data.popleft() + + upward_pressure = 0 + downward_pressure = 0 + + # Calculate price changes and pressures + for old_time, old_price in self.tick_data: + price_change = new_price - old_price + if price_change > 0: + upward_pressure += price_change + elif price_change < 0: + downward_pressure += abs(price_change) + + # Add new tick data + self.tick_data.append((new_time, new_price)) + + # Calculate OIO + if upward_pressure + downward_pressure > 0: + oio = (upward_pressure - downward_pressure) / (upward_pressure + downward_pressure) + else: + oio = 0 # OIO is zero if there's no pressure in either direction + + return oio + +# # Example usage +# oio_indicator = TickBasedOIO(window_size_seconds=5) + +# # Example tick data: (timestamp, price) +# tick_data = [(1, 100), (2, 101), (3, 102), (4, 100), (5, 99), (6, 98), (7, 97), (8, 98), (9, 99), (10, 100)] + +# for data in tick_data: +# oio_value = oio_indicator.next(data) +# print(f"Tick-Based OIO: {oio_value:.2f}") + + +#WEIGHTED VARIANT + +# from collections import deque +# from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +# class TickBasedOIO(IndicatorBase): +# """ +# Tick-based Order Imbalance Oscillator (OIO) focusing on the imbalance between buying and selling +# pressure in the market, calculates the difference in cumulative price changes in both directions +# over a specified window of time, optionally weighted by volume. +# """ +# def __init__(self, window_size_seconds=5, use_volume_weighting=False, state=None): +# super().__init__(state) +# self.window_size_seconds = window_size_seconds +# self.use_volume_weighting = use_volume_weighting +# self.tick_data = deque() + +# def next(self, time, close, volume): +# new_time, new_price, new_volume = time[-1], close[-1], volume[-1] + +# # Remove old data outside the time window +# while self.tick_data and new_time - self.tick_data[0][0] > self.window_size_seconds: +# self.tick_data.popleft() + +# upward_pressure = 0 +# downward_pressure = 0 + +# # Calculate price changes and pressures, optionally weighted by volume +# for old_time, old_price, old_volume in self.tick_data: +# price_change = new_price - old_price +# weighted_change = price_change * old_volume if self.use_volume_weighting else price_change +# if price_change > 0: +# upward_pressure += weighted_change +# elif price_change < 0: +# downward_pressure += abs(weighted_change) + +# # Add new tick data with volume +# self.tick_data.append((new_time, new_price, new_volume)) + +# # Calculate OIO +# total_pressure = upward_pressure + downward_pressure +# if total_pressure > 0: +# oio = (upward_pressure - downward_pressure) / total_pressure +# else: +# oio = 0 # OIO is zero if there's no pressure in either direction + +# return oio + +# Example usage +# oio_indicator_weighted = TickBasedOIO(window_size_seconds=5, use_volume_weighting=True) +# oio_indicator_unweighted = TickBasedOIO(window_size_seconds=5, use_volume_weighting=False) +# Example tick data: (timestamp, price, volume) +# tick_data = [(1, 100, 500), (2, 101, 600), ..., (10, 100, 550)] +# for data in tick_data: +# oio_value_weighted = oio_indicator_weighted.next(*data) +# oio_value_unweighted = oio_indicator_unweighted.next(*data) +# print(f"Weighted Tick-Based OIO: {oio_value_weighted:.2f}, Unweighted Tick-Based OIO: {oio_value_unweighted:.2f}") diff --git a/v2realbot/strategyblocks/indicators/custom/classes/TickCount.py b/v2realbot/strategyblocks/indicators/custom/classes/TickCount.py new file mode 100644 index 0000000..e85e95f --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/classes/TickCount.py @@ -0,0 +1,26 @@ +from collections import deque +from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + + +class TickCount(IndicatorBase): + """ + Calculates the count of ticks within a rolling time window in seconds. + """ + def __init__(self, window_size_seconds=5, state=None): + super().__init__(state) + self.window_size_seconds = window_size_seconds + self.tick_times = deque() + self.tick_count = 0 + + def next(self, timestamp): + timestamp = timestamp[-1] + # Remove old timestamps outside the time window + while self.tick_times and timestamp - self.tick_times[0] > self.window_size_seconds: + self.tick_times.popleft() + self.tick_count -= 1 + + # Add new timestamp + self.tick_times.append(timestamp) + self.tick_count += 1 + + return self.tick_count \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/classes/TickVariance.py b/v2realbot/strategyblocks/indicators/custom/classes/TickVariance.py index d0b657b..fde6bf1 100644 --- a/v2realbot/strategyblocks/indicators/custom/classes/TickVariance.py +++ b/v2realbot/strategyblocks/indicators/custom/classes/TickVariance.py @@ -1,9 +1,9 @@ import numpy as np from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase -#usecase - pocitat variance ticku -# v ramci BARu - posilame sem index a resetujeme pri naslednem indxu -# do budoucna mo +#TOTO predelat - jsou tu dva UC - na ticku (per position and time), uvnitr baru (per index baru) +#rozdelit tyto a predelat + class TickVariance(IndicatorBase): def __init__(self, state, window_size=1): """ diff --git a/v2realbot/strategyblocks/indicators/custom/classes/TickVolumeWindow.py b/v2realbot/strategyblocks/indicators/custom/classes/TickVolumeWindow.py new file mode 100644 index 0000000..36ba56d --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/classes/TickVolumeWindow.py @@ -0,0 +1,28 @@ +from collections import deque +from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + + +class TickVolumeWindow(IndicatorBase): + """ + Calculates volume in the rolling time window in seconds. + """ + def __init__(self, window_size_seconds=5, state=None): + super().__init__(state) + self.window_size_seconds = window_size_seconds + self.volume = 0 + self.time_start = None + + def next(self, timestamp, volume): + timestamp = timestamp[-1] + volume = volume[-1] + + if self.time_start is None: + self.time_start = timestamp + + if self.time_start + self.window_size_seconds < timestamp: + self.volume = 0 + self.time_start = timestamp + + self.volume += volume + + return self.volume \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/classes/VolumeNeeded2Move.py b/v2realbot/strategyblocks/indicators/custom/classes/VolumeNeeded2Move.py new file mode 100644 index 0000000..6900bc6 --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/classes/VolumeNeeded2Move.py @@ -0,0 +1,200 @@ +from collections import deque +from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +class VolumeNeeded2Move(IndicatorBase): + """ + VOLUME NEEDED FOR MOVEMENT + + This indicator measures the volume required to move the price by a specified amount either upwards or downwards. + The track_mode parameter determines the direction of price movement to be tracked. + When the threshold for the opposite movement direction is reached, the indicator resets but returns the previous value. + + + TODO ted se volume neresetuje, ale porad nacita ??? !! + + NOTE pozor dela to neco trohcu jinyho + + ## pocita to average volume per unit of price movement (kolik VOLUME stála(spotřebovala) kumulativně 1 jednotuka pohybu - to je zajimavý) + - tzn. uloží si to za danou jednotku volume k jejimu dosazeni a ulozi si + TOTO VOLUME, JEDNOTKA k dosazeni + 45666, 0.03 + 36356536, 0.03 + 33333, 0.03 + + a pak spocte prumer kolik prumerne stala jednotka za cele predchozi obdobi a toto cele vraci + + projit, jak by to spravne melo fungovat pro UP and DOWN + + + 1) zkusit vracet ty konkretni nekumulativni hodnoty za obdobi prekonani (nebo prumernou "cenu" jednotky) + + 2) kumulovat podle okna (tzn. ze vracim prumernou cenu za jednotku za rolling window) + + + TODO vrácena verze s absolutním thresholdem, která fungocvala. + projít a zkusit relativní threshold nebo nejak uložit natvrdo ten pct ve formě ticku a ten používat po celou dobu, aby se nám neměnil + + PCT verze zde: + #https://chat.openai.com/share/954a3481-f43f-43ee-8cb5-c2278384fa20 + + referenční obrázky:https://trading.mujdenik.eu/xwiki/bin/view/Trading-platform/Indicators-detail/VolumeNeeded2Move/ + případně si vytvořit ref runny z této abs verze + + + TODO pridat jeste time_window, kdy pro CUM bude vracet jen za dane okno (promyslet) + """ + #TYPE - last or cumulative + def __init__(self, price_movement_threshold, track_mode='up', return_type="cum", state=None): + super().__init__(state) + self.price_movement_threshold_tmp = price_movement_threshold + self.price_movement_threshold = None + self.track_mode = track_mode + self.price_volume_data = deque() # Stores tuples of (price, volume) + self.last_price = None + self.accumulated_volume = 0 + self.last_avg_volume_per_price_movement = 0 + self.return_type = return_type + + def next(self, close, volume): + new_price = close[-1] + new_volume = volume[-1] + + #pri prvni iteraci udelame z pct thresholdu fixni tick a ten pak pouzivame + if self.price_movement_threshold is None: + self.price_movement_threshold = (new_price / 100) * self.price_movement_threshold_tmp + + print("threshold in ticks:",self.price_movement_threshold ) + + # Initialize last_price if not set + if self.last_price is None: + self.last_price = new_price + + # Accumulate volume + self.accumulated_volume += new_volume + + # Calculate price change + price_change = new_price - self.last_price + + # Check if the price movement threshold is reached + reset_indicator = False + if (self.track_mode == 'up' and price_change >= self.price_movement_threshold) or \ + (self.track_mode == 'down' and price_change <= -self.price_movement_threshold): + self.price_volume_data.append((self.accumulated_volume, abs(price_change))) + reset_indicator = True + elif (self.track_mode == 'up' and price_change <= -self.price_movement_threshold) or \ + (self.track_mode == 'down' and price_change >= self.price_movement_threshold): + reset_indicator = True + + # Reset if threshold is reached for either direction + if reset_indicator: + self.accumulated_volume = 0 + self.last_price = new_price + if len(self.price_volume_data) > 0: + #print("pred resetem",self.price_volume_data) + total_volume, total_price_change = zip(*self.price_volume_data) + #print("total volume, price change",total_volume,total_price_change) + #CELKEM PRUMER VOLUME za danou zemnu + if self.return_type=="cum": + self.last_avg_volume_per_price_movement = sum(total_volume)/len(total_volume) #/ sum(total_price_change) + #(last) + else: + #KONKRETNI zA PREDCHOZI CAST + self.last_avg_volume_per_price_movement = total_volume[-1] + + return self.last_avg_volume_per_price_movement + + +#PCT VARIANT + +# from collections import deque +# from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +# class VolumePriceMovementIndicator(IndicatorBase): +# """ +# This indicator measures the volume required to move the price by a specified percentage. +# It tracks the accumulated volume since the last time the price moved by the predefined threshold. +# """ +# def __init__(self, price_movement_threshold_pct, state=None): +# super().__init__(state) +# # Price movement threshold is now a percentage +# self.price_movement_threshold_pct = price_movement_threshold_pct / 100.0 # Convert to decimal +# self.price_volume_data = deque() # Stores tuples of (price, volume) +# self.last_price = None +# self.accumulated_volume = 0 + +# def next(self, index, close, volume): +# new_price = close[-1] +# new_volume = volume[-1] + +# # Initialize last_price if not set +# if self.last_price is None: +# self.last_price = new_price + +# # Accumulate volume +# self.accumulated_volume += new_volume + +# # Calculate percentage price change relative to the last price +# price_change_pct = abs((new_price - self.last_price) / self.last_price) + +# # Check if price movement threshold is reached +# if price_change_pct >= self.price_movement_threshold_pct: +# # Threshold reached, record the data and reset +# self.price_volume_data.append((self.accumulated_volume, price_change_pct)) +# self.accumulated_volume = 0 +# self.last_price = new_price + +# # Compute average volume per percentage of price movement +# if len(self.price_volume_data) > 0: +# total_volume, total_price_change_pct = zip(*self.price_volume_data) +# avg_volume_per_pct_price_movement = sum(total_volume) / sum(total_price_change_pct) +# else: +# avg_volume_per_pct_price_movement = 0 + +# return avg_volume_per_pct_price_movement + + +##puvodni absolutni funkcni + # def __init__(self, price_movement_threshold, track_mode='up', state=None): + # super().__init__(state) + # self.price_movement_threshold = price_movement_threshold + # self.track_mode = track_mode + # self.price_volume_data = deque() # Stores tuples of (price, volume) + # self.last_price = None + # self.accumulated_volume = 0 + # self.last_avg_volume_per_price_movement = 0 + + # def next(self, close, volume): + # new_price = close[-1] + # new_volume = volume[-1] + + # # Initialize last_price if not set + # if self.last_price is None: + # self.last_price = new_price + + # # Accumulate volume + # self.accumulated_volume += new_volume + + # # Calculate price change + # price_change = new_price - self.last_price + + # # Check if the price movement threshold is reached + # reset_indicator = False + # if (self.track_mode == 'up' and price_change >= self.price_movement_threshold) or \ + # (self.track_mode == 'down' and price_change <= -self.price_movement_threshold): + # self.price_volume_data.append((self.accumulated_volume, abs(price_change))) + # reset_indicator = True + # elif (self.track_mode == 'up' and price_change <= -self.price_movement_threshold) or \ + # (self.track_mode == 'down' and price_change >= self.price_movement_threshold): + # reset_indicator = True + + # # Reset if threshold is reached for either direction + # if reset_indicator: + # self.accumulated_volume = 0 + # self.last_price = new_price + # if len(self.price_volume_data) > 0: + # print("pred resetem",self.price_volume_data) + # total_volume, total_price_change = zip(*self.price_volume_data) + # print("total volume, price change",total_volume,total_price_change) + # self.last_avg_volume_per_price_movement = sum(total_volume) / sum(total_price_change) + + # return self.last_avg_volume_per_price_movement \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/classes/VolumeNeeded2Movev2.py b/v2realbot/strategyblocks/indicators/custom/classes/VolumeNeeded2Movev2.py new file mode 100644 index 0000000..60c8540 --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/classes/VolumeNeeded2Movev2.py @@ -0,0 +1,209 @@ +from collections import deque +from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +class VolumeNeeded2Movev2(IndicatorBase): + """ + VOLUME NEEDED FOR MOVEMENT + + This indicator measures the volume required to move the price by a specified amount either upwards or downwards. + The track_mode parameter determines the direction of price movement to be tracked. + When the threshold for the opposite movement direction is reached, the indicator resets but returns the previous value. + + + TODO ted se volume neresetuje, ale porad nacita ??? !! + + NOTE pozor dela to neco trohcu jinyho + + ## pocita to average volume per unit of price movement (kolik VOLUME stála(spotřebovala) kumulativně 1 jednotuka pohybu - to je zajimavý) + - tzn. uloží si to za danou jednotku volume k jejimu dosazeni a ulozi si + TOTO VOLUME, JEDNOTKA k dosazeni + 45666, 0.03 + 36356536, 0.03 + 33333, 0.03 + + a pak spocte prumer kolik prumerne stala jednotka za cele predchozi obdobi a toto cele vraci + + projit, jak by to spravne melo fungovat pro UP and DOWN + + + 1) zkusit vracet ty konkretni nekumulativni hodnoty za obdobi prekonani (nebo prumernou "cenu" jednotky) + + 2) kumulovat podle okna (tzn. ze vracim prumernou cenu za jednotku za rolling window) + + + TODO vrácena verze s absolutním thresholdem, která fungocvala. + projít a zkusit relativní threshold nebo nejak uložit natvrdo ten pct ve formě ticku a ten používat po celou dobu, aby se nám neměnil + + PCT verze zde: + #https://chat.openai.com/share/954a3481-f43f-43ee-8cb5-c2278384fa20 + + referenční obrázky:https://trading.mujdenik.eu/xwiki/bin/view/Trading-platform/Indicators-detail/VolumeNeeded2Move/ + případně si vytvořit ref runny z této abs verze + + + TODO pridat jeste time_window, kdy pro CUM bude vracet jen za dane okno (promyslet) + """ + #TYPE - last or cumulative + def __init__(self, price_movement_threshold, rolling_window_seconds = None, track_mode='up', return_type="cum", state=None): + super().__init__(state) + self.price_movement_threshold_tmp = price_movement_threshold + self.price_movement_threshold = None + self.rolling_window_seconds = rolling_window_seconds + self.track_mode = track_mode + self.price_volume_data = deque() # Stores tuples of (price, volume) + self.last_price = None + self.accumulated_volume = 0 + self.last_avg_volume_per_price_movement = 0 + self.return_type = return_type + + def next(self, close, volume, time): + new_price = close[-1] + new_volume = volume[-1] + new_timestamp = time[-1] + + #pri prvni iteraci udelame z pct thresholdu fixni tick a ten pak pouzivame + if self.price_movement_threshold is None: + self.price_movement_threshold = (new_price / 100) * self.price_movement_threshold_tmp + + print("threshold in ticks:",self.price_movement_threshold ) + + # Initialize last_price if not set + if self.last_price is None: + self.last_price = new_price + + # Keep only the items where the timestamp is within the window + # Efficiently remove old data + if self.rolling_window_seconds is not None: + while self.price_volume_data and self.price_volume_data[0][0] < new_timestamp - self.rolling_window_seconds: + self.price_volume_data.popleft() + + # Accumulate volume + self.accumulated_volume += new_volume + + # Calculate price change + price_change = new_price - self.last_price + + # Check if the price movement threshold is reached + reset_indicator = False + if (self.track_mode == 'up' and price_change >= self.price_movement_threshold) or \ + (self.track_mode == 'down' and price_change <= -self.price_movement_threshold): + self.price_volume_data.append((new_timestamp, self.accumulated_volume, abs(price_change))) + reset_indicator = True + #pokud prekonalo druhym smerem, nulujeme, volume si bere druhy indikator + elif (self.track_mode == 'up' and price_change <= -self.price_movement_threshold) or \ + (self.track_mode == 'down' and price_change >= self.price_movement_threshold): + reset_indicator = False + self.accumulated_volume = 0 + self.last_price = new_price + + if reset_indicator: + # Compute average efficiently + if len(self.price_volume_data) > 0: + total_volume = sum(v for _, v, _ in self.price_volume_data) + num_items = len(self.price_volume_data) + if self.return_type == "cum": + self.last_avg_volume_per_price_movement = total_volume / num_items + else: + self.last_avg_volume_per_price_movement = self.price_volume_data[-1][1] + + self.accumulated_volume = 0 + self.last_price = new_price + return self.last_avg_volume_per_price_movement + + return self.last_avg_volume_per_price_movement + + +#PCT VARIANT + +# from collections import deque +# from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +# class VolumePriceMovementIndicator(IndicatorBase): +# """ +# This indicator measures the volume required to move the price by a specified percentage. +# It tracks the accumulated volume since the last time the price moved by the predefined threshold. +# """ +# def __init__(self, price_movement_threshold_pct, state=None): +# super().__init__(state) +# # Price movement threshold is now a percentage +# self.price_movement_threshold_pct = price_movement_threshold_pct / 100.0 # Convert to decimal +# self.price_volume_data = deque() # Stores tuples of (price, volume) +# self.last_price = None +# self.accumulated_volume = 0 + +# def next(self, index, close, volume): +# new_price = close[-1] +# new_volume = volume[-1] + +# # Initialize last_price if not set +# if self.last_price is None: +# self.last_price = new_price + +# # Accumulate volume +# self.accumulated_volume += new_volume + +# # Calculate percentage price change relative to the last price +# price_change_pct = abs((new_price - self.last_price) / self.last_price) + +# # Check if price movement threshold is reached +# if price_change_pct >= self.price_movement_threshold_pct: +# # Threshold reached, record the data and reset +# self.price_volume_data.append((self.accumulated_volume, price_change_pct)) +# self.accumulated_volume = 0 +# self.last_price = new_price + +# # Compute average volume per percentage of price movement +# if len(self.price_volume_data) > 0: +# total_volume, total_price_change_pct = zip(*self.price_volume_data) +# avg_volume_per_pct_price_movement = sum(total_volume) / sum(total_price_change_pct) +# else: +# avg_volume_per_pct_price_movement = 0 + +# return avg_volume_per_pct_price_movement + + +##puvodni absolutni funkcni + # def __init__(self, price_movement_threshold, track_mode='up', state=None): + # super().__init__(state) + # self.price_movement_threshold = price_movement_threshold + # self.track_mode = track_mode + # self.price_volume_data = deque() # Stores tuples of (price, volume) + # self.last_price = None + # self.accumulated_volume = 0 + # self.last_avg_volume_per_price_movement = 0 + + # def next(self, close, volume): + # new_price = close[-1] + # new_volume = volume[-1] + + # # Initialize last_price if not set + # if self.last_price is None: + # self.last_price = new_price + + # # Accumulate volume + # self.accumulated_volume += new_volume + + # # Calculate price change + # price_change = new_price - self.last_price + + # # Check if the price movement threshold is reached + # reset_indicator = False + # if (self.track_mode == 'up' and price_change >= self.price_movement_threshold) or \ + # (self.track_mode == 'down' and price_change <= -self.price_movement_threshold): + # self.price_volume_data.append((self.accumulated_volume, abs(price_change))) + # reset_indicator = True + # elif (self.track_mode == 'up' and price_change <= -self.price_movement_threshold) or \ + # (self.track_mode == 'down' and price_change >= self.price_movement_threshold): + # reset_indicator = True + + # # Reset if threshold is reached for either direction + # if reset_indicator: + # self.accumulated_volume = 0 + # self.last_price = new_price + # if len(self.price_volume_data) > 0: + # print("pred resetem",self.price_volume_data) + # total_volume, total_price_change = zip(*self.price_volume_data) + # print("total volume, price change",total_volume,total_price_change) + # self.last_avg_volume_per_price_movement = sum(total_volume) / sum(total_price_change) + + # return self.last_avg_volume_per_price_movement \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/divergence.py b/v2realbot/strategyblocks/indicators/custom/divergence.py index a84e130..46a8261 100644 --- a/v2realbot/strategyblocks/indicators/custom/divergence.py +++ b/v2realbot/strategyblocks/indicators/custom/divergence.py @@ -23,8 +23,8 @@ def divergence(state, params, name): val = round((abs(float(source1_series[-1]) - float(source2_series[-1])))/float(source1_series[-1]),4) elif mode == "rel": val = round(float(source1_series[-1]) - float(source2_series[-1]),4) - elif mode == "reln": - val = round((float(source1_series[-1]) - float(source2_series[-1]))/float(source1_series[-1]),4) + elif mode == "reln": #div = a+b / a-b will give value between -1 and 1 + val = round((float(source1_series[-1]) - float(source2_series[-1]))/(float(source1_series[-1])+float(source2_series[-1])),4) elif mode == "pctabs": val = pct_diff(num1=float(source1_series[-1]),num2=float(source2_series[-1]), absolute=True) elif mode == "pct": diff --git a/v2realbot/strategyblocks/indicators/custom/model.py b/v2realbot/strategyblocks/indicators/custom/model.py index 334519e..43e9a30 100644 --- a/v2realbot/strategyblocks/indicators/custom/model.py +++ b/v2realbot/strategyblocks/indicators/custom/model.py @@ -7,6 +7,9 @@ from traceback import format_exc import numpy as np from collections import defaultdict +""" +Předpokladm, že buď používáme 1) bar+standard indikator 2) cbars indicators - zatim nepodporovano spolecne (jine time rozliseni) +""" def model(state, params, ind_name): funcName = "model" if params is None: @@ -28,10 +31,26 @@ def model(state, params, ind_name): try: mdl = state.vars.loaded_models[name] - if len(state.bars["close"]) < mdl.input_sequences: - return 0, 0 - #return -2, f"too soon - not enough data for seq {seq=}" - value = mdl.predict(state.bars, state.indicators) + + #Optimalizovano, aby se v kazde iteraci nemusel volat len + if state.cache.get(name, {}).get("skip_init", False) is False: + if mdl.use_cbars is False: + if len(state.bars["close"]) < mdl.input_sequences: + return 0, 0 + else: + state.cache[name]["skip_init"] = True + state.cache[name]["indicators"] = state.indicators + state.cache[name]["bars"] = state.bars if mdl.use_bars else {} + #return -2, f"too soon - not enough data for seq {seq=}" + else: + if len(state.cbar_indicators["time"]) < mdl.input_sequences: + return 0, 0 + else: + state.cache[name]["skip_init"] = True + state.cache[name]["indicators"] = state.cbar_indicators + state.cache[name]["bars"] = state.bars if mdl.use_bars else {} + + value = mdl.predict(state.cache[name]["bars"], state.cache[name]["indicators"]) return 0, value except Exception as e: printanyway(str(e)+format_exc()) diff --git a/v2realbot/strategyblocks/indicators/custom/slope.py b/v2realbot/strategyblocks/indicators/custom/slope.py index 07d9e52..4b3677d 100644 --- a/v2realbot/strategyblocks/indicators/custom/slope.py +++ b/v2realbot/strategyblocks/indicators/custom/slope.py @@ -1,7 +1,7 @@ from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.strategy.base import StrategyState from v2realbot.indicators.indicators import ema, natr, roc -from v2realbot.strategyblocks.indicators.helpers import get_source_series +from v2realbot.strategyblocks.indicators.helpers import get_source_series, find_index_optimized from rich import print as printanyway from traceback import format_exc import numpy as np @@ -13,13 +13,15 @@ def slope(state, params, name): source = safe_get(params, "source", None) source_series = get_source_series(state, source) - lookback_type = safe_get(params, "lookback_type", "positions") + lookback_unit = safe_get(params, "lookback_unit", "position") lookback = safe_get(params, "lookback", 5) lookback_priceline = safe_get(params, "lookback_priceline", None) #bars|close lookback_series = get_source_series(state, lookback_priceline) + #ukládáme si do cache incializaci + cache = safe_get(params, "CACHE", None) - match lookback_type: - case "positions": + match lookback_unit: + case "position": try: lookbackprice = lookback_series[-lookback-1] lookbacktime = state.bars.updated[-lookback-1] @@ -27,17 +29,22 @@ def slope(state, params, name): max_delka = len(lookback_series) lookbackprice =lookback_series[-max_delka] lookbacktime = state.bars.updated[-max_delka] - case "seconds": - #předpokládáme, že lookback_priceline je ve formě #bars|close - #abychom ziskali relevantní time - split_index = lookback_priceline.find("|") - if split_index == -1: - return -2, "for time it is required in format bars|close" - dict_name = lookback_priceline[:split_index] - time_series = getattr(state, dict_name)["time"] + case "second": + if state.cache.get(name, None) is None: + #předpokládáme, že lookback_priceline je ve formě #bars|close + #abychom ziskali relevantní time + split_index = lookback_priceline.find("|") + if split_index == -1: + return -2, "for time it is required in format bars|close" + dict_name = lookback_priceline[:split_index] + time_series = getattr(state, dict_name)["time"] + state.cache[name]["time_series"] = time_series + else: + time_series = state.cache[name]["time_series"] lookback_idx = find_index_optimized(time_list=time_series, seconds=lookback) lookbackprice = lookback_series[lookback_idx] - lookbacktime = time_series[lookback_idx] + lookbacktime = time_series[lookback_idx] + #výpočet úhlu - a jeho normalizace currval = source_series[-1] @@ -46,24 +53,3 @@ def slope(state, params, name): state.ilog(lvl=1,e=f"INSIDE {name}:{funcName} {slope} {source=} {lookback=}", currval_source=currval, lookbackprice=lookbackprice, lookbacktime=lookbacktime, **params) return 0, slope - -""" -TODO pripadne dat do -Finds index of first value less than X seconds -This version assumes: -time_list is always non-empty and sorted. -There's always a timestamp at least 5 seconds before the current time. -""" -def find_index_optimized(time_list, seconds): - current_time = time_list[-1] - threshold = current_time - seconds - left, right = 0, len(time_list) - 1 - - while left < right: - mid = (left + right) // 2 - if time_list[mid] < threshold: - left = mid + 1 - else: - right = mid - - return left if time_list[left] >= threshold else None \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/target.py b/v2realbot/strategyblocks/indicators/custom/target.py index c333989..40812d4 100644 --- a/v2realbot/strategyblocks/indicators/custom/target.py +++ b/v2realbot/strategyblocks/indicators/custom/target.py @@ -1,7 +1,7 @@ from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.strategy.base import StrategyState from v2realbot.indicators.indicators import ema, natr, roc -from v2realbot.strategyblocks.indicators.helpers import get_source_series +from v2realbot.strategyblocks.indicators.helpers import get_source_series, find_index_optimized from rich import print as printanyway from traceback import format_exc import numpy as np @@ -26,12 +26,18 @@ def target(state, params, name): source_series = get_source_series(state, source) #zatim podporovano jen bar - dokud nepridame o level vyse do "custom" save_to_past na urovni casu window_length_value = safe_get(params, "window_length_value", None) - window_length_unit= safe_get(params, "window_length_unit", "bar") + window_length_unit= safe_get(params, "window_length_unit", "position") + smoothing_window= int(safe_get(params, "smoothing_window", 1)) #pocet jednotek casu, kdy se signal drzi (zatm jen pro cas, TODO pro pozice) no_move_threshold= safe_get(params, "no_move_threshold", 0.033) pct_change_full_scale = safe_get(params, "pct_change_full_scale", 0.1) #pct change that is considered for full scaling factor move_base= safe_get(params, "move_base", 0.2) #base output value for move up/down, to this value scaling factor is added based on magnitude of change scaling_factor= safe_get(params, "scaling_factor", 0.8)# This represents the maximum additional value to be added to 0.5, based on steepness of the move - + #ukládáme si do cache incializaci + lookback_idx = None + #workaround for state + #params["last_activated_up"] = None + #params["last_activated_down"] = None + if window_length_value is None or source is None: return -2, "window_length_value/source required" @@ -42,16 +48,65 @@ def target(state, params, name): future_price = float(source_series[-1]) - try: - current_price = float(source_series[-window_length_value]) - except IndexError: - return 0, val - + #LOOKUP of current_price (price in the past) + if window_length_unit == "position": + try: + current_price = float(source_series[-window_length_value]) + except IndexError: + return 0, val + #seconds type, predpoklada opet source v kompletnim tvaru a predpoklada save_to_past typu seconds + else: + if state.cache.get(name, None) is None: + split_index = source.find("|") + if split_index == -1: + return -2, "for second based window length, source is required in format bars|close" + dict_name = source[:split_index] + time_series = getattr(state, dict_name)["time"] + state.cache[name]["time_series"] = time_series + else: + time_series = state.cache[name]["time_series"] + + lookback_idx = find_index_optimized(time_list=time_series, seconds=window_length_value) + current_price = source_series[lookback_idx] + upper_move_threshold = add_pct(no_move_threshold, current_price) lower_move_threshold = add_pct(-no_move_threshold, current_price) + #5 a 7 + def fill_skipped_indx(pastidx, curridx): + #mame mezi indexy mezeru, iterujeme mezi temito a doplnime predchozi hodnoty primo do indikatoru + if pastidx + 1 < curridx: + for i in range(pastidx+1, curridx): + ind_dict = get_source_series(state, name) + ind_dict[i] = ind_dict[i-1] + + if params.get("last_returned_idx", None) is not None: + #pokud je mezi poslednim indexem a aktualnim dira pak je vyplnime predchozi hodnotou + fill_skipped_indx(params["last_returned_idx"],lookback_idx) + #no move if lower_move_threshold <= future_price <= upper_move_threshold: + #SMOOTHING WINDOW (3 secs) - workaround of state + #pokud no move, ale jsme 2 sekundy po signálu tak vracíme predchozi hodnotu (realizováno vrácním -2) + #zatim pouze pro casove okno + #po osvedceni do conf + if params.get("last_activated_up",None) is not None: + #jsme v okno, vracime predchozi hodnotu + last_plus_window = float(time_series[params["last_activated_up"]]) + smoothing_window + if last_plus_window > float(time_series[lookback_idx]): + val = params["last_returned_val"] + #okno prekroceno, nulujeme a vracime 0 + else: + params["last_activated_up"] = None + + elif params.get("last_activated_down",None) is not None: + last_plus_window = float(time_series[params["last_activated_down"]]) + smoothing_window + if last_plus_window > float(time_series[lookback_idx]): + val = params["last_returned_val"] + else: + params["last_activated_down"] = None + params["last_returned_val"] = val + params["last_returned_idx"] = lookback_idx return 0, val #calculates weight based on magnitude of change @@ -64,13 +119,21 @@ def target(state, params, name): #price is bigger than threshold if upper_move_threshold < future_price: + params["last_activated_down"] = None magnitude_val = calculate_factor(current_price, future_price) - return 0, move_base + magnitude_val + params["last_activated_up"] = lookback_idx + params["last_returned_val"] = move_base + magnitude_val + params["last_returned_idx"] = lookback_idx + return 0, params["last_returned_val"] #price is bigger than threshold if lower_move_threshold > future_price: + params["last_activated_up"] = None magnitude_val = calculate_factor(current_price, future_price) - return 0, - move_base - magnitude_val + params["last_activated_down"] = lookback_idx + params["last_returned_val"] = - move_base - magnitude_val + params["last_returned_idx"] = lookback_idx + return 0, params["last_returned_val"] def pct_delta(base, second_number): diff --git a/v2realbot/strategyblocks/indicators/custom_hub.py b/v2realbot/strategyblocks/indicators/custom_hub.py index 3d2090d..bf04efb 100644 --- a/v2realbot/strategyblocks/indicators/custom_hub.py +++ b/v2realbot/strategyblocks/indicators/custom_hub.py @@ -6,6 +6,7 @@ from v2realbot.indicators.indicators import ema from traceback import format_exc import importlib import v2realbot.strategyblocks.indicators.custom as ci +from v2realbot.strategyblocks.indicators.helpers import find_index_optimized def populate_dynamic_custom_indicator(data, state: StrategyState, name): @@ -43,6 +44,8 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): # např. 5 - znamená ulož hodnotu indikatoru 5 barů dozadu namísto posledni hodnoty - hodí se pro vytvareni targetu pro ML trening save_to_past = int(safe_get(options, "save_to_past", 0)) + save_to_past_unit = safe_get(options, "save_to_past_unit", "position") + def is_time_to_run(): # on_confirmed_only = true (def. False) @@ -135,6 +138,17 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): state.vars.indicators[name]["last_run_time"] = datetime.fromtimestamp(data["updated"]).astimezone(zoneNY) state.vars.indicators[name]["last_run_index"] = data["index"] + + #pomocna funkce + def save_to_past_func(indicators_dict,name,save_to_past_unit, steps, new_val): + if save_to_past_unit == "position": + indicators_dict[name][-1-steps]=new_val + #time + else: + ##find index X seconds ago + lookback_idx = find_index_optimized(time_list=indicators_dict["time"], seconds=steps) + indicators_dict[name][lookback_idx]=new_val + # - volame custom funkci pro ziskani hodnoty indikatoru # - tu ulozime jako novou hodnotu indikatoru a prepocteme MAcka pokud je pozadovane # - pokud cas neni, nechavame puvodni, vcetna pripadneho MAcka @@ -145,15 +159,18 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): custom_function = eval(subtype) res_code, new_val = custom_function(state, custom_params, name) if res_code == 0: - indicators_dict[name][-1-save_to_past]=new_val + save_to_past_func(indicators_dict,name,save_to_past_unit, save_to_past, new_val) state.ilog(lvl=1,e=f"IND {name} {subtype} VAL FROM FUNCTION: {new_val}", lastruntime=state.vars.indicators[name]["last_run_time"], lastrunindex=state.vars.indicators[name]["last_run_index"], save_to_past=save_to_past) #prepocitame MA if required if MA_length is not None: src = indicators_dict[name][-MA_length:] MA_res = ema(src, MA_length) MA_value = round(MA_res[-1],7) - indicators_dict[name+"MA"][-1-save_to_past]=MA_value + + save_to_past_func(indicators_dict,name+"MA",save_to_past_unit, save_to_past, MA_value) state.ilog(lvl=0,e=f"IND {name}MA {subtype} {MA_value}",save_to_past=save_to_past) + + return else: err = f"IND ERROR {name} {subtype}Funkce {custom_function} vratila {res_code} {new_val}." diff --git a/v2realbot/strategyblocks/indicators/helpers.py b/v2realbot/strategyblocks/indicators/helpers.py index f1e4d4c..080e04f 100644 --- a/v2realbot/strategyblocks/indicators/helpers.py +++ b/v2realbot/strategyblocks/indicators/helpers.py @@ -2,6 +2,28 @@ from v2realbot.utils.utils import isrising, isfalling,isfallingc, isrisingc, zon #from v2realbot.strategy.base import StrategyState from traceback import format_exc + +""" +TODO pripadne dat do +Finds index of first value less than X seconds +This version assumes: +time_list is always non-empty and sorted. +There's always a timestamp at least 5 seconds before the current time. +""" +def find_index_optimized(time_list, seconds): + current_time = time_list[-1] + threshold = current_time - seconds + left, right = 0, len(time_list) - 1 + + while left < right: + mid = (left + right) // 2 + if time_list[mid] < threshold: + left = mid + 1 + else: + right = mid + + return left if time_list[left] >= threshold else None + #ZATIM tyto zkopirovany SEM DO HELPERS #podle toho jak se osvedci se zakl.indikatory to s state #zatim se mi to moc nezda diff --git a/v2realbot/strategyblocks/indicators/indicators_hub.py b/v2realbot/strategyblocks/indicators/indicators_hub.py index d8fce86..f326997 100644 --- a/v2realbot/strategyblocks/indicators/indicators_hub.py +++ b/v2realbot/strategyblocks/indicators/indicators_hub.py @@ -63,7 +63,7 @@ def populate_all_indicators(data, state: StrategyState): else: pass - #toto je spíše interní ukládání tick_price a tick_volume - s tím pak mohou pracovat jak bar based tak tick based indikatory + #toto je spíše interní ukládání tick_price a tick_volume a tick_tradenct - s tím pak mohou pracovat jak bar based tak tick based indikatory #TODO do budoucna prejmenovat state.cbar_indicators na state.tick_indicators populate_cbar_tick_price_indicator(data, state) diff --git a/v2realbot/strategyblocks/inits/init_indicators.py b/v2realbot/strategyblocks/inits/init_indicators.py index 015cd37..64dd563 100644 --- a/v2realbot/strategyblocks/inits/init_indicators.py +++ b/v2realbot/strategyblocks/inits/init_indicators.py @@ -58,7 +58,7 @@ def initialize_dynamic_indicators(state): modelname = safe_get(indsettings["cp"], 'name', None) modelversion = safe_get(indsettings["cp"], 'version', "1") if modelname is not None: - state.vars.loaded_models[modelname] = ml.load_model(modelname, modelversion, MODEL_DIR) + state.vars.loaded_models[modelname] = ml.load_model(modelname, modelversion, None, MODEL_DIR) if state.vars.loaded_models[modelname] is not None: printanyway(f"model {modelname} loaded") else: diff --git a/v2realbot/utils/ilog.py b/v2realbot/utils/ilog.py index b06a63f..6f25c60 100644 --- a/v2realbot/utils/ilog.py +++ b/v2realbot/utils/ilog.py @@ -2,6 +2,7 @@ from v2realbot.config import DATA_DIR from v2realbot.utils.utils import json_serial from uuid import UUID, uuid4 import orjson +import json from datetime import datetime from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account from v2realbot.common.db import pool, insert_queue @@ -59,7 +60,7 @@ def insert_log_multiple_queue(runner_id:UUID, loglist: list): def get_log_window(runner_id: UUID, timestamp_from: float = 0, timestamp_to: float = 9682851459): conn = pool.get_connection() try: - conn.row_factory = lambda c, r: orjson.loads(r[0]) + conn.row_factory = lambda c, r: json.loads(r[0]) c = conn.cursor() res = c.execute(f"SELECT data FROM runner_logs WHERE runner_id='{str(runner_id)}' AND time >={timestamp_from} AND time <={timestamp_to} ORDER BY time") finally: