I want to implement a stepping logic for a multi-timeframe charting solution with bokeh library. I need to export a static HTML file with all the data, therefore the size of the HTML export matters.
In this solution multiple timeframes can be visualized at the same time, the charts of different timeframes are drawn on each other, BUT only until a given point in time (let’s call this trigger_date
).
time_tracker = ColumnDataSource(data=dict(trigger_date=[max_dt]))
I’m using ColumnDataSource
to store the data for each timeframe and I need to generate at least 2 buttons (backward step, forward step) for each timeframe.
To each button I need to pass all the data, which means every data source of all timeframes and every dataframe of all timeframes for implementing the step logic (to show the charts until a given point in time only and emit()
changes properly).
Important: The number of timeframes (aggregation units) is unknown in advance. It’s determined by the input only. Can not be listed in a constant list.
e.g.: 30s, 1m, 5m, 15m, 30m, 1h, 4h, 1d, ...
It’s simply impossible to define such thing:
step_buttons[timeframe]['prev'].js_on_click(CustomJS(
args = dict(
time_tracker = time_tracker,
direction = -1,
min_dt = min_dt,
max_dt = max_dt,
# ALL TIMEFRAMES CAN NOT BE LISTED HERE BECAUSE IT'S UNKNOWN
datasource_5m = ...,
datasource_15m = ...,
datasource_30m = ...,
datasource_1h = ...,
datasource_4h = ...,
?????????????? = ...,
),
code = JS_CODE_STEP_LOGIC,
))
Therefore I HAVE TO wrap all possible data sources for each timeframe into a more complex data structure which can be passed to the button as a single argument:
js_data[timeframe] = {
'data_source' : ColumnDataSource(dataframes[timeframe]),
'candle_data' : dataframes[timeframe].to_dict(orient="list"),
}
# or
# data_sources = {}
# candle_data = {}
# for timeframe in dataframes.keys():
# data_sources[timeframe] = ColumnDataSource(dataframes[timeframe])
# candle_data[timeframe] = dataframes[timeframe].to_dict(orient="list")
# ...
for tf in timeframes:
# I'VE A LOT OF THESE BUTTONS
# THE ARGUMENT LIST CAN NOT BE FIXED HERE
# I'VE TO PUT DATA SOURCES INTO A HIERARCHY WITH TIMEFRAME KEYS (candle_data_and_sources )
# AND IMPLEMENT A DYNAMIC LOGIC ON JS SIDE
# THE TIMEFRAMES ARE NOT KNOWN IN ADVANCE
# THIS IS WHAT DUPLICATES THE DATA
# AND INCREASES THE SIZE OF THE GENERATED HTML
step_buttons[tf]['prev'].js_on_click(CustomJS(
args = dict(
candle_data_and_sources = js_data, # need to use complex structure here
# or
# data_sources = data_sources
# candle_data = candle_data
time_tracker = time_tracker,
direction = -1,
timeframe = tf,
min_dt = min_dt,
max_dt = max_dt,
),
code = JS_CODE_STEP_LOGIC,
))
step_buttons[tf]['next'] = ...
BUT in this case the model will be obviously duplicated and will result a much larger file size then required. This will cause performance issues during visualization. The browser will fail to open this file.
QUESTIONS:
- How could I pass all available data only once to each button without duplicating the model here?
- Do I feel correctly that hardcoding all possible timeframes into the button’s arguments feels not the good way to implement this (and in my case it might be even impossible)..
Additional information 1:
I’ve tried to workaround this limitation with setting this complex data structure as a global variable on JS side, but I could not find a working solution.
See the details here: Initialize global variable in bokeh and use it in handler code?
Additional information 2:
The step logic being used is similar to this:
JS_CODE_STEP_LOGIC = """
const trigger_date = new Date(time_tracker.data['trigger_date'][0]);
let new_date = new Date(trigger_date);
new_date.setDate(new_date.getDate() + 1 * direction * get_tf_value(timeframe));
if (direction < 0){
new_date = new Date(Math.max(min_dt, new_date));
} else if (direction > 0){
new_date = new Date(Math.min(max_dt, new_date));
}
time_tracker.data['trigger_date'][0] = new_date.toISOString();
// I NEED TO DO THE FOLLOWING LOGIC FOR EACH TIMEFRAME
// THE NUMBER/VALUE OF TIMEFRAMES HERE ARE DYNAMIC
// THEREFORE THEY ARE ADDRESSING THE DATASOURCE IN THE HIERARCHY
for (const [timeframe, data] of Object.entries(candle_data_and_sources)) {
const filtererd_obejcts = {};
for (const [key, value] of Object.entries(data['candle_data'])) {
if(!filtererd_obejcts[key]){
filtererd_obejcts[key] = [];
}
}
for (let i = 0; i < data['candle_data'].trigger_dt.length; i++) {
if (new Date(data['candle_data'].trigger_dt[i]) <= new_date) {
for (const [key, value] of Object.entries(data['candle_data'])) {
filtererd_obejcts[key].push(value[i]);
}
}
}
data['data_source'].data = filtererd_obejcts;
data['data_source'].change.emit();
}
time_tracker.change.emit();
"""