cbar indicators + ml enhancements
This commit is contained in:
@ -97,9 +97,7 @@ def init(state: StrategyState):
|
|||||||
#pripadne udelat refresh kazdych x-iterací
|
#pripadne udelat refresh kazdych x-iterací
|
||||||
state.vars['sell_in_progress'] = False
|
state.vars['sell_in_progress'] = False
|
||||||
state.vars.mode = None
|
state.vars.mode = None
|
||||||
state.vars.last_tick_price = 0
|
|
||||||
state.vars.last_50_deltas = []
|
state.vars.last_50_deltas = []
|
||||||
state.vars.last_tick_volume = 0
|
|
||||||
state.vars.next_new = 0
|
state.vars.next_new = 0
|
||||||
state.vars.last_buy_index = None
|
state.vars.last_buy_index = None
|
||||||
state.vars.last_exit_index = None
|
state.vars.last_exit_index = None
|
||||||
@ -114,9 +112,15 @@ def init(state: StrategyState):
|
|||||||
state.vars.blockbuy = 0
|
state.vars.blockbuy = 0
|
||||||
#models
|
#models
|
||||||
state.vars.loaded_models = {}
|
state.vars.loaded_models = {}
|
||||||
|
|
||||||
|
#INITIALIZE CBAR INDICATORS - do vlastni funkce
|
||||||
#state.cbar_indicators['ivwap'] = []
|
#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_price'] = []
|
||||||
state.cbar_indicators['tick_volume'] = []
|
state.cbar_indicators['tick_volume'] = []
|
||||||
|
state.cbar_indicators['tick_trades'] = []
|
||||||
state.cbar_indicators['CRSI'] = []
|
state.cbar_indicators['CRSI'] = []
|
||||||
|
|
||||||
initialize_dynamic_indicators(state)
|
initialize_dynamic_indicators(state)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
#directory for generated images and basic reports
|
#directory for generated images and basic reports
|
||||||
MEDIA_DIRECTORY = Path(__file__).parent.parent.parent / "media"
|
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
|
#location of strat.log - it is used to fetch by gui
|
||||||
LOG_FILE = Path(__file__).parent.parent / "strat.log"
|
LOG_FILE = Path(__file__).parent.parent / "strat.log"
|
||||||
|
|||||||
1
v2realbot/controller/runner_details.py
Normal file
1
v2realbot/controller/runner_details.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
#PLACEHOLDER TO RUNNER_DETAILS SERVICES - refactored
|
||||||
@ -14,7 +14,7 @@ from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeSt
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from v2realbot.loader.trade_offline_streamer import Trade_Offline_Streamer
|
from v2realbot.loader.trade_offline_streamer import Trade_Offline_Streamer
|
||||||
from threading import Thread, current_thread, Event, enumerate
|
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
|
import importlib
|
||||||
from alpaca.trading.requests import GetCalendarRequest
|
from alpaca.trading.requests import GetCalendarRequest
|
||||||
from alpaca.trading.client import TradingClient
|
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
|
from v2realbot.interfaces.backtest_interface import BacktestInterface
|
||||||
import os
|
import os
|
||||||
from v2realbot.reporting.metricstoolsimage import generate_trading_report_image
|
from v2realbot.reporting.metricstoolsimage import generate_trading_report_image
|
||||||
|
import msgpack
|
||||||
|
import gzip
|
||||||
|
import os
|
||||||
#import gc
|
#import gc
|
||||||
#from pyinstrument import Profiler
|
#from pyinstrument import Profiler
|
||||||
#adding lock to ensure thread safety of TinyDB (in future will be migrated to proper db)
|
#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")
|
print(f"error delete")
|
||||||
return res, f"ERROR deleting {batch_id} : {val}"
|
return res, f"ERROR deleting {batch_id} : {val}"
|
||||||
|
|
||||||
|
|
||||||
#delete runner in archive and archive detail and runner logs
|
#delete runner in archive and archive detail and runner logs
|
||||||
#predelano do JEDNE TRANSAKCE
|
#predelano do JEDNE TRANSAKCE
|
||||||
def delete_archived_runners_byIDs(ids: list[UUID]):
|
def delete_archived_runners_byIDs(ids: list[UUID]):
|
||||||
@ -1297,6 +1301,32 @@ def delete_archive_header_byID(id: UUID):
|
|||||||
|
|
||||||
# region ARCHIVE DETAIL
|
# 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
|
#returns number of deleted elements
|
||||||
def delete_archive_detail_byID(id: UUID):
|
def delete_archive_detail_byID(id: UUID):
|
||||||
conn = pool.get_connection()
|
conn = pool.get_connection()
|
||||||
|
|||||||
0
v2realbot/endpoints/__init__.py
Normal file
0
v2realbot/endpoints/__init__.py
Normal file
0
v2realbot/endpoints/archived_runners.py
Normal file
0
v2realbot/endpoints/archived_runners.py
Normal file
0
v2realbot/endpoints/batches.py
Normal file
0
v2realbot/endpoints/batches.py
Normal file
0
v2realbot/endpoints/configs.py
Normal file
0
v2realbot/endpoints/configs.py
Normal file
0
v2realbot/endpoints/models.py
Normal file
0
v2realbot/endpoints/models.py
Normal file
0
v2realbot/endpoints/runners.py
Normal file
0
v2realbot/endpoints/runners.py
Normal file
0
v2realbot/endpoints/stratins.py
Normal file
0
v2realbot/endpoints/stratins.py
Normal file
0
v2realbot/endpoints/testlists.py
Normal file
0
v2realbot/endpoints/testlists.py
Normal file
@ -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
|
#Plot 8 Cumulative profit - bud 1 den nebo vice dni + pridame pod to vyvoj ceny
|
||||||
# Extract the closing prices and times
|
# 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 = 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
|
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
|
# # 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')
|
ax2.tick_params(axis='y', labelcolor='orange')
|
||||||
|
|
||||||
# Set the limits for the x-axis to cover the full range of 'times'
|
# 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')
|
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(max_profit_time, max_profit, color='green', label='Max Profit')
|
||||||
axs[1, 3].scatter(min_profit_time, min_profit, color='red', label='Min Profit')
|
axs[1, 3].scatter(min_profit_time, min_profit, color='red', label='Min Profit')
|
||||||
|
|||||||
@ -125,7 +125,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
require(["vs/editor/editor.main"], () => {
|
require(["vs/editor/editor.main"], () => {
|
||||||
model_editor_json = monaco.editor.create(document.getElementById('toml-editor-container'), {
|
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',
|
language: 'toml',
|
||||||
theme: 'tomlTheme-dark',
|
theme: 'tomlTheme-dark',
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
|
|||||||
@ -744,6 +744,7 @@ class StrategyState:
|
|||||||
self.ilog_save = ilog_save
|
self.ilog_save = ilog_save
|
||||||
self.sl_optimizer_short = optimsl.SLOptimizer(ptm.TradeDirection.SHORT)
|
self.sl_optimizer_short = optimsl.SLOptimizer(ptm.TradeDirection.SHORT)
|
||||||
self.sl_optimizer_long = optimsl.SLOptimizer(ptm.TradeDirection.LONG)
|
self.sl_optimizer_long = optimsl.SLOptimizer(ptm.TradeDirection.LONG)
|
||||||
|
self.cache = defaultdict(dict)
|
||||||
|
|
||||||
bars = {'high': [],
|
bars = {'high': [],
|
||||||
'low': [],
|
'low': [],
|
||||||
|
|||||||
@ -9,20 +9,24 @@ def populate_cbar_tick_price_indicator(data, state: StrategyState):
|
|||||||
try:
|
try:
|
||||||
tick_price = data['close']
|
tick_price = data['close']
|
||||||
tick_delta_volume = data['volume'] - state.vars.last_tick_volume
|
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_price[-1] = tick_price
|
||||||
state.cbar_indicators.tick_volume[-1] = tick_delta_volume
|
state.cbar_indicators.tick_volume[-1] = tick_delta_volume
|
||||||
|
state.cbar_indicators.tick_trades[-1] = tick_delta_trades
|
||||||
except:
|
except:
|
||||||
pass
|
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_price = tick_price
|
||||||
state.vars.last_tick_volume = data['volume']
|
state.vars.last_tick_volume = data['volume']
|
||||||
|
state.vars.last_tick_trades = data['trades']
|
||||||
|
|
||||||
if conf_bar == 1:
|
if conf_bar == 1:
|
||||||
#pri potvrzem CBARu nulujeme counter volume pro tick based indicator
|
#pri potvrzem CBARu nulujeme counter volume pro tick based indicator
|
||||||
state.vars.last_tick_volume = 0
|
state.vars.last_tick_volume = 0
|
||||||
|
state.vars.last_tick_trades = 0
|
||||||
state.vars.next_new = 1
|
state.vars.next_new = 1
|
||||||
|
|
||||||
#pro standardní CBARy
|
#pro standardní CBARy
|
||||||
@ -30,6 +34,7 @@ def populate_cbar_tick_price_indicator(data, state: StrategyState):
|
|||||||
if conf_bar == 1:
|
if conf_bar == 1:
|
||||||
#pri potvrzem CBARu nulujeme counter volume pro tick based indicator
|
#pri potvrzem CBARu nulujeme counter volume pro tick based indicator
|
||||||
state.vars.last_tick_volume = 0
|
state.vars.last_tick_volume = 0
|
||||||
|
state.vars.last_tick_trades = 0
|
||||||
state.vars.next_new = 1
|
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 = round2five(data['close'])
|
||||||
tick_price = data['close']
|
tick_price = data['close']
|
||||||
tick_delta_volume = data['volume'] - state.vars.last_tick_volume
|
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_price[-1] = tick_price
|
||||||
state.cbar_indicators.tick_volume[-1] = tick_delta_volume
|
state.cbar_indicators.tick_volume[-1] = tick_delta_volume
|
||||||
|
state.cbar_indicators.tick_trades[-1] = tick_delta_trades
|
||||||
except:
|
except:
|
||||||
pass
|
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_price = tick_price
|
||||||
state.vars.last_tick_volume = data['volume']
|
state.vars.last_tick_volume = data['volume']
|
||||||
|
state.vars.last_tick_trades = data['trades']
|
||||||
@ -23,49 +23,49 @@ import importlib
|
|||||||
#OBECNA trida pro statefull indicators - realized by class with the same name, deriving from parent IndicatorBase class
|
#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
|
#todo v initu inicializovat state.classed_indicators a ve stopu uklidit - resetovat
|
||||||
def classed(state, params, name):
|
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:
|
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:
|
if name not in state.classed_indicators:
|
||||||
classname = name
|
class_module = importlib.import_module("v2realbot.strategyblocks.indicators.custom.classes."+class_name)
|
||||||
class_module = importlib.import_module("v2realbot.strategyblocks.indicators.custom.classes."+classname)
|
indicatorClass = getattr(class_module, class_name)
|
||||||
indicatorClass = getattr(class_module, classname)
|
|
||||||
instance = indicatorClass(state=state, **init_params)
|
instance = indicatorClass(state=state, **init_params)
|
||||||
print("instance vytvorena", instance)
|
print("instance vytvorena", instance)
|
||||||
state.classed_indicators[name] = 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:
|
if len(source_dict) >0:
|
||||||
val = state.classed_indicators[name].next(**source_dict)
|
val = state.classed_indicators[name].next(**source_dict)
|
||||||
else:
|
else:
|
||||||
val = state.classed_indicators[name].next()
|
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
|
return 0, val
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -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}")
|
||||||
@ -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}")
|
||||||
@ -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
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase
|
from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase
|
||||||
|
|
||||||
#usecase - pocitat variance ticku
|
#TOTO predelat - jsou tu dva UC - na ticku (per position and time), uvnitr baru (per index baru)
|
||||||
# v ramci BARu - posilame sem index a resetujeme pri naslednem indxu
|
#rozdelit tyto a predelat
|
||||||
# do budoucna mo
|
|
||||||
class TickVariance(IndicatorBase):
|
class TickVariance(IndicatorBase):
|
||||||
def __init__(self, state, window_size=1):
|
def __init__(self, state, window_size=1):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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)
|
val = round((abs(float(source1_series[-1]) - float(source2_series[-1])))/float(source1_series[-1]),4)
|
||||||
elif mode == "rel":
|
elif mode == "rel":
|
||||||
val = round(float(source1_series[-1]) - float(source2_series[-1]),4)
|
val = round(float(source1_series[-1]) - float(source2_series[-1]),4)
|
||||||
elif mode == "reln":
|
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]),4)
|
val = round((float(source1_series[-1]) - float(source2_series[-1]))/(float(source1_series[-1])+float(source2_series[-1])),4)
|
||||||
elif mode == "pctabs":
|
elif mode == "pctabs":
|
||||||
val = pct_diff(num1=float(source1_series[-1]),num2=float(source2_series[-1]), absolute=True)
|
val = pct_diff(num1=float(source1_series[-1]),num2=float(source2_series[-1]), absolute=True)
|
||||||
elif mode == "pct":
|
elif mode == "pct":
|
||||||
|
|||||||
@ -7,6 +7,9 @@ from traceback import format_exc
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from collections import defaultdict
|
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):
|
def model(state, params, ind_name):
|
||||||
funcName = "model"
|
funcName = "model"
|
||||||
if params is None:
|
if params is None:
|
||||||
@ -28,10 +31,26 @@ def model(state, params, ind_name):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
mdl = state.vars.loaded_models[name]
|
mdl = state.vars.loaded_models[name]
|
||||||
if len(state.bars["close"]) < mdl.input_sequences:
|
|
||||||
return 0, 0
|
#Optimalizovano, aby se v kazde iteraci nemusel volat len
|
||||||
#return -2, f"too soon - not enough data for seq {seq=}"
|
if state.cache.get(name, {}).get("skip_init", False) is False:
|
||||||
value = mdl.predict(state.bars, state.indicators)
|
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
|
return 0, value
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
printanyway(str(e)+format_exc())
|
printanyway(str(e)+format_exc())
|
||||||
|
|||||||
@ -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.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.strategy.base import StrategyState
|
||||||
from v2realbot.indicators.indicators import ema, natr, roc
|
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 rich import print as printanyway
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -13,13 +13,15 @@ def slope(state, params, name):
|
|||||||
source = safe_get(params, "source", None)
|
source = safe_get(params, "source", None)
|
||||||
source_series = get_source_series(state, source)
|
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 = safe_get(params, "lookback", 5)
|
||||||
lookback_priceline = safe_get(params, "lookback_priceline", None) #bars|close
|
lookback_priceline = safe_get(params, "lookback_priceline", None) #bars|close
|
||||||
lookback_series = get_source_series(state, lookback_priceline)
|
lookback_series = get_source_series(state, lookback_priceline)
|
||||||
|
#ukládáme si do cache incializaci
|
||||||
|
cache = safe_get(params, "CACHE", None)
|
||||||
|
|
||||||
match lookback_type:
|
match lookback_unit:
|
||||||
case "positions":
|
case "position":
|
||||||
try:
|
try:
|
||||||
lookbackprice = lookback_series[-lookback-1]
|
lookbackprice = lookback_series[-lookback-1]
|
||||||
lookbacktime = state.bars.updated[-lookback-1]
|
lookbacktime = state.bars.updated[-lookback-1]
|
||||||
@ -27,18 +29,23 @@ def slope(state, params, name):
|
|||||||
max_delka = len(lookback_series)
|
max_delka = len(lookback_series)
|
||||||
lookbackprice =lookback_series[-max_delka]
|
lookbackprice =lookback_series[-max_delka]
|
||||||
lookbacktime = state.bars.updated[-max_delka]
|
lookbacktime = state.bars.updated[-max_delka]
|
||||||
case "seconds":
|
case "second":
|
||||||
#předpokládáme, že lookback_priceline je ve formě #bars|close
|
if state.cache.get(name, None) is None:
|
||||||
#abychom ziskali relevantní time
|
#předpokládáme, že lookback_priceline je ve formě #bars|close
|
||||||
split_index = lookback_priceline.find("|")
|
#abychom ziskali relevantní time
|
||||||
if split_index == -1:
|
split_index = lookback_priceline.find("|")
|
||||||
return -2, "for time it is required in format bars|close"
|
if split_index == -1:
|
||||||
dict_name = lookback_priceline[:split_index]
|
return -2, "for time it is required in format bars|close"
|
||||||
time_series = getattr(state, dict_name)["time"]
|
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)
|
lookback_idx = find_index_optimized(time_list=time_series, seconds=lookback)
|
||||||
lookbackprice = lookback_series[lookback_idx]
|
lookbackprice = lookback_series[lookback_idx]
|
||||||
lookbacktime = time_series[lookback_idx]
|
lookbacktime = time_series[lookback_idx]
|
||||||
|
|
||||||
|
|
||||||
#výpočet úhlu - a jeho normalizace
|
#výpočet úhlu - a jeho normalizace
|
||||||
currval = source_series[-1]
|
currval = source_series[-1]
|
||||||
slope = ((currval - lookbackprice)/abs(lookbackprice))*100
|
slope = ((currval - lookbackprice)/abs(lookbackprice))*100
|
||||||
@ -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)
|
state.ilog(lvl=1,e=f"INSIDE {name}:{funcName} {slope} {source=} {lookback=}", currval_source=currval, lookbackprice=lookbackprice, lookbacktime=lookbacktime, **params)
|
||||||
return 0, slope
|
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
|
|
||||||
@ -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.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.strategy.base import StrategyState
|
||||||
from v2realbot.indicators.indicators import ema, natr, roc
|
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 rich import print as printanyway
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -26,11 +26,17 @@ def target(state, params, name):
|
|||||||
source_series = get_source_series(state, source)
|
source_series = get_source_series(state, source)
|
||||||
#zatim podporovano jen bar - dokud nepridame o level vyse do "custom" save_to_past na urovni casu
|
#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_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)
|
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
|
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
|
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
|
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:
|
if window_length_value is None or source is None:
|
||||||
return -2, "window_length_value/source required"
|
return -2, "window_length_value/source required"
|
||||||
@ -42,16 +48,65 @@ def target(state, params, name):
|
|||||||
|
|
||||||
future_price = float(source_series[-1])
|
future_price = float(source_series[-1])
|
||||||
|
|
||||||
try:
|
#LOOKUP of current_price (price in the past)
|
||||||
current_price = float(source_series[-window_length_value])
|
if window_length_unit == "position":
|
||||||
except IndexError:
|
try:
|
||||||
return 0, val
|
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)
|
upper_move_threshold = add_pct(no_move_threshold, current_price)
|
||||||
lower_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
|
#no move
|
||||||
if lower_move_threshold <= future_price <= upper_move_threshold:
|
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
|
return 0, val
|
||||||
|
|
||||||
#calculates weight based on magnitude of change
|
#calculates weight based on magnitude of change
|
||||||
@ -64,13 +119,21 @@ def target(state, params, name):
|
|||||||
|
|
||||||
#price is bigger than threshold
|
#price is bigger than threshold
|
||||||
if upper_move_threshold < future_price:
|
if upper_move_threshold < future_price:
|
||||||
|
params["last_activated_down"] = None
|
||||||
magnitude_val = calculate_factor(current_price, future_price)
|
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
|
#price is bigger than threshold
|
||||||
if lower_move_threshold > future_price:
|
if lower_move_threshold > future_price:
|
||||||
|
params["last_activated_up"] = None
|
||||||
magnitude_val = calculate_factor(current_price, future_price)
|
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):
|
def pct_delta(base, second_number):
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from v2realbot.indicators.indicators import ema
|
|||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
import importlib
|
import importlib
|
||||||
import v2realbot.strategyblocks.indicators.custom as ci
|
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):
|
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
|
# 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 = 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():
|
def is_time_to_run():
|
||||||
# on_confirmed_only = true (def. False)
|
# 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_time"] = datetime.fromtimestamp(data["updated"]).astimezone(zoneNY)
|
||||||
state.vars.indicators[name]["last_run_index"] = data["index"]
|
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
|
# - volame custom funkci pro ziskani hodnoty indikatoru
|
||||||
# - tu ulozime jako novou hodnotu indikatoru a prepocteme MAcka pokud je pozadovane
|
# - tu ulozime jako novou hodnotu indikatoru a prepocteme MAcka pokud je pozadovane
|
||||||
# - pokud cas neni, nechavame puvodni, vcetna pripadneho MAcka
|
# - pokud cas neni, nechavame puvodni, vcetna pripadneho MAcka
|
||||||
@ -145,16 +159,19 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name):
|
|||||||
custom_function = eval(subtype)
|
custom_function = eval(subtype)
|
||||||
res_code, new_val = custom_function(state, custom_params, name)
|
res_code, new_val = custom_function(state, custom_params, name)
|
||||||
if res_code == 0:
|
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)
|
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
|
#prepocitame MA if required
|
||||||
if MA_length is not None:
|
if MA_length is not None:
|
||||||
src = indicators_dict[name][-MA_length:]
|
src = indicators_dict[name][-MA_length:]
|
||||||
MA_res = ema(src, MA_length)
|
MA_res = ema(src, MA_length)
|
||||||
MA_value = round(MA_res[-1],7)
|
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)
|
state.ilog(lvl=0,e=f"IND {name}MA {subtype} {MA_value}",save_to_past=save_to_past)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
else:
|
else:
|
||||||
err = f"IND ERROR {name} {subtype}Funkce {custom_function} vratila {res_code} {new_val}."
|
err = f"IND ERROR {name} {subtype}Funkce {custom_function} vratila {res_code} {new_val}."
|
||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
|
|||||||
@ -2,6 +2,28 @@ from v2realbot.utils.utils import isrising, isfalling,isfallingc, isrisingc, zon
|
|||||||
#from v2realbot.strategy.base import StrategyState
|
#from v2realbot.strategy.base import StrategyState
|
||||||
from traceback import format_exc
|
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
|
#ZATIM tyto zkopirovany SEM DO HELPERS
|
||||||
#podle toho jak se osvedci se zakl.indikatory to s state
|
#podle toho jak se osvedci se zakl.indikatory to s state
|
||||||
#zatim se mi to moc nezda
|
#zatim se mi to moc nezda
|
||||||
|
|||||||
@ -63,7 +63,7 @@ def populate_all_indicators(data, state: StrategyState):
|
|||||||
else:
|
else:
|
||||||
pass
|
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
|
#TODO do budoucna prejmenovat state.cbar_indicators na state.tick_indicators
|
||||||
populate_cbar_tick_price_indicator(data, state)
|
populate_cbar_tick_price_indicator(data, state)
|
||||||
|
|
||||||
|
|||||||
@ -58,7 +58,7 @@ def initialize_dynamic_indicators(state):
|
|||||||
modelname = safe_get(indsettings["cp"], 'name', None)
|
modelname = safe_get(indsettings["cp"], 'name', None)
|
||||||
modelversion = safe_get(indsettings["cp"], 'version', "1")
|
modelversion = safe_get(indsettings["cp"], 'version', "1")
|
||||||
if modelname is not None:
|
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:
|
if state.vars.loaded_models[modelname] is not None:
|
||||||
printanyway(f"model {modelname} loaded")
|
printanyway(f"model {modelname} loaded")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from v2realbot.config import DATA_DIR
|
|||||||
from v2realbot.utils.utils import json_serial
|
from v2realbot.utils.utils import json_serial
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
import orjson
|
import orjson
|
||||||
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account
|
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account
|
||||||
from v2realbot.common.db import pool, insert_queue
|
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):
|
def get_log_window(runner_id: UUID, timestamp_from: float = 0, timestamp_to: float = 9682851459):
|
||||||
conn = pool.get_connection()
|
conn = pool.get_connection()
|
||||||
try:
|
try:
|
||||||
conn.row_factory = lambda c, r: orjson.loads(r[0])
|
conn.row_factory = lambda c, r: json.loads(r[0])
|
||||||
c = conn.cursor()
|
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")
|
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:
|
finally:
|
||||||
|
|||||||
Reference in New Issue
Block a user