dynamic toolbutts on json and plugin report system
This commit is contained in:
@ -8,7 +8,6 @@ from pydantic import BaseModel
|
|||||||
from v2realbot.enums.enums import Mode, Account
|
from v2realbot.enums.enums import Mode, Account
|
||||||
from alpaca.data.enums import Exchange
|
from alpaca.data.enums import Exchange
|
||||||
|
|
||||||
|
|
||||||
#models for server side datatables
|
#models for server side datatables
|
||||||
# Model for individual column data
|
# Model for individual column data
|
||||||
class ColumnData(BaseModel):
|
class ColumnData(BaseModel):
|
||||||
@ -55,6 +54,7 @@ class DataTablesRequest(BaseModel):
|
|||||||
|
|
||||||
#obecny vstup pro analyzera (vstupem muze byt bud batch_id nebo seznam runneru)
|
#obecny vstup pro analyzera (vstupem muze byt bud batch_id nebo seznam runneru)
|
||||||
class AnalyzerInputs(BaseModel):
|
class AnalyzerInputs(BaseModel):
|
||||||
|
function: str
|
||||||
batch_id: Optional[str] = None
|
batch_id: Optional[str] = None
|
||||||
runner_ids: Optional[List[UUID]] = None
|
runner_ids: Optional[List[UUID]] = None
|
||||||
#additional parameter
|
#additional parameter
|
||||||
|
|||||||
@ -33,7 +33,8 @@ from time import sleep
|
|||||||
import v2realbot.reporting.metricstools as mt
|
import v2realbot.reporting.metricstools as mt
|
||||||
from v2realbot.reporting.metricstoolsimage import generate_trading_report_image
|
from v2realbot.reporting.metricstoolsimage import generate_trading_report_image
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from v2realbot.reporting.optimizecutoffs import find_optimal_cutoff
|
#from v2realbot.reporting.optimizecutoffs import find_optimal_cutoff
|
||||||
|
import v2realbot.reporting.analyzer as ci
|
||||||
#from async io import Queue, QueueEmpty
|
#from async io import Queue, QueueEmpty
|
||||||
#
|
#
|
||||||
# install()
|
# install()
|
||||||
@ -591,19 +592,37 @@ def _generate_report_image(runner_ids: list[UUID]):
|
|||||||
#TODO toto bude zaklad pro obecnou funkci, ktera bude volat ruzne analyzy
|
#TODO toto bude zaklad pro obecnou funkci, ktera bude volat ruzne analyzy
|
||||||
#vstupem bude obecny objekt, ktery ponese nazev analyzy + atributy
|
#vstupem bude obecny objekt, ktery ponese nazev analyzy + atributy
|
||||||
@app.post("/batches/optimizecutoff", dependencies=[Depends(api_key_auth)], responses={200: {"content": {"image/png": {}}}})
|
@app.post("/batches/optimizecutoff", dependencies=[Depends(api_key_auth)], responses={200: {"content": {"image/png": {}}}})
|
||||||
def _generate_analysis(analyzerInputs: AnalyzerInputs):
|
def _optimize_cutoff(analyzerInputs: AnalyzerInputs):
|
||||||
try:
|
try:
|
||||||
if len(analyzerInputs.runner_ids) == 0 and analyzerInputs.batch_id is None:
|
if len(analyzerInputs.runner_ids) == 0 and analyzerInputs.batch_id is None:
|
||||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: batch_id or runner_ids required")
|
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: batch_id or runner_ids required")
|
||||||
|
|
||||||
#bude predelano na obecny analyzator s obecnym rozhrannim
|
#bude predelano na obecny analyzator s obecnym rozhrannim
|
||||||
res, stream = find_optimal_cutoff(runner_ids=analyzerInputs.runner_ids, batch_id=analyzerInputs.batch_id, stream=True, **analyzerInputs.params)
|
res, stream = ci.find_optimal_cutoff.find_optimal_cutoff(runner_ids=analyzerInputs.runner_ids, batch_id=analyzerInputs.batch_id, stream=True, **analyzerInputs.params)
|
||||||
if res == 0: return StreamingResponse(stream, media_type="image/png",headers={"Content-Disposition": "attachment; filename=optimizedcutoff.png"})
|
if res == 0: return StreamingResponse(stream, media_type="image/png",headers={"Content-Disposition": "attachment; filename=optimizedcutoff.png"})
|
||||||
elif res < 0:
|
elif res < 0:
|
||||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {res}:{id}")
|
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {res}:{id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {str(e)}" + format_exc())
|
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {str(e)}" + format_exc())
|
||||||
|
|
||||||
|
#obecna funkce pro analyzy
|
||||||
|
#vstupem bude obecny objekt, ktery ponese nazev analyzy + atributy
|
||||||
|
@app.post("/batches/analytics", dependencies=[Depends(api_key_auth)], responses={200: {"content": {"image/png": {}}}})
|
||||||
|
def _generate_analysis(analyzerInputs: AnalyzerInputs):
|
||||||
|
try:
|
||||||
|
if (analyzerInputs.runner_ids is None or len(analyzerInputs.runner_ids) == 0) and analyzerInputs.batch_id is None:
|
||||||
|
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: batch_id or runner_ids required")
|
||||||
|
|
||||||
|
funct = "ci."+analyzerInputs.function+"."+analyzerInputs.function
|
||||||
|
custom_function = eval(funct)
|
||||||
|
stream = None
|
||||||
|
res, stream = custom_function(runner_ids=analyzerInputs.runner_ids, batch_id=analyzerInputs.batch_id, stream=True, **analyzerInputs.params)
|
||||||
|
|
||||||
|
if res == 0: return StreamingResponse(stream, media_type="image/png")
|
||||||
|
elif res < 0:
|
||||||
|
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {res}:{id}")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {str(e)}" + format_exc())
|
||||||
|
|
||||||
#TestList APIS - do budoucna predelat SQL do separatnich funkci
|
#TestList APIS - do budoucna predelat SQL do separatnich funkci
|
||||||
@app.post('/testlists/', dependencies=[Depends(api_key_auth)])
|
@app.post('/testlists/', dependencies=[Depends(api_key_auth)])
|
||||||
|
|||||||
8
v2realbot/reporting/analyzer/__init__.py
Normal file
8
v2realbot/reporting/analyzer/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
for filename in os.listdir("v2realbot/reporting/analyzer"):
|
||||||
|
if filename.endswith(".py") and filename != "__init__.py":
|
||||||
|
# __import__(filename[:-3])
|
||||||
|
__import__(f"v2realbot.reporting.analyzer.{filename[:-3]}")
|
||||||
|
#importlib.import_module()
|
||||||
|
|
||||||
203
v2realbot/reporting/analyzer/example_plugin.py
Normal file
203
v2realbot/reporting/analyzer/example_plugin.py
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import matplotlib
|
||||||
|
import matplotlib.dates as mdates
|
||||||
|
#matplotlib.use('Agg') # Set the Matplotlib backend to 'Agg'
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
|
from enum import Enum
|
||||||
|
import numpy as np
|
||||||
|
import v2realbot.controller.services as cs
|
||||||
|
from rich import print
|
||||||
|
from v2realbot.common.model import AnalyzerInputs
|
||||||
|
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||||
|
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||||
|
from pathlib import Path
|
||||||
|
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||||
|
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.utils.historicals import get_historical_bars
|
||||||
|
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
|
||||||
|
from collections import defaultdict
|
||||||
|
from scipy.stats import zscore
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.reporting.load_trades import load_trades
|
||||||
|
from traceback import format_exc
|
||||||
|
# Assuming Trade, TradeStatus, TradeDirection, TradeStoplossType classes are defined elsewhere
|
||||||
|
|
||||||
|
|
||||||
|
def example_plugin(runner_ids: list = None, batch_id: str = None, stream: bool = False, rem_outliers:bool = False, file: str = "optimalcutoff.png",steps:int = 50):
|
||||||
|
try:
|
||||||
|
res, trades, days = load_trades(runner_ids, batch_id)
|
||||||
|
if res < 0:
|
||||||
|
return (res, trades)
|
||||||
|
|
||||||
|
cnt_max = days
|
||||||
|
#in trades is list of Trades
|
||||||
|
|
||||||
|
#print(trades)
|
||||||
|
|
||||||
|
##THIS IS how you can fetch historical data for given period and for given TimeFrame (if needed in future)
|
||||||
|
# symbol = sada.symbol
|
||||||
|
# #hour bars for backtested period
|
||||||
|
# print(start_date,end_date)
|
||||||
|
# bars= get_historical_bars(symbol, start_date, end_date, TimeFrame.Hour)
|
||||||
|
# print("bars for given period",bars)
|
||||||
|
# """Bars a dictionary with the following keys:
|
||||||
|
# * high: A list of high prices
|
||||||
|
# * low: A list of low prices
|
||||||
|
# * volume: A list of volumes
|
||||||
|
# * close: A list of close prices
|
||||||
|
# * hlcc4: A list of HLCC4 indicators
|
||||||
|
# * open: A list of open prices
|
||||||
|
# * time: A list of times in UTC (ISO 8601 format)
|
||||||
|
# * trades: A list of number of trades
|
||||||
|
# * resolution: A list of resolutions (all set to 'D')
|
||||||
|
# * confirmed: A list of booleans (all set to True)
|
||||||
|
# * vwap: A list of VWAP indicator
|
||||||
|
# * updated: A list of booleans (all set to True)
|
||||||
|
# * index: A list of integers (from 0 to the length of the list of daily bars)
|
||||||
|
# """
|
||||||
|
|
||||||
|
# Filter to only use trades with status 'CLOSED'
|
||||||
|
closed_trades = [trade for trade in trades if trade.status == TradeStatus.CLOSED]
|
||||||
|
|
||||||
|
#print(closed_trades)
|
||||||
|
|
||||||
|
if len(closed_trades) == 0:
|
||||||
|
return -1, "image generation no closed trades"
|
||||||
|
|
||||||
|
# # Group trades by date and calculate daily profits
|
||||||
|
# trades_by_day = defaultdict(list)
|
||||||
|
# for trade in trades:
|
||||||
|
# if trade.status == TradeStatus.CLOSED and trade.exit_time:
|
||||||
|
# trade_day = trade.exit_time.date()
|
||||||
|
# trades_by_day[trade_day].append(trade)
|
||||||
|
|
||||||
|
# Precompute daily cumulative profits
|
||||||
|
daily_cumulative_profits = defaultdict(list)
|
||||||
|
for trade in trades:
|
||||||
|
if trade.status == TradeStatus.CLOSED and trade.exit_time:
|
||||||
|
day = trade.exit_time.date()
|
||||||
|
daily_cumulative_profits[day].append(trade.profit)
|
||||||
|
|
||||||
|
for day in daily_cumulative_profits:
|
||||||
|
daily_cumulative_profits[day] = np.cumsum(daily_cumulative_profits[day])
|
||||||
|
|
||||||
|
|
||||||
|
if rem_outliers:
|
||||||
|
# Remove outliers based on z-scores
|
||||||
|
def remove_outliers(cumulative_profits):
|
||||||
|
all_profits = [profit[-1] for profit in cumulative_profits.values() if len(profit) > 0]
|
||||||
|
z_scores = zscore(all_profits)
|
||||||
|
print(z_scores)
|
||||||
|
filtered_profits = {}
|
||||||
|
for day, profits in cumulative_profits.items():
|
||||||
|
if len(profits) > 0:
|
||||||
|
day_z_score = z_scores[list(cumulative_profits.keys()).index(day)]
|
||||||
|
if abs(day_z_score) < 3: # Adjust threshold as needed
|
||||||
|
filtered_profits[day] = profits
|
||||||
|
return filtered_profits
|
||||||
|
|
||||||
|
daily_cumulative_profits = remove_outliers(daily_cumulative_profits)
|
||||||
|
|
||||||
|
# OPT2 Calculate profit_range and loss_range based on all cumulative profits
|
||||||
|
all_cumulative_profits = np.concatenate([profits for profits in daily_cumulative_profits.values()])
|
||||||
|
max_cumulative_profit = np.max(all_cumulative_profits)
|
||||||
|
min_cumulative_profit = np.min(all_cumulative_profits)
|
||||||
|
profit_range = (0, max_cumulative_profit) if max_cumulative_profit > 0 else (0, 0)
|
||||||
|
loss_range = (min_cumulative_profit, 0) if min_cumulative_profit < 0 else (0, 0)
|
||||||
|
|
||||||
|
print("Calculated ranges", profit_range, loss_range)
|
||||||
|
|
||||||
|
num_points = steps # Adjust for speed vs accuracy
|
||||||
|
profit_cutoffs = np.linspace(*profit_range, num_points)
|
||||||
|
loss_cutoffs = np.linspace(*loss_range, num_points)
|
||||||
|
|
||||||
|
# OPT 3Statically define ranges for loss and profit cutoffs
|
||||||
|
# profit_range = (0, 1000) # Adjust based on your data
|
||||||
|
# loss_range = (-1000, 0)
|
||||||
|
# num_points = 20 # Adjust for speed vs accuracy
|
||||||
|
|
||||||
|
profit_cutoffs = np.linspace(*profit_range, num_points)
|
||||||
|
loss_cutoffs = np.linspace(*loss_range, num_points)
|
||||||
|
|
||||||
|
total_profits_matrix = np.zeros((len(profit_cutoffs), len(loss_cutoffs)))
|
||||||
|
|
||||||
|
for i, profit_cutoff in enumerate(profit_cutoffs):
|
||||||
|
for j, loss_cutoff in enumerate(loss_cutoffs):
|
||||||
|
total_profit = 0
|
||||||
|
for daily_profit in daily_cumulative_profits.values():
|
||||||
|
cutoff_index = np.where((daily_profit >= profit_cutoff) | (daily_profit <= loss_cutoff))[0]
|
||||||
|
if cutoff_index.size > 0:
|
||||||
|
total_profit += daily_profit[cutoff_index[0]]
|
||||||
|
else:
|
||||||
|
total_profit += daily_profit[-1] if daily_profit.size > 0 else 0
|
||||||
|
total_profits_matrix[i, j] = total_profit
|
||||||
|
|
||||||
|
# Find the optimal combination
|
||||||
|
optimal_idx = np.unravel_index(total_profits_matrix.argmax(), total_profits_matrix.shape)
|
||||||
|
optimal_profit_cutoff = profit_cutoffs[optimal_idx[0]]
|
||||||
|
optimal_loss_cutoff = loss_cutoffs[optimal_idx[1]]
|
||||||
|
max_profit = total_profits_matrix[optimal_idx]
|
||||||
|
|
||||||
|
# Plotting
|
||||||
|
# Setting up dark mode for the plots
|
||||||
|
plt.style.use('dark_background')
|
||||||
|
|
||||||
|
# Optionally, you can further customize colors, labels, and axes
|
||||||
|
params = {
|
||||||
|
'axes.titlesize': 9,
|
||||||
|
'axes.labelsize': 8,
|
||||||
|
'xtick.labelsize': 9,
|
||||||
|
'ytick.labelsize': 9,
|
||||||
|
'axes.labelcolor': '#a9a9a9', #a1a3aa',
|
||||||
|
'axes.facecolor': '#121722', #'#0e0e0e', #202020', # Dark background for plot area
|
||||||
|
'axes.grid': False, # Turn off the grid globally
|
||||||
|
'grid.color': 'gray', # If the grid is on, set grid line color
|
||||||
|
'grid.linestyle': '--', # Grid line style
|
||||||
|
'grid.linewidth': 1,
|
||||||
|
'xtick.color': '#a9a9a9',
|
||||||
|
'ytick.color': '#a9a9a9',
|
||||||
|
'axes.edgecolor': '#a9a9a9'
|
||||||
|
}
|
||||||
|
plt.rcParams.update(params)
|
||||||
|
plt.figure(figsize=(10, 8))
|
||||||
|
sns.heatmap(total_profits_matrix, xticklabels=np.rint(loss_cutoffs).astype(int), yticklabels=np.rint(profit_cutoffs).astype(int), cmap="viridis")
|
||||||
|
plt.xticks(rotation=90) # Rotate x-axis labels to be vertical
|
||||||
|
plt.yticks(rotation=0) # Keep y-axis labels horizontal
|
||||||
|
plt.gca().invert_yaxis()
|
||||||
|
plt.gca().invert_xaxis()
|
||||||
|
plt.suptitle(f"Total Profit for Combinations of Profit/Loss Cutoffs ({cnt_max})", fontsize=16)
|
||||||
|
plt.title(f"Optimal Profit Cutoff: {optimal_profit_cutoff:.2f}, Optimal Loss Cutoff: {optimal_loss_cutoff:.2f}, Max Profit: {max_profit:.2f}", fontsize=10)
|
||||||
|
plt.xlabel("Loss Cutoff")
|
||||||
|
plt.ylabel("Profit Cutoff")
|
||||||
|
|
||||||
|
if stream is False:
|
||||||
|
plt.savefig(file)
|
||||||
|
plt.close()
|
||||||
|
print(f"Optimal Profit Cutoff(rem_outliers:{rem_outliers}): {optimal_profit_cutoff}, Optimal Loss Cutoff: {optimal_loss_cutoff}, Max Profit: {max_profit}")
|
||||||
|
return 0, None
|
||||||
|
else:
|
||||||
|
# Return the image as a BytesIO stream
|
||||||
|
img_stream = BytesIO()
|
||||||
|
plt.savefig(img_stream, format='png')
|
||||||
|
plt.close()
|
||||||
|
img_stream.seek(0) # Rewind the stream to the beginning
|
||||||
|
return 0, img_stream
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Detailed error reporting
|
||||||
|
return (-1, str(e) + format_exc())
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
# trades = [list of Trade objects]
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# id_list = ["e8938b2e-8462-441a-8a82-d823c6a025cb"]
|
||||||
|
# generate_trading_report_image(runner_ids=id_list)
|
||||||
|
batch_id = "73ad1866"
|
||||||
|
res, val = example_plugin(batch_id=batch_id, file="optimal_cutoff_vectorized.png",steps=20)
|
||||||
|
#res, val = find_optimal_cutoff(batch_id=batch_id, rem_outliers=True, file="optimal_cutoff_vectorized_nooutliers.png")
|
||||||
|
|
||||||
|
print(res,val)
|
||||||
99
v2realbot/reporting/analyzer/ls_profit_distribution.py
Normal file
99
v2realbot/reporting/analyzer/ls_profit_distribution.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import matplotlib
|
||||||
|
import matplotlib.dates as mdates
|
||||||
|
#matplotlib.use('Agg') # Set the Matplotlib backend to 'Agg'
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
|
from enum import Enum
|
||||||
|
import numpy as np
|
||||||
|
import v2realbot.controller.services as cs
|
||||||
|
from rich import print
|
||||||
|
from v2realbot.common.model import AnalyzerInputs
|
||||||
|
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||||
|
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||||
|
from pathlib import Path
|
||||||
|
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||||
|
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.utils.historicals import get_historical_bars
|
||||||
|
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
|
||||||
|
from collections import defaultdict
|
||||||
|
from scipy.stats import zscore
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.reporting.load_trades import load_trades
|
||||||
|
from typing import Tuple, Optional, List
|
||||||
|
from traceback import format_exc
|
||||||
|
# Assuming Trade, TradeStatus, TradeDirection, TradeStoplossType classes are defined elsewhere
|
||||||
|
|
||||||
|
def ls_profit_distribution(runner_ids: List = None, batch_id: str = None, stream: bool = False) -> Tuple[int, Optional[BytesIO]]:
|
||||||
|
try:
|
||||||
|
# Load trades
|
||||||
|
result, trades, days_cnt = load_trades(runner_ids, batch_id)
|
||||||
|
|
||||||
|
# Proceed only if trades are successfully loaded
|
||||||
|
if result == 0:
|
||||||
|
# Filter trades based on direction and calculate profit
|
||||||
|
long_trades = [trade for trade in trades if trade.direction == TradeDirection.LONG]
|
||||||
|
short_trades = [trade for trade in trades if trade.direction == TradeDirection.SHORT]
|
||||||
|
|
||||||
|
long_profits = [trade.profit for trade in long_trades]
|
||||||
|
short_profits = [trade.profit for trade in short_trades]
|
||||||
|
|
||||||
|
# Setting up dark mode for visualization with custom parameters
|
||||||
|
plt.style.use('dark_background')
|
||||||
|
custom_params = {
|
||||||
|
'axes.titlesize': 9,
|
||||||
|
'axes.labelsize': 8,
|
||||||
|
'xtick.labelsize': 9,
|
||||||
|
'ytick.labelsize': 9,
|
||||||
|
'axes.labelcolor': '#a9a9a9',
|
||||||
|
'axes.facecolor': '#121722',
|
||||||
|
'axes.grid': False,
|
||||||
|
'grid.color': 'gray',
|
||||||
|
'grid.linestyle': '--',
|
||||||
|
'grid.linewidth': 1,
|
||||||
|
'xtick.color': '#a9a9a9',
|
||||||
|
'ytick.color': '#a9a9a9',
|
||||||
|
'axes.edgecolor': '#a9a9a9'
|
||||||
|
}
|
||||||
|
plt.rcParams.update(custom_params)
|
||||||
|
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
sns.histplot(long_profits, color='blue', label='Long Trades', kde=True)
|
||||||
|
sns.histplot(short_profits, color='red', label='Short Trades', kde=True)
|
||||||
|
plt.xlabel('Profit')
|
||||||
|
plt.ylabel('Number of Trades')
|
||||||
|
plt.title('Profit Distribution by Trade Direction')
|
||||||
|
plt.legend()
|
||||||
|
|
||||||
|
# Handling the output
|
||||||
|
if stream:
|
||||||
|
img_stream = BytesIO()
|
||||||
|
plt.savefig(img_stream, format='png')
|
||||||
|
plt.close()
|
||||||
|
img_stream.seek(0)
|
||||||
|
return (0, img_stream)
|
||||||
|
else:
|
||||||
|
plt.savefig('profit_distribution.png')
|
||||||
|
plt.close()
|
||||||
|
return (0, None)
|
||||||
|
else:
|
||||||
|
return (-1, None) # Error handling in case of unsuccessful trade loading
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Detailed error reporting
|
||||||
|
return (-1, str(e) + format_exc())
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
# trades = [list of Trade objects]
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# id_list = ["e8938b2e-8462-441a-8a82-d823c6a025cb"]
|
||||||
|
# generate_trading_report_image(runner_ids=id_list)
|
||||||
|
batch_id = "73ad1866"
|
||||||
|
res, val = ls_profit_distribution(batch_id=batch_id)
|
||||||
|
#res, val = find_optimal_cutoff(batch_id=batch_id, rem_outliers=True, file="optimal_cutoff_vectorized_nooutliers.png")
|
||||||
|
|
||||||
|
print(res,val)
|
||||||
82
v2realbot/reporting/analyzer/profit_distribution_by_month.py
Normal file
82
v2realbot/reporting/analyzer/profit_distribution_by_month.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import matplotlib
|
||||||
|
import matplotlib.dates as mdates
|
||||||
|
#matplotlib.use('Agg') # Set the Matplotlib backend to 'Agg'
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
|
from enum import Enum
|
||||||
|
import numpy as np
|
||||||
|
import v2realbot.controller.services as cs
|
||||||
|
from rich import print
|
||||||
|
from v2realbot.common.model import AnalyzerInputs
|
||||||
|
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||||
|
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||||
|
from pathlib import Path
|
||||||
|
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||||
|
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.utils.historicals import get_historical_bars
|
||||||
|
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
|
||||||
|
from collections import defaultdict
|
||||||
|
from scipy.stats import zscore
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.reporting.load_trades import load_trades
|
||||||
|
from typing import Tuple, Optional, List
|
||||||
|
from traceback import format_exc
|
||||||
|
# Assuming Trade, TradeStatus, TradeDirection, TradeStoplossType classes are defined elsewhere
|
||||||
|
|
||||||
|
def profit_distribution_by_month(runner_ids: List = None, batch_id: str = None, stream: bool = False) -> Tuple[int, BytesIO or None]:
|
||||||
|
try:
|
||||||
|
# Load trades
|
||||||
|
res, trades, days_cnt = load_trades(runner_ids, batch_id)
|
||||||
|
if res != 0:
|
||||||
|
raise Exception("Error in loading trades")
|
||||||
|
|
||||||
|
# Filter trades by status and create DataFrame
|
||||||
|
df_trades = pd.DataFrame([t.dict() for t in trades if t.status == 'closed'])
|
||||||
|
|
||||||
|
# Extract month and year from trade exit time
|
||||||
|
df_trades['month'] = df_trades['exit_time'].apply(lambda x: x.strftime('%Y-%m') if x is not None else None)
|
||||||
|
|
||||||
|
# Group by direction and month, and sum the profits
|
||||||
|
grouped = df_trades.groupby(['direction', 'month']).profit.sum().unstack(fill_value=0)
|
||||||
|
|
||||||
|
# Visualization
|
||||||
|
plt.style.use('dark_background')
|
||||||
|
fig, ax = plt.subplots(figsize=(10, 6))
|
||||||
|
|
||||||
|
# Plotting
|
||||||
|
grouped.T.plot(kind='bar', ax=ax)
|
||||||
|
|
||||||
|
# Styling
|
||||||
|
ax.set_title('Profit Distribution by Month: Long vs Short')
|
||||||
|
ax.set_xlabel('Month')
|
||||||
|
ax.set_ylabel('Total Profit')
|
||||||
|
ax.legend(title='Trade Direction')
|
||||||
|
|
||||||
|
# Adding footer
|
||||||
|
plt.figtext(0.99, 0.01, f'Days Count: {days_cnt}', horizontalalignment='right')
|
||||||
|
|
||||||
|
# Save or stream
|
||||||
|
if stream:
|
||||||
|
img = BytesIO()
|
||||||
|
plt.savefig(img, format='png')
|
||||||
|
plt.close()
|
||||||
|
img.seek(0)
|
||||||
|
return (0, img)
|
||||||
|
else:
|
||||||
|
plt.savefig('profit_distribution_by_month.png')
|
||||||
|
plt.close()
|
||||||
|
return (0, None)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Detailed error reporting
|
||||||
|
return (-1, str(e) + format_exc())
|
||||||
|
|
||||||
|
# Local debugging
|
||||||
|
if __name__ == '__main__':
|
||||||
|
batch_id = "73ad1866"
|
||||||
|
res, val = profit_distribution_by_month(batch_id=batch_id)
|
||||||
|
print(res, val)
|
||||||
106
v2realbot/reporting/analyzer/profit_sum_by_hour.py
Normal file
106
v2realbot/reporting/analyzer/profit_sum_by_hour.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import matplotlib
|
||||||
|
import matplotlib.dates as mdates
|
||||||
|
matplotlib.use('Agg') # Set the Matplotlib backend to 'Agg'
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
|
from enum import Enum
|
||||||
|
import numpy as np
|
||||||
|
import v2realbot.controller.services as cs
|
||||||
|
from rich import print
|
||||||
|
from v2realbot.common.model import AnalyzerInputs
|
||||||
|
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||||
|
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||||
|
from pathlib import Path
|
||||||
|
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||||
|
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.utils.historicals import get_historical_bars
|
||||||
|
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
|
||||||
|
from collections import defaultdict
|
||||||
|
from scipy.stats import zscore
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.reporting.load_trades import load_trades
|
||||||
|
from typing import Tuple, Optional, List
|
||||||
|
from traceback import format_exc
|
||||||
|
# Assuming Trade, TradeStatus, TradeDirection, TradeStoplossType classes are defined elsewhere
|
||||||
|
def profit_sum_by_hour(runner_ids: list = None, batch_id: str = None, stream: bool = False, group_by: str = 'entry_time'):
|
||||||
|
try:
|
||||||
|
# Load trades
|
||||||
|
res, trades, days_cnt = load_trades(runner_ids, batch_id)
|
||||||
|
if res != 0:
|
||||||
|
raise Exception("Error in loading trades")
|
||||||
|
|
||||||
|
# Filter closed trades
|
||||||
|
closed_trades = [trade for trade in trades if trade.status == 'closed']
|
||||||
|
total_closed_trades = len(closed_trades)
|
||||||
|
|
||||||
|
# Extract hour and profit/loss based on group_by parameter
|
||||||
|
hourly_profit_loss = {}
|
||||||
|
hourly_trade_count = {}
|
||||||
|
for trade in closed_trades:
|
||||||
|
# Determine the time attribute to group by
|
||||||
|
time_attribute = getattr(trade, group_by) if group_by in ['entry_time', 'exit_time'] else trade.entry_time
|
||||||
|
if time_attribute:
|
||||||
|
hour = time_attribute.hour
|
||||||
|
hourly_profit_loss.setdefault(hour, []).append(trade.profit)
|
||||||
|
hourly_trade_count[hour] = hourly_trade_count.get(hour, 0) + 1
|
||||||
|
|
||||||
|
# Aggregate profits and losses by hour
|
||||||
|
hourly_aggregated = {hour: sum(profits) for hour, profits in hourly_profit_loss.items()}
|
||||||
|
|
||||||
|
# Visualization
|
||||||
|
hours = list(hourly_aggregated.keys())
|
||||||
|
profits = list(hourly_aggregated.values())
|
||||||
|
trade_counts = [hourly_trade_count.get(hour, 0) for hour in hours]
|
||||||
|
|
||||||
|
plt.style.use('dark_background')
|
||||||
|
colors = ['blue' if profit >= 0 else 'orange' for profit in profits]
|
||||||
|
bars = plt.bar(hours, profits, color=colors)
|
||||||
|
|
||||||
|
# Make the grid subtler
|
||||||
|
plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.5)
|
||||||
|
|
||||||
|
plt.xlabel('Hour of Day')
|
||||||
|
plt.ylabel('Profit/Loss')
|
||||||
|
plt.title(f'Distribution of Profit/Loss Sum by Hour ({group_by.replace("_", " ").title()})')
|
||||||
|
|
||||||
|
# Add trade count and percentage inside the bars
|
||||||
|
for bar, count in zip(bars, trade_counts):
|
||||||
|
height = bar.get_height()
|
||||||
|
percent = (count / total_closed_trades) * 100
|
||||||
|
# Position the text inside the bars
|
||||||
|
position = height - 20 if height > 0 else height + 20
|
||||||
|
plt.text(bar.get_x() + bar.get_width() / 2., position,
|
||||||
|
f'{count} Trades\n({percent:.1f}%)', ha='center', va='center', color='white', fontsize=9)
|
||||||
|
|
||||||
|
# Adjust footer position and remove large gap
|
||||||
|
footer_text = f'Days Count: {days_cnt} | Parameters: {{"runner_ids": {len(runner_ids) if runner_ids is not None else None}, "batch_id": {batch_id}, "stream": {stream}, "group_by": "{group_by}"}}'
|
||||||
|
plt.gcf().subplots_adjust(bottom=0.2)
|
||||||
|
plt.figtext(0.5, 0.02, footer_text, ha="center", fontsize=8, color='gray', bbox=dict(facecolor='black', edgecolor='none', pad=3.0))
|
||||||
|
|
||||||
|
# Output
|
||||||
|
if stream:
|
||||||
|
img = BytesIO()
|
||||||
|
plt.savefig(img, format='png', bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
img.seek(0)
|
||||||
|
return (0, img)
|
||||||
|
else:
|
||||||
|
plt.savefig('profit_loss_by_hour.png', bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
return (0, None)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Detailed error reporting
|
||||||
|
plt.close()
|
||||||
|
return (-1, str(e))
|
||||||
|
|
||||||
|
# Local debugging
|
||||||
|
if __name__ == '__main__':
|
||||||
|
batch_id = "9e990e4b"
|
||||||
|
# Example usage with group_by parameter
|
||||||
|
res, val = profit_sum_by_hour(batch_id=batch_id, group_by='exit_time')
|
||||||
|
print(res, val)
|
||||||
70
v2realbot/reporting/load_trades.py
Normal file
70
v2realbot/reporting/load_trades.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import matplotlib
|
||||||
|
import matplotlib.dates as mdates
|
||||||
|
#matplotlib.use('Agg') # Set the Matplotlib backend to 'Agg'
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
|
from enum import Enum
|
||||||
|
import numpy as np
|
||||||
|
import v2realbot.controller.services as cs
|
||||||
|
from rich import print
|
||||||
|
from v2realbot.common.model import AnalyzerInputs
|
||||||
|
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||||
|
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||||
|
from pathlib import Path
|
||||||
|
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
|
||||||
|
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
|
||||||
|
from io import BytesIO
|
||||||
|
from v2realbot.utils.historicals import get_historical_bars
|
||||||
|
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
|
||||||
|
from collections import defaultdict
|
||||||
|
from scipy.stats import zscore
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import Tuple, Optional, List
|
||||||
|
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||||
|
|
||||||
|
def load_trades(runner_ids: List = None, batch_id: str = None) -> Tuple[int, List[Trade], int]:
|
||||||
|
if runner_ids is None and batch_id is None:
|
||||||
|
return -2, f"runner_id or batch_id must be present", 0
|
||||||
|
|
||||||
|
if batch_id is not None:
|
||||||
|
res, runner_ids =cs.get_archived_runnerslist_byBatchID(batch_id)
|
||||||
|
|
||||||
|
if res != 0:
|
||||||
|
print(f"no batch {batch_id} found")
|
||||||
|
return -1, f"no batch {batch_id} found", 0
|
||||||
|
|
||||||
|
#DATA PREPARATION
|
||||||
|
trades = []
|
||||||
|
cnt_max = len(runner_ids)
|
||||||
|
cnt = 0
|
||||||
|
#zatim zjistujeme start a end z min a max dni - jelikoz muze byt i seznam runner_ids a nejenom batch
|
||||||
|
end_date = None
|
||||||
|
start_date = None
|
||||||
|
for id in runner_ids:
|
||||||
|
cnt += 1
|
||||||
|
#get runner
|
||||||
|
res, sada =cs.get_archived_runner_header_byID(id)
|
||||||
|
if res != 0:
|
||||||
|
print(f"no runner {id} found")
|
||||||
|
return -1, f"no runner {id} found", 0
|
||||||
|
|
||||||
|
#print("archrunner")
|
||||||
|
#print(sada)
|
||||||
|
|
||||||
|
if cnt == 1:
|
||||||
|
start_date = sada.bt_from if sada.mode in [Mode.BT,Mode.PREP] else sada.started
|
||||||
|
if cnt == cnt_max:
|
||||||
|
end_date = sada.bt_to if sada.mode in [Mode.BT or Mode.PREP] else sada.stopped
|
||||||
|
# Parse trades
|
||||||
|
|
||||||
|
trades_dicts = sada.metrics["prescr_trades"]
|
||||||
|
|
||||||
|
for trade_dict in trades_dicts:
|
||||||
|
trade_dict['last_update'] = datetime.fromtimestamp(trade_dict.get('last_update')).astimezone(zoneNY) if trade_dict['last_update'] is not None else None
|
||||||
|
trade_dict['entry_time'] = datetime.fromtimestamp(trade_dict.get('entry_time')).astimezone(zoneNY) if trade_dict['entry_time'] is not None else None
|
||||||
|
trade_dict['exit_time'] = datetime.fromtimestamp(trade_dict.get('exit_time')).astimezone(zoneNY) if trade_dict['exit_time'] is not None else None
|
||||||
|
trades.append(Trade(**trade_dict))
|
||||||
|
return 0, trades, cnt_max
|
||||||
@ -320,10 +320,11 @@
|
|||||||
<button title="Export selected days to XML" id="button_export_xml" class="btn btn-outline-success btn-sm">Export xml</button>
|
<button title="Export selected days to XML" id="button_export_xml" class="btn btn-outline-success btn-sm">Export xml</button>
|
||||||
<button title="Export selected days to CSV" id="button_export_csv" class="btn btn-outline-success btn-sm">Export csv</button>
|
<button title="Export selected days to CSV" id="button_export_csv" class="btn btn-outline-success btn-sm">Export csv</button>
|
||||||
<button title="For selected days generates basic report image." id="button_report" class="btn btn-outline-success btn-sm">Report(q)</button>
|
<button title="For selected days generates basic report image." id="button_report" class="btn btn-outline-success btn-sm">Report(q)</button>
|
||||||
<button title="For selected days creates heatmap for optimal profit/loss cutoffs" id="button_analyze" class="btn btn-outline-success btn-sm">Cutoffs Heatmap</button>
|
<!-- <button title="For selected days creates heatmap for optimal profit/loss cutoffs" id="button_analyze" class="btn btn-outline-success btn-sm">Cutoffs Heatmap</button> -->
|
||||||
<!-- <button id="button_stopall" class="btn btn-outline-success btn-sm">Stop All</button>
|
<!-- <button id="button_stopall" class="btn btn-outline-success btn-sm">Stop All</button>
|
||||||
<button id="button_refresh" class="btn btn-outline-success btn-sm">Refresh</button> -->
|
<button id="button_refresh" class="btn btn-outline-success btn-sm">Refresh</button> -->
|
||||||
<div id="buttons-container"></div>
|
<div id="buttons-container" style="display: contents"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div>
|
<!-- <div>
|
||||||
@ -850,7 +851,7 @@
|
|||||||
<!-- tady zacina polska docasna lokalizace -->
|
<!-- tady zacina polska docasna lokalizace -->
|
||||||
<!-- <script type="text/javascript" src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script> -->
|
<!-- <script type="text/javascript" src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script> -->
|
||||||
<script type="text/javascript" src="/static/js/libs/lightweightcharts/lightweight-charts.standalone.production410.js"></script>
|
<script type="text/javascript" src="/static/js/libs/lightweightcharts/lightweight-charts.standalone.production410.js"></script>
|
||||||
|
<script src="/static/js/dynamicbuttons.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<!-- <script src="/static/js/utils.js?v=1.01"></script> -->
|
<!-- <script src="/static/js/utils.js?v=1.01"></script> -->
|
||||||
@ -872,12 +873,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script src="/static/js/livewebsocket.js?v=1.01"></script>
|
<script src="/static/js/livewebsocket.js?v=1.01"></script>
|
||||||
<script src="/static/js/realtimechart.js?v=1.01"></script>
|
<script src="/static/js/realtimechart.js?v=1.01"></script>
|
||||||
<script src="/static/js/mytables.js?v=1.01"></script>
|
<script src="/static/js/mytables.js?v=1.01"></script>
|
||||||
<script src="/static/js/testlist.js?v=1.01"></script>
|
<script src="/static/js/testlist.js?v=1.01"></script>
|
||||||
<script src="/static/js/configform.js?v=1.01"></script>
|
<script src="/static/js/configform.js?v=1.01"></script>
|
||||||
<!-- <script src="/static/js/dynamicbuttons.js"></script> -->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
184
v2realbot/static/index2.html
Normal file
184
v2realbot/static/index2.html
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Bootstrap CSS (Dark Mode Enabled) -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css">
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<style>
|
||||||
|
/* Custom styles for dark mode and form offset */
|
||||||
|
.dropdown-menu-dark .form-control, .dropdown-menu-dark .btn {
|
||||||
|
background-color: #343a40;
|
||||||
|
border-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.dropdown-menu-dark .form-control:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: #5cb85c;
|
||||||
|
}
|
||||||
|
.dropdown-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* Align play icon vertically */
|
||||||
|
}
|
||||||
|
.hover-icon {
|
||||||
|
margin-left: auto; /* Push play icon to the right */
|
||||||
|
cursor: pointer; /* Change cursor on hover */
|
||||||
|
}
|
||||||
|
.action-form {
|
||||||
|
display: none; /* Hide form by default */
|
||||||
|
position: absolute;
|
||||||
|
left: 100%; /* Position form to the right of the dropdown item */
|
||||||
|
top: 0;
|
||||||
|
white-space: nowrap; /* Prevent wrapping on small screens */
|
||||||
|
width: max-content;
|
||||||
|
/* Add some space between the dropdown item and the form */
|
||||||
|
background: #343a40; /* Match the dropdown background color */
|
||||||
|
border-radius: 0.25rem; /* Match Bootstrap's border radius */
|
||||||
|
border: 1px solid #6c757d; /* Slight border for the form */
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem; /* Spacing between form fields */
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem; /* Spacing between each form group */
|
||||||
|
}
|
||||||
|
/* Floating label styles */
|
||||||
|
.form-label-group {
|
||||||
|
position: relative;
|
||||||
|
/* padding-top: 15px; */
|
||||||
|
}
|
||||||
|
.form-label-group label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 12px;
|
||||||
|
font-size: 75%;
|
||||||
|
/* transform: translateY(-50%); */
|
||||||
|
margin-top: 0; /* Adjusted for font size */
|
||||||
|
color: #6c757d;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.form-label-group input,
|
||||||
|
.form-label-group select {
|
||||||
|
padding-top: 18px;
|
||||||
|
/* padding-bottom: 2px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-dark text-white">
|
||||||
|
<div class="container mt-5">
|
||||||
|
<!-- Dropdown Button -->
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-secondary dropdown-toggle" type="button" id="actionDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
Choose Action
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="actionDropdown">
|
||||||
|
<!-- Action 1-->
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
Action 1
|
||||||
|
<i class="bi bi-play-circle float-end hover-icon"></i>
|
||||||
|
<!-- ... Action 1 content ... -->
|
||||||
|
<form class="d-none action-form">
|
||||||
|
<div class="form-label-group">
|
||||||
|
<input type="text" id="param1-action1" class="form-control form-control-sm" placeholder="Parameter 1" value="Default Value">
|
||||||
|
<label for="param1-action1">Parameter 1</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-label-group">
|
||||||
|
<input type="text" id="param2-action1" class="form-control form-control-sm" placeholder="Parameter 2">
|
||||||
|
<label for="param2-action1">Parameter 2</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-label-group">
|
||||||
|
<select class="form-select form-select-sm" id="select-action1">
|
||||||
|
<option selected>Option 1</option>
|
||||||
|
<option value="1">Option 2</option>
|
||||||
|
<option value="2">Option 3</option>
|
||||||
|
</select>
|
||||||
|
<label for="select-action1">Select Option</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm">Submit</button>
|
||||||
|
</form>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<!-- ... Additional Actions ... -->
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
Action 2
|
||||||
|
<i class="bi bi-play-circle float-end hover-icon"></i> <!-- Bootstrap Icons for Play -->
|
||||||
|
<!-- ... Action 1 content ... -->
|
||||||
|
<form class="d-none action-form">
|
||||||
|
<div class="form-label-group">
|
||||||
|
<input type="text" id="param1-action2" class="form-control form-control-sm" placeholder="Parameter 1" value="Default Value">
|
||||||
|
<label for="param1-action1">Parameter 1</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-label-group">
|
||||||
|
<input type="text" id="param2-action2" class="form-control form-control-sm" placeholder="Parameter 2">
|
||||||
|
<label for="param2-action2">Parameter 2</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-label-group">
|
||||||
|
<select class="form-select form-select-sm" id="select-action2">
|
||||||
|
<option selected>Option 1</option>
|
||||||
|
<option value="1">Option 2</option>
|
||||||
|
<option value="2">Option 3</option>
|
||||||
|
</select>
|
||||||
|
<label for="select-action2">Select Option</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm">Submit</button>
|
||||||
|
</form>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- jQuery and Bootstrap Bundle -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// Toggle visibility of form on hover for any number of actions
|
||||||
|
$('.dropdown-menu').on('mouseenter', '.dropdown-item', function() {
|
||||||
|
$(this).find('.action-form').removeClass('d-none').show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.dropdown-menu').on('mouseleave', '.dropdown-item', function() {
|
||||||
|
setTimeout(() => { // Timeout to prevent flickering effect
|
||||||
|
if (!$('.action-form:hover').length) {
|
||||||
|
$(this).find('.action-form').addClass('d-none').hide();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// // Show the form when hovering over the play icon
|
||||||
|
// $('.dropdown-menu').on('mouseenter', '.hover-icon', function() {
|
||||||
|
// $(this).siblings('.action-form').removeClass('d-none').show();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Hide the form when hovering out of the play icon and form area
|
||||||
|
// $('.dropdown-menu').on('mouseleave', '.hover-icon, .action-form', function() {
|
||||||
|
// setTimeout(() => { // Timeout to prevent flickering effect
|
||||||
|
// if (!$('.action-form:hover').length) {
|
||||||
|
// $('.action-form').hide();
|
||||||
|
// }
|
||||||
|
// }, 100);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Hide form when mouse leaves the form area
|
||||||
|
$('.dropdown-menu').on('mouseleave', '.action-form', function() {
|
||||||
|
$(this).hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Submit form logic
|
||||||
|
$('.dropdown-menu').on('submit', '.action-form', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Add logic to process form submission
|
||||||
|
console.log('Form submitted for', $(this).closest('.dropdown-item').text().trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -9,9 +9,9 @@
|
|||||||
// PRIMARY KEY("id" AUTOINCREMENT)
|
// PRIMARY KEY("id" AUTOINCREMENT)
|
||||||
// ); //novy komentar
|
// ); //novy komentar
|
||||||
|
|
||||||
configData = {}
|
let configData = {}
|
||||||
|
|
||||||
//pridat sem i config area
|
//sluzba z globalni promenne s JS configuraci dotahne dana data
|
||||||
function get_from_config(name, def_value) {
|
function get_from_config(name, def_value) {
|
||||||
def_value = def_value ? def_value : null
|
def_value = def_value ? def_value : null
|
||||||
console.log("required", name, configData)
|
console.log("required", name, configData)
|
||||||
@ -25,52 +25,58 @@ function get_from_config(name, def_value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
const apiBaseUrl = '';
|
|
||||||
|
|
||||||
// Function to populate the config list and load JSON data initially
|
function loadConfig(configName) {
|
||||||
function loadConfig(configName) {
|
return new Promise((resolve, reject) => {
|
||||||
const rec = new Object()
|
const rec = new Object();
|
||||||
rec.item_name = configName
|
rec.item_name = configName;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `${apiBaseUrl}/config-items-by-name/`,
|
url: `/config-items-by-name/`,
|
||||||
beforeSend: function (xhr) {
|
beforeSend: function (xhr) {
|
||||||
xhr.setRequestHeader('X-API-Key',
|
xhr.setRequestHeader('X-API-Key', API_KEY);
|
||||||
API_KEY); },
|
},
|
||||||
METHOD: 'GET',
|
method: 'GET',
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: rec,
|
data: rec,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
console.log(data)
|
|
||||||
try {
|
try {
|
||||||
configData[configName] = JSON.parse(data.json_data)
|
var configData = JSON.parse(data.json_data);
|
||||||
console.log(configData)
|
resolve(configData); // Resolve the promise with configData
|
||||||
console.log("jsme tu")
|
|
||||||
indConfig = configData["JS"].indConfig
|
|
||||||
console.log("after")
|
|
||||||
//console.log(JSON.stringify(indConfig, null,null, 2))
|
|
||||||
|
|
||||||
console.log("before CHART_SHOW_TEXT",CHART_SHOW_TEXT)
|
|
||||||
var CHART_SHOW_TEXT = configData["JS"].CHART_SHOW_TEXT
|
|
||||||
console.log("after CHART_SHOW_TEXT",CHART_SHOW_TEXT)
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
window.alert(`Nešlo rozparsovat JSON_data string ${configName}`, error.message)
|
reject(error); // Reject the promise if there's an error
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
var err = eval("(" + xhr.responseText + ")");
|
reject(new Error(xhr.responseText)); // Reject the promise on AJAX error
|
||||||
window.alert(`Nešlo dotáhnout config nastaveni z db ${configName}`, JSON.stringify(xhr));
|
|
||||||
console.log(JSON.stringify(xhr));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfiguration(area) {
|
||||||
|
return loadConfig(area).then(configData => {
|
||||||
|
console.log("Config loaded for", area, configData);
|
||||||
|
return configData;
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error loading config for', area, error);
|
||||||
|
throw error; // Re-throw to allow caller to handle
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//asynchrone naplni promennou
|
||||||
|
async function loadConfigData(jsConfigName) {
|
||||||
|
try {
|
||||||
|
configData[jsConfigName] = await getConfiguration(jsConfigName);
|
||||||
|
console.log("jsConfigName", jsConfigName);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load button configuration:',jsConfigName, error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const jsConfigName = "JS"
|
|
||||||
//naloadovan config
|
|
||||||
loadConfig(jsConfigName)
|
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
var jsConfigName = "JS"
|
||||||
|
loadConfigData(jsConfigName)
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,66 +1,285 @@
|
|||||||
//ekvivalent to ready
|
//ekvivalent to ready
|
||||||
$(function(){
|
$(function(){
|
||||||
|
|
||||||
//dynamicke buttony predelat na trdi se vstupem (nazev cfg klice, id conteineru)
|
//load configu buttons
|
||||||
var buttonConfig = get_from_config("analyze_buttons");
|
loadConfig("dynamic_buttons").then(config => {
|
||||||
|
console.log("Config loaded for dynamic_buttons", config);
|
||||||
|
|
||||||
console.log("here")
|
// $(targetElement).append(dropdownHtml)
|
||||||
|
// // Find the ul element within the dropdown
|
||||||
|
// var dropdownMenu = $(targetElement).find('.dropdown-menu');
|
||||||
|
configData["dynamic_buttons"] = config
|
||||||
|
//toto je obecné nad table buttony
|
||||||
|
console.log("conf data z buttonu po loadu", configData)
|
||||||
|
populate_dynamic_buttons($("#buttons-container"), config);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error loading config for', "dynamic_buttons", error);
|
||||||
|
});
|
||||||
|
|
||||||
buttonConfig.forEach(function(button) {
|
|
||||||
var $btnGroup = $('<div>', {class: 'btn-group'});
|
|
||||||
var $btn = $('<button>', {
|
|
||||||
type: 'button',
|
|
||||||
class: 'btn btn-primary',
|
|
||||||
text: button.label
|
|
||||||
});
|
|
||||||
|
|
||||||
var $form = $('<form>', {class: 'input-group'});
|
|
||||||
|
|
||||||
// Handling additional parameters
|
|
||||||
for (var key in button.additionalParameters) {
|
|
||||||
var param = button.additionalParameters[key];
|
|
||||||
var $input;
|
|
||||||
|
|
||||||
if (param.type === 'select') {
|
|
||||||
$input = $('<select>', {class: 'form-select', name: key});
|
})
|
||||||
param.options.forEach(function(option) {
|
|
||||||
$input.append($('<option>', {value: option, text: option}));
|
//vstupem je #some dropdown menu (TODO mozna dava smysl, abychom si element vytvorili predtim
|
||||||
});
|
//a nikoliv v nasledne funkci jak to zatim je)
|
||||||
} else {
|
function populate_dynamic_buttons(targetElement, config, batch_id = null) {
|
||||||
$input = $('<input>', {
|
//console.log("buttonConfig",config)
|
||||||
type: param.type === 'number' ? 'number' : 'text',
|
|
||||||
class: 'form-control',
|
// Function to create form inputs based on the configuration
|
||||||
name: key,
|
function createFormInputs(additionalParameters, batch_id = null) {
|
||||||
placeholder: key
|
var formHtml = ''
|
||||||
});
|
// else
|
||||||
|
// {
|
||||||
|
// $.each(runner_ids, function(index, id) {
|
||||||
|
// formHtml += '<input type="hidden" name="runner_ids[]" value="' + id + '">';
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
$.each(additionalParameters, function(key, param) {
|
||||||
|
// Include 'name' attribute in each input element
|
||||||
|
var id_prefix = batch_id ? batch_id : ''
|
||||||
|
var id = id_prefix + key
|
||||||
|
switch(param.type) {
|
||||||
|
case 'select':
|
||||||
|
formHtml += '<div class="form-label-group"><select class="form-select form-select-sm" name="' + key + '" id="' + id + '">';
|
||||||
|
$.each(param.options, function(index, option) {
|
||||||
|
var selected = (option == param.defval) ? 'selected' : '';
|
||||||
|
formHtml += '<option ' + selected + '>' + option + '</option>';
|
||||||
|
});
|
||||||
|
formHtml += '</select><label for="' + id + '">' + key + '</label></div>';
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
formHtml += '<div class="form-label-group"><input type="text" name="' + key + '" id="' + id + '" class="form-control form-control-sm" placeholder="' + key + '" value="' + param.default + '"><label for="' + id + '">' + key + '</label></div>';
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
formHtml += '<div class="form-label-group"><input type="number" name="' + key + '" id="' + id + '" class="form-control form-control-sm" placeholder="' + key + '" value="' + param.default + '"><label for="' + id + '">' + key + '</label></div>';
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
formHtml += '<div class="form-label-group"><input type="checkbox" name="' + key + '" id="' + id + '" class="form-check" ' + (param.default? 'checked' : '') + '><label for="' + id + '">' + key + '</label></div>';
|
||||||
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
return formHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
//naplnime obecny element (mozna delat ve volajici fci)
|
||||||
|
|
||||||
|
//pro batche to je ikonka
|
||||||
|
if (batch_id) {
|
||||||
|
dropdownHtml = '<div class="dropdown stat_div" id="dd'+batch_id+'"><span class="material-symbols-outlined tool-icon dropdown-toggle" id="actionDropdown'+batch_id+'" data-bs-toggle="dropdown" aria-expanded="false">query_stats</span><ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="actionDropdown'+batch_id+'" id="ul'+batch_id+'"></ul></div>'
|
||||||
|
}
|
||||||
|
//pro runnery je to button
|
||||||
|
else {
|
||||||
|
dropdownHtml = '<div class="dropdown stat_div" id="dd'+batch_id+'"><button title="Available analysis to run on selected days" class="btn btn-outline-success btn-sm dropdown-toggle" type="button" id="actionDropdown'+batch_id+'" data-bs-toggle="dropdown" aria-expanded="false">Analytics</button><ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="actionDropdown'+batch_id+'" id="ul'+batch_id+'"></ul></div>'
|
||||||
|
|
||||||
|
}
|
||||||
|
targetElement.append(dropdownHtml)
|
||||||
|
//console.log("po pridani", targetElement)
|
||||||
|
// Find the ul element within the dropdown
|
||||||
|
var dropdownMenu = targetElement.find('.dropdown-menu');
|
||||||
|
|
||||||
|
// Dynamically create buttons and forms based on the configuration
|
||||||
|
$.each(config, function(index, buttonConfig) {
|
||||||
|
var formHtml = createFormInputs(buttonConfig.additionalParameters, batch_id);
|
||||||
|
var batchInputHtml = batch_id ? '<input type="hidden" name="batch_id" id="batch'+buttonConfig.function+batch_id+'" value="'+batch_id+'">': ''
|
||||||
|
var buttonHtml = '<li><a class="dropdown-item" href="#">' + buttonConfig.label +
|
||||||
|
'<i class="bi bi-play-circle float-end hover-icon"></i><form class="d-none action-form" data-endpoint="' + buttonConfig.apiEndpoint + '"><div class="spinner-border text-primary d-none" role="status" id="formSpinner"><span class="visually-hidden">Loading...</span></div><input type="hidden" name="function" id="func'+buttonConfig.function+batch_id+'" value="'+buttonConfig.function+'"></input>' +
|
||||||
|
batchInputHtml + formHtml + '<button type="submit" class="btn btn-primary btn-sm">Submit</button></form></a></li>';
|
||||||
|
dropdownMenu.append(buttonHtml);
|
||||||
|
//$(targetElement).append(buttonHtml);
|
||||||
|
//$('#actionDropdown').next('.dropdown-menu').append(buttonHtml);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Submit form logic
|
||||||
|
targetElement.find('.dropdown-menu').on('submit', '.action-form', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $form = $(this);
|
||||||
|
var $submitButton = $form.find('input[type="submit"], button[type="submit"]'); // Locate the submit button
|
||||||
|
var $spinner = $form.find('#formSpinner');
|
||||||
|
|
||||||
|
// Serialize the form data to a JSON object
|
||||||
|
var formData = $form.serializeArray().reduce(function(obj, item) {
|
||||||
|
// Handle checkbox, translating to boolean
|
||||||
|
if ($form.find(`[name="${item.name}"]`).attr('type') === 'checkbox') {
|
||||||
|
obj[item.name] = item.value === 'on' ? true : false;
|
||||||
|
} else {
|
||||||
|
obj[item.name] = item.value;
|
||||||
|
}
|
||||||
|
//Number should be numbers, not strings
|
||||||
|
if ($form.find(`[name="${item.name}"]`).attr('type') === 'number') {
|
||||||
|
obj[item.name] = Number(item.value)
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// puvodni bez boolean translatu
|
||||||
|
//var formData = $(this).serializeJSON();
|
||||||
|
|
||||||
|
//pokud nemame batch_id - dotahujeme rows ze selected runnerů
|
||||||
|
console.log("toto jsou formdata pred submitem", formData)
|
||||||
|
if (formData.batch_id == undefined) {
|
||||||
|
console.log("batch undefined")
|
||||||
|
rows = archiveRecords.rows('.selected');
|
||||||
|
console.log(rows)
|
||||||
|
if (rows == undefined || rows.data().length == 0) {
|
||||||
|
console.log("no selected rows")
|
||||||
|
alert("no selected rows or batch_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Creating an array to store the IDs
|
||||||
|
formData.runner_ids = []
|
||||||
|
|
||||||
|
// Iterating over the selected rows to extract the IDs
|
||||||
|
rows.every(function (rowIdx, tableLoop, rowLoop ) {
|
||||||
|
var data = this.data()
|
||||||
|
formData.runner_ids.push(data.id);
|
||||||
|
});
|
||||||
|
|
||||||
$form.append($input);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$btnGroup.append($btn).append($form);
|
//population of object that is expected by the endpoint
|
||||||
$('#buttons-container').append($btnGroup);
|
obj = {}
|
||||||
|
if (formData.runner_ids) {
|
||||||
|
obj.runner_ids = formData.runner_ids
|
||||||
|
delete formData.runner_ids
|
||||||
|
}
|
||||||
|
if (formData.batch_id) {
|
||||||
|
obj.batch_id = formData.batch_id
|
||||||
|
delete formData.batch_id
|
||||||
|
}
|
||||||
|
obj.function = formData.function
|
||||||
|
delete formData.function
|
||||||
|
obj.params = {}
|
||||||
|
obj.params = formData
|
||||||
|
|
||||||
// Event listener for button
|
$submitButton.attr('disabled', true);
|
||||||
$btn.on('click', function(event) {
|
$spinner.removeClass('d-none');
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
var formData = $form.serializeArray().reduce(function(obj, item) {
|
console.log("toto jsou transformovana data", obj)
|
||||||
obj[item.name] = item.value;
|
var apiEndpoint = $(this).data('endpoint');
|
||||||
return obj;
|
// console.log("formdata", formData)
|
||||||
}, {});
|
// API call (adjust as needed for your backend)
|
||||||
|
$.ajax({
|
||||||
$.ajax({
|
url: apiEndpoint,
|
||||||
url: button.apiEndpoint,
|
beforeSend: function (xhr) {
|
||||||
method: 'POST',
|
xhr.setRequestHeader('X-API-Key',
|
||||||
data: formData,
|
API_KEY); },
|
||||||
success: function(response) {
|
method: 'POST',
|
||||||
console.log('API Call Successful:', response);
|
//menime hlavicku podle toho jestli je uspesne nebo ne, abychom mohli precist chybovou hlasku
|
||||||
},
|
xhr: function() {
|
||||||
error: function(error) {
|
var xhr = new XMLHttpRequest();
|
||||||
console.error('API Call Failed:', error);
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState === 2) { // Headers have been received
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
xhr.responseType = "blob"; // Set responseType to 'blob' for successful image responses
|
||||||
|
} else {
|
||||||
|
xhr.responseType = "text"; // Set responseType to 'text' for error messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return xhr;
|
||||||
|
},
|
||||||
|
xhrFields: {
|
||||||
|
responseType: 'blob'
|
||||||
|
},
|
||||||
|
contentType: "application/json",
|
||||||
|
processData: false,
|
||||||
|
data: JSON.stringify(obj),
|
||||||
|
success: function(data, textStatus, xhr) {
|
||||||
|
if (xhr.getResponseHeader("Content-Type") === "image/png") {
|
||||||
|
// Process as Blob
|
||||||
|
var blob = new Blob([data], { type: 'image/png' });
|
||||||
|
var url = window.URL || window.webkitURL;
|
||||||
|
display_image(url.createObjectURL(blob));
|
||||||
|
} else {
|
||||||
|
// Process as JSON
|
||||||
|
console.log('Received JSON', data);
|
||||||
}
|
}
|
||||||
});
|
$submitButton.attr('disabled', false);
|
||||||
|
$spinner.addClass('d-none');
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
$spinner.addClass('d-none');
|
||||||
|
$submitButton.attr('disabled', false);
|
||||||
|
console.log(xhr, status, error)
|
||||||
|
console.log(xhr.responseJSON.message)
|
||||||
|
if (xhr.responseJSON && xhr.responseJSON.detail) {
|
||||||
|
console.log('Error:', xhr.responseJSON.detail);
|
||||||
|
window.alert(xhr.responseJSON.detail);
|
||||||
|
} else {
|
||||||
|
// Fallback error message
|
||||||
|
console.log('Error:', error);
|
||||||
|
window.alert('An unexpected error occurred');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
console.log('Form submitted for', $(this).closest('.dropdown-item').text().trim());
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
//HANDLERS
|
||||||
|
|
||||||
|
//CLICKABLE VERSION (odstranit d-none z action-formu)
|
||||||
|
// Attach click event to each dropdown item
|
||||||
|
// $('.dropdown-menu').on('click', '.dropdown-item', function(event) {
|
||||||
|
// event.stopPropagation(); // Stop the event from bubbling up
|
||||||
|
|
||||||
|
// var currentForm = $(this).find('.action-form');
|
||||||
|
// // Hide all other forms
|
||||||
|
// $('.action-form').not(currentForm).hide();
|
||||||
|
// // Toggle current form
|
||||||
|
// currentForm.toggle();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Hide form when clicking outside
|
||||||
|
// $(document).on('click', function(event) {
|
||||||
|
// if (!$(event.target).closest('.dropdown-item').length) {
|
||||||
|
// $('.action-form').hide();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Prevent global click event from hiding form when clicking inside a form
|
||||||
|
// $('.dropdown-menu').on('click', '.action-form', function(event) {
|
||||||
|
// event.stopPropagation();
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
//VERZE on HOVER (je treba pridat class d-none do action formu)
|
||||||
|
// Toggle visibility of form on hover
|
||||||
|
targetElement.find('.dropdown-menu').on('mouseenter', '.dropdown-item', function() {
|
||||||
|
$(this).find('.action-form').removeClass('d-none').show();
|
||||||
|
}).on('mouseleave', '.dropdown-item', function() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!$('.action-form:hover').length) {
|
||||||
|
$(this).find('.action-form').addClass('d-none').hide();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
// // Hide form when mouse leaves the form area
|
||||||
|
// targetElement.find('.dropdown-menu').on('mouseleave', '.action-form', function() {
|
||||||
|
// $(this).hide();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// stop propagating click up
|
||||||
|
targetElement.find('.dropdown').on('click', function(event) {
|
||||||
|
// Stop the event from propagating to parent elements
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
// stop propagating click up
|
||||||
|
targetElement.find('.action-form').on('click', function(event) {
|
||||||
|
// Stop the event from propagating to parent elements
|
||||||
|
event.stopPropagation();
|
||||||
|
// Check if the clicked element or any of its parents is a submit button
|
||||||
|
if (!$(event.target).closest('input[type="submit"], button[type="submit"]').length) {
|
||||||
|
// Stop the event from propagating to parent elements
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
140
v2realbot/static/js/dynamicbuttons_oldModal.js
Normal file
140
v2realbot/static/js/dynamicbuttons_oldModal.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
//ekvivalent to ready
|
||||||
|
$(function(){
|
||||||
|
|
||||||
|
// Toggle input fields based on the selected button
|
||||||
|
$('.main-btn, .dropdown-item').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var targetId = $(this).data('target');
|
||||||
|
|
||||||
|
// Hide all input groups
|
||||||
|
$('.input-group').hide();
|
||||||
|
|
||||||
|
// Show the corresponding input group
|
||||||
|
$(targetId).show();
|
||||||
|
});
|
||||||
|
|
||||||
|
// //load configu buttons
|
||||||
|
// loadConfig("dynamic_buttons").then(configData => {
|
||||||
|
// console.log("Config loaded for dynamic_buttons", configData);
|
||||||
|
// populate_dynamic_buttons(configData);
|
||||||
|
// }).catch(error => {
|
||||||
|
// console.error('Error loading config for', area, error);
|
||||||
|
// });
|
||||||
|
|
||||||
|
function populate_dynamic_buttons(buttonConfig) {
|
||||||
|
console.log("buttonConfig",buttonConfig)
|
||||||
|
|
||||||
|
|
||||||
|
buttonConfig.forEach(function(button) {
|
||||||
|
var modalId = 'modal-' + button.id;
|
||||||
|
var $btn = $('<button>', {
|
||||||
|
type: 'button',
|
||||||
|
class: 'btn btn-primary',
|
||||||
|
'data-bs-toggle': 'modal',
|
||||||
|
'data-bs-target': '#' + modalId,
|
||||||
|
text: button.label
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create and append modal structure
|
||||||
|
var $modal = createModalStructure(button, modalId);
|
||||||
|
$('#buttons-container').append($btn).append($modal);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global event listener for modal form submission
|
||||||
|
$(document).on('submit', '.modal form', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var $form = $(this);
|
||||||
|
var formData = $form.serializeArray().reduce(function(obj, item) {
|
||||||
|
obj[item.name] = item.value;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
var apiEndpoint = $form.data('api-endpoint');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: apiEndpoint,
|
||||||
|
method: 'POST',
|
||||||
|
data: formData,
|
||||||
|
success: function(response) {
|
||||||
|
console.log('API Call Successful:', response);
|
||||||
|
$form.closest('.modal').modal('hide');
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
console.error('API Call Failed:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function createModalStructure(button, modalId) {
|
||||||
|
var $modal = $('<div>', {
|
||||||
|
class: 'modal fade',
|
||||||
|
id: modalId,
|
||||||
|
tabindex: '-1',
|
||||||
|
'aria-labelledby': modalId + 'Label',
|
||||||
|
'aria-hidden': 'true'
|
||||||
|
});
|
||||||
|
|
||||||
|
var $modalDialog = $('<div>', {class: 'modal-dialog'});
|
||||||
|
var $modalContent = $('<div>', {class: 'modal-content'});
|
||||||
|
|
||||||
|
var $modalHeader = $('<div>', {class: 'modal-header'});
|
||||||
|
$modalHeader.append($('<h5>', {
|
||||||
|
class: 'modal-title',
|
||||||
|
id: modalId + 'Label',
|
||||||
|
text: button.label
|
||||||
|
}));
|
||||||
|
$modalHeader.append($('<button>', {
|
||||||
|
type: 'button',
|
||||||
|
class: 'btn-close',
|
||||||
|
'data-bs-dismiss': 'modal',
|
||||||
|
'aria-label': 'Close'
|
||||||
|
}));
|
||||||
|
|
||||||
|
var $modalBody = $('<div>', {class: 'modal-body'});
|
||||||
|
var $form = $('<form>', {
|
||||||
|
'data-api-endpoint': button.apiEndpoint
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handling additional parameters
|
||||||
|
for (var key in button.additionalParameters) {
|
||||||
|
var param = button.additionalParameters[key];
|
||||||
|
var $formGroup = $('<div>', {class: 'mb-3'});
|
||||||
|
|
||||||
|
if (param.type === 'select') {
|
||||||
|
var $label = $('<label>', {class: 'form-label', text: key});
|
||||||
|
var $select = $('<select>', {class: 'form-select', name: key});
|
||||||
|
param.options.forEach(function(option) {
|
||||||
|
$select.append($('<option>', {value: option, text: option}));
|
||||||
|
});
|
||||||
|
$formGroup.append($label).append($select);
|
||||||
|
} else {
|
||||||
|
$formGroup.append($('<label>', {class: 'form-label', text: key}));
|
||||||
|
$formGroup.append($('<input>', {
|
||||||
|
type: param.type === 'number' ? 'number' : 'text',
|
||||||
|
class: 'form-control',
|
||||||
|
name: key,
|
||||||
|
placeholder: key
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
$form.append($formGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
var $modalFooter = $('<div>', {class: 'modal-footer'});
|
||||||
|
$modalFooter.append($('<button>', {
|
||||||
|
type: 'submit',
|
||||||
|
class: 'btn btn-primary',
|
||||||
|
text: 'Submit'
|
||||||
|
}));
|
||||||
|
|
||||||
|
$modalBody.append($form);
|
||||||
|
$modalContent.append($modalHeader).append($modalBody).append($modalFooter);
|
||||||
|
$modalDialog.append($modalContent);
|
||||||
|
$modal.append($modalDialog);
|
||||||
|
|
||||||
|
return $modal;
|
||||||
|
}
|
||||||
@ -296,7 +296,7 @@ function delete_batch(event){
|
|||||||
function analyze_optimal_cutoff(batch_id = null) {
|
function analyze_optimal_cutoff(batch_id = null) {
|
||||||
//definice parametru
|
//definice parametru
|
||||||
param_obj = { rem_outliers:false, steps:50}
|
param_obj = { rem_outliers:false, steps:50}
|
||||||
obj = {runner_ids:[], batch_id:null, params:param_obj}
|
obj = {function: "analyze_optimal_cutoff", runner_ids:[], batch_id:null, params:param_obj}
|
||||||
//bereme bud selected runners
|
//bereme bud selected runners
|
||||||
if (!batch_id) {
|
if (!batch_id) {
|
||||||
rows = archiveRecords.rows('.selected').data();
|
rows = archiveRecords.rows('.selected').data();
|
||||||
@ -330,6 +330,19 @@ function analyze_optimal_cutoff(batch_id = null) {
|
|||||||
xhrFields: {
|
xhrFields: {
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
},
|
},
|
||||||
|
xhr: function() {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState === 2) { // Headers have been received
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
xhr.responseType = "blob"; // Set responseType to 'blob' for successful image responses
|
||||||
|
} else {
|
||||||
|
xhr.responseType = "text"; // Set responseType to 'text' for error messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return xhr;
|
||||||
|
},
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
processData: false,
|
processData: false,
|
||||||
data: JSON.stringify(obj),
|
data: JSON.stringify(obj),
|
||||||
@ -344,7 +357,7 @@ function analyze_optimal_cutoff(batch_id = null) {
|
|||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
console.log("proc to skace do erroru?")
|
console.log("proc to skace do erroru?")
|
||||||
//window.alert(JSON.stringify(xhr));
|
window.alert(JSON.stringify(xhr));
|
||||||
console.log(JSON.stringify(xhr));
|
console.log(JSON.stringify(xhr));
|
||||||
$('#button_analyze').attr('disabled',false);
|
$('#button_analyze').attr('disabled',false);
|
||||||
if (!batch_id) {
|
if (!batch_id) {
|
||||||
@ -419,7 +432,9 @@ function display_image(imageUrl) {
|
|||||||
//$('#imagePreview').show();
|
//$('#imagePreview').show();
|
||||||
window.$('#imageModal').modal('show');
|
window.$('#imageModal').modal('show');
|
||||||
};
|
};
|
||||||
img.onerror = function() {
|
img.onerror = function(e) {
|
||||||
|
console.log("Image load error", e);
|
||||||
|
console.log("Image object:", img);
|
||||||
console.log("no image available")
|
console.log("no image available")
|
||||||
// If the image fails to load, do nothing
|
// If the image fails to load, do nothing
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
initialize_archiveRecords();
|
||||||
archiveRecords.ajax.reload();
|
archiveRecords.ajax.reload();
|
||||||
disable_arch_buttons();
|
disable_arch_buttons();
|
||||||
|
|
||||||
|
|||||||
@ -1,90 +1,72 @@
|
|||||||
//archive table
|
var archiveRecords = null
|
||||||
var archiveRecords =
|
|
||||||
$('#archiveTable').DataTable( {
|
|
||||||
ajax: {
|
|
||||||
url: '/archived_runners_p/',
|
|
||||||
dataSrc: 'data',
|
|
||||||
method:"POST",
|
|
||||||
contentType: "application/json",
|
|
||||||
// dataType: "json",
|
|
||||||
beforeSend: function (xhr) {
|
|
||||||
xhr.setRequestHeader('X-API-Key',
|
|
||||||
API_KEY); },
|
|
||||||
data: function (d) {
|
|
||||||
return JSON.stringify(d);
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
//var err = eval("(" + xhr.responseText + ")");
|
|
||||||
//window.alert(JSON.stringify(xhr));
|
|
||||||
console.log(JSON.stringify(xhr));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
columns: [{ data: 'id' },
|
|
||||||
{data: 'strat_id'},
|
|
||||||
{data: 'name'},
|
|
||||||
{data: 'symbol'},
|
|
||||||
{data: 'note'},
|
|
||||||
{data: 'started'},
|
|
||||||
{data: 'stopped'},
|
|
||||||
{data: 'mode'},
|
|
||||||
{data: 'account', visible: true},
|
|
||||||
{data: 'bt_from', visible: true},
|
|
||||||
{data: 'bt_to', visible: true},
|
|
||||||
{data: 'ilog_save', visible: true},
|
|
||||||
{data: 'profit'},
|
|
||||||
{data: 'trade_count', visible: true},
|
|
||||||
{data: 'end_positions', visible: true},
|
|
||||||
{data: 'end_positions_avgp', visible: true},
|
|
||||||
{data: 'metrics', visible: true},
|
|
||||||
{data: 'batch_id', visible: true},
|
|
||||||
],
|
|
||||||
paging: true,
|
|
||||||
processing: true,
|
|
||||||
serverSide: true,
|
|
||||||
columnDefs: [
|
|
||||||
{
|
|
||||||
targets: 1,
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
if (type === 'display') {
|
|
||||||
//console.log("arch")
|
|
||||||
var color = getColorForId(data);
|
|
||||||
return '<div class="tdnowrap" data-bs-toggle="tooltip" data-bs-placement="top" title="'+data+'"><span class="color-tag" style="background-color:' + color + ';"></span>'+data+'</div>';
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: [0,17],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
if (!data) return data
|
|
||||||
return '<div class="tdnowrap" title="'+data+'">'+data+'</i>'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: [5],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
now = new Date(data)
|
|
||||||
if (type == "sort") {
|
|
||||||
return new Date(data).getTime();
|
|
||||||
}
|
|
||||||
var date = new Date(data);
|
|
||||||
tit = date.toLocaleString('cs-CZ', {
|
|
||||||
timeZone: 'America/New_York',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isToday(now)) {
|
//ekvivalent to ready
|
||||||
//return local time only
|
function initialize_archiveRecords() {
|
||||||
return '<div title="'+tit+'">'+ 'dnes ' + format_date(data,false,true)+'</div>'
|
|
||||||
}
|
//archive table
|
||||||
else
|
archiveRecords =
|
||||||
{
|
$('#archiveTable').DataTable( {
|
||||||
//return local datetime
|
ajax: {
|
||||||
return '<div title="'+tit+'">'+ format_date(data,false,false)+'</div>'
|
url: '/archived_runners_p/',
|
||||||
}
|
dataSrc: 'data',
|
||||||
|
method:"POST",
|
||||||
|
contentType: "application/json",
|
||||||
|
// dataType: "json",
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
data: function (d) {
|
||||||
|
return JSON.stringify(d);
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
//var err = eval("(" + xhr.responseText + ")");
|
||||||
|
//window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
columns: [{ data: 'id' },
|
||||||
|
{data: 'strat_id'},
|
||||||
|
{data: 'name'},
|
||||||
|
{data: 'symbol'},
|
||||||
|
{data: 'note'},
|
||||||
|
{data: 'started'},
|
||||||
|
{data: 'stopped'},
|
||||||
|
{data: 'mode'},
|
||||||
|
{data: 'account', visible: true},
|
||||||
|
{data: 'bt_from', visible: true},
|
||||||
|
{data: 'bt_to', visible: true},
|
||||||
|
{data: 'ilog_save', visible: true},
|
||||||
|
{data: 'profit'},
|
||||||
|
{data: 'trade_count', visible: true},
|
||||||
|
{data: 'end_positions', visible: true},
|
||||||
|
{data: 'end_positions_avgp', visible: true},
|
||||||
|
{data: 'metrics', visible: true},
|
||||||
|
{data: 'batch_id', visible: true},
|
||||||
|
],
|
||||||
|
paging: true,
|
||||||
|
processing: true,
|
||||||
|
serverSide: true,
|
||||||
|
columnDefs: [
|
||||||
|
{
|
||||||
|
targets: 1,
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
if (type === 'display') {
|
||||||
|
//console.log("arch")
|
||||||
|
var color = getColorForId(data);
|
||||||
|
return '<div class="tdnowrap" data-bs-toggle="tooltip" data-bs-placement="top" title="'+data+'"><span class="color-tag" style="background-color:' + color + ';"></span>'+data+'</div>';
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [0,17],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
if (!data) return data
|
||||||
|
return '<div class="tdnowrap" title="'+data+'">'+data+'</i>'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
targets: [6],
|
targets: [5],
|
||||||
render: function ( data, type, row ) {
|
render: function ( data, type, row ) {
|
||||||
now = new Date(data)
|
now = new Date(data)
|
||||||
if (type == "sort") {
|
if (type == "sort") {
|
||||||
@ -93,289 +75,338 @@ var archiveRecords =
|
|||||||
var date = new Date(data);
|
var date = new Date(data);
|
||||||
tit = date.toLocaleString('cs-CZ', {
|
tit = date.toLocaleString('cs-CZ', {
|
||||||
timeZone: 'America/New_York',
|
timeZone: 'America/New_York',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isToday(now)) {
|
if (isToday(now)) {
|
||||||
//return local time only
|
//return local time only
|
||||||
return '<div title="'+tit+'" class="token level comment">'+ 'dnes ' + format_date(data,false,true)+'</div>'
|
return '<div title="'+tit+'">'+ 'dnes ' + format_date(data,false,true)+'</div>'
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//return local datetime
|
//return local datetime
|
||||||
return '<div title="'+tit+'" class="token level number">'+ format_date(data,false,false)+'</div>'
|
return '<div title="'+tit+'">'+ format_date(data,false,false)+'</div>'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
targets: [9,10],
|
targets: [6],
|
||||||
render: function ( data, type, row ) {
|
render: function ( data, type, row ) {
|
||||||
if (type == "sort") {
|
now = new Date(data)
|
||||||
return new Date(data).getTime();
|
if (type == "sort") {
|
||||||
}
|
return new Date(data).getTime();
|
||||||
//console.log(data)
|
}
|
||||||
//market datetime
|
var date = new Date(data);
|
||||||
return data ? format_date(data, true) : data
|
tit = date.toLocaleString('cs-CZ', {
|
||||||
},
|
timeZone: 'America/New_York',
|
||||||
},
|
})
|
||||||
{
|
|
||||||
targets: [2],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
return '<div class="tdname tdnowrap" title="'+data+'">'+data+'</div>'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// targets: [4],
|
|
||||||
// render: function ( data, type, row ) {
|
|
||||||
// return '<div class="tdname tdnowrap" title="'+data+'">'+data+'</div>'
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
targets: [16],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
//console.log("metrics", data)
|
|
||||||
try {
|
|
||||||
data = JSON.parse(data)
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
//console.log(error)
|
|
||||||
}
|
|
||||||
var res = JSON.stringify(data)
|
|
||||||
var unquoted = res.replace(/"([^"]+)":/g, '$1:')
|
|
||||||
|
|
||||||
//zobrazujeme jen kratkou summary pokud mame, jinak davame vse, do titlu davame vzdy vse
|
if (isToday(now)) {
|
||||||
|
//return local time only
|
||||||
|
return '<div title="'+tit+'" class="token level comment">'+ 'dnes ' + format_date(data,false,true)+'</div>'
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//return local datetime
|
||||||
|
return '<div title="'+tit+'" class="token level number">'+ format_date(data,false,false)+'</div>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [9,10],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
if (type == "sort") {
|
||||||
|
return new Date(data).getTime();
|
||||||
|
}
|
||||||
//console.log(data)
|
//console.log(data)
|
||||||
short = null
|
//market datetime
|
||||||
if ((data) && (data.profit) && (data.profit.sum)) {
|
return data ? format_date(data, true) : data
|
||||||
short = data.profit.sum
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [2],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
return '<div class="tdname tdnowrap" title="'+data+'">'+data+'</div>'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// targets: [4],
|
||||||
|
// render: function ( data, type, row ) {
|
||||||
|
// return '<div class="tdname tdnowrap" title="'+data+'">'+data+'</div>'
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
targets: [16],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
//console.log("metrics", data)
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
//console.log(error)
|
||||||
|
}
|
||||||
|
var res = JSON.stringify(data)
|
||||||
|
var unquoted = res.replace(/"([^"]+)":/g, '$1:')
|
||||||
|
|
||||||
|
//zobrazujeme jen kratkou summary pokud mame, jinak davame vse, do titlu davame vzdy vse
|
||||||
|
//console.log(data)
|
||||||
|
short = null
|
||||||
|
if ((data) && (data.profit) && (data.profit.sum)) {
|
||||||
|
short = data.profit.sum
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
short = unquoted
|
||||||
|
}
|
||||||
|
return '<div class="tdmetrics" title="'+unquoted+'">'+short+'</div>'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [4],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
return '<div class="tdnote" title="'+data+'">'+data+'</div>'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [13,14,15],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
return '<div class="tdsmall">'+data+'</div>'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [11],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
//if ilog_save true
|
||||||
|
if (data) {
|
||||||
|
return '<span class="material-symbols-outlined">done_outline</span>'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [8],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
//if ilog_save true
|
||||||
|
if (data == "ACCOUNT1") {
|
||||||
|
res="ACC1"
|
||||||
|
}
|
||||||
|
else if (data == "ACCOUNT2") {
|
||||||
|
res="ACC2"
|
||||||
|
}
|
||||||
|
else { res=data}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [7],
|
||||||
|
render: function ( data, type, row ) {
|
||||||
|
//if ilog_save true
|
||||||
|
if (data == "backtest") {
|
||||||
|
res="bt"
|
||||||
|
}
|
||||||
|
else { res=data}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
order: [[6, 'desc']],
|
||||||
|
select: {
|
||||||
|
info: true,
|
||||||
|
style: 'multi',
|
||||||
|
//selector: 'tbody > tr:not(.group-header)'
|
||||||
|
selector: 'tbody > tr:not(.group-header)'
|
||||||
|
},
|
||||||
|
paging: true,
|
||||||
|
// lengthChange: false,
|
||||||
|
// select: true,
|
||||||
|
// createdRow: function( row, data, dataIndex){
|
||||||
|
// if (is_running(data.id) ){
|
||||||
|
// alert("runner");
|
||||||
|
// $(row).addClass('highlight');
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
// Add row grouping based on 'batch_id'
|
||||||
|
//TODO projit a zrevidovat - pripadne optimalizovat
|
||||||
|
//NOTE zde jse skoncil
|
||||||
|
rowGroup: {
|
||||||
|
dataSrc: 'batch_id',
|
||||||
|
//toto je volano pri renderovani headeru grupy
|
||||||
|
startRender: function (rows, group) {
|
||||||
|
var firstRowData = rows.data()[0];
|
||||||
|
//pro no-batch-id je idcko prvni id
|
||||||
|
var groupId = group ? group : 'no-batch-id-' + firstRowData.id;
|
||||||
|
var stateKey = 'dt-group-state-' + groupId;
|
||||||
|
var state = localStorage.getItem(stateKey);
|
||||||
|
|
||||||
|
// Iterate over each row in the group to set the data attribute
|
||||||
|
// zaroven pro kazdy node nastavime viditelnost podle nastaveni
|
||||||
|
rows.every(function (rowIdx, tableLoop, rowLoop) {
|
||||||
|
var rowNode = $(this.node());
|
||||||
|
rowNode.attr('data-group-name', groupId);
|
||||||
|
if (state == 'collapsed') {
|
||||||
|
rowNode.hide();
|
||||||
|
} else {
|
||||||
|
rowNode.show();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize variables for the group
|
||||||
|
var itemCount = 0;
|
||||||
|
var period = '';
|
||||||
|
var profit = '';
|
||||||
|
var started = null;
|
||||||
|
var stratinId = null;
|
||||||
|
|
||||||
|
// // Process each item only once
|
||||||
|
// archiveRecords.rows({ search: 'applied' }).every(function (rowIdx, tableLoop, rowLoop) {
|
||||||
|
// var data = this.data();
|
||||||
|
|
||||||
|
// if ((group && data.batch_id == group)) {
|
||||||
|
// itemCount++;
|
||||||
|
// if (itemCount === 1 ) {
|
||||||
|
// firstNote = data.note ? data.note.substring(0, 14) : '';
|
||||||
|
|
||||||
|
// if (data.note) {
|
||||||
|
// better_counter = extractNumbersFromString(data.note);
|
||||||
|
// }
|
||||||
|
// try {
|
||||||
|
// profit = data.metrics.profit.batch_sum_profit;
|
||||||
|
// } catch (e) {
|
||||||
|
// profit = 'N/A';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
//pokud mame batch_id podivame se zda jeho nastaveni uz nema a pokud ano pouzijeme to
|
||||||
|
//pokud nemame tak si ho loadneme
|
||||||
|
if (group) {
|
||||||
|
const existingBatch = batchHeaders.find(batch => batch.batch_id == group);
|
||||||
|
//jeste neni v poli batchu - udelame hlavicku
|
||||||
|
if (!existingBatch) {
|
||||||
|
itemCount = extractNumbersFromString(firstRowData.note);
|
||||||
|
profit = firstRowData.metrics.profit.batch_sum_profit;
|
||||||
|
period = firstRowData.note ? firstRowData.note.substring(0, 14) : '';
|
||||||
|
started = firstRowData.started
|
||||||
|
stratinId = firstRowData.strat_id
|
||||||
|
var newBatchHeader = {batch_id:group, profit:profit, itemCount:itemCount, period:period, started:started, stratinId:stratinId}
|
||||||
|
batchHeaders.push(newBatchHeader)
|
||||||
|
}
|
||||||
|
//uz je v poli, ale mame novejsi (pribyl v ramci backtestu napr.) - updatujeme
|
||||||
|
else if (new Date(existingBatch.started) < new Date(firstRowData.started)) {
|
||||||
|
itemCount = extractNumbersFromString(firstRowData.note);
|
||||||
|
profit = firstRowData.metrics.profit.batch_sum_profit;
|
||||||
|
period = firstRowData.note ? firstRowData.note.substring(0, 14) : '';
|
||||||
|
started = firstRowData.started
|
||||||
|
stratinId = firstRowData.id
|
||||||
|
existingBatch.itemCount = itemCount;
|
||||||
|
existingBatch.profit = profit;
|
||||||
|
existingBatch.period = period;
|
||||||
|
existingBatch.started = started;
|
||||||
|
}
|
||||||
|
//uz je v poli batchu vytahneme
|
||||||
else {
|
else {
|
||||||
short = unquoted
|
profit = existingBatch.profit
|
||||||
|
itemCount = existingBatch.itemCount
|
||||||
|
period = existingBatch.period
|
||||||
|
started = existingBatch.started
|
||||||
|
stratinId = existingBatch.stratinId
|
||||||
}
|
}
|
||||||
return '<div class="tdmetrics" title="'+unquoted+'">'+short+'</div>'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: [4],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
return '<div class="tdnote" title="'+data+'">'+data+'</div>'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: [13,14,15],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
return '<div class="tdsmall">'+data+'</div>'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: [11],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
//if ilog_save true
|
|
||||||
if (data) {
|
|
||||||
return '<span class="material-symbols-outlined">done_outline</span>'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: [8],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
//if ilog_save true
|
|
||||||
if (data == "ACCOUNT1") {
|
|
||||||
res="ACC1"
|
|
||||||
}
|
|
||||||
else if (data == "ACCOUNT2") {
|
|
||||||
res="ACC2"
|
|
||||||
}
|
|
||||||
else { res=data}
|
|
||||||
return res
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: [7],
|
|
||||||
render: function ( data, type, row ) {
|
|
||||||
//if ilog_save true
|
|
||||||
if (data == "backtest") {
|
|
||||||
res="bt"
|
|
||||||
}
|
|
||||||
else { res=data}
|
|
||||||
return res
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
order: [[6, 'desc']],
|
|
||||||
select: {
|
|
||||||
info: true,
|
|
||||||
style: 'multi',
|
|
||||||
//selector: 'tbody > tr:not(.group-header)'
|
|
||||||
selector: 'tbody > tr:not(.group-header)'
|
|
||||||
},
|
|
||||||
paging: true,
|
|
||||||
// lengthChange: false,
|
|
||||||
// select: true,
|
|
||||||
// createdRow: function( row, data, dataIndex){
|
|
||||||
// if (is_running(data.id) ){
|
|
||||||
// alert("runner");
|
|
||||||
// $(row).addClass('highlight');
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
// Add row grouping based on 'batch_id'
|
|
||||||
//TODO projit a zrevidovat - pripadne optimalizovat
|
|
||||||
//NOTE zde jse skoncil
|
|
||||||
rowGroup: {
|
|
||||||
dataSrc: 'batch_id',
|
|
||||||
//toto je volano pri renderovani headeru grupy
|
|
||||||
startRender: function (rows, group) {
|
|
||||||
var firstRowData = rows.data()[0];
|
|
||||||
//pro no-batch-id je idcko prvni id
|
|
||||||
var groupId = group ? group : 'no-batch-id-' + firstRowData.id;
|
|
||||||
var stateKey = 'dt-group-state-' + groupId;
|
|
||||||
var state = localStorage.getItem(stateKey);
|
|
||||||
|
|
||||||
// Iterate over each row in the group to set the data attribute
|
|
||||||
// zaroven pro kazdy node nastavime viditelnost podle nastaveni
|
|
||||||
rows.every(function (rowIdx, tableLoop, rowLoop) {
|
|
||||||
var rowNode = $(this.node());
|
|
||||||
rowNode.attr('data-group-name', groupId);
|
|
||||||
if (state == 'collapsed') {
|
|
||||||
rowNode.hide();
|
|
||||||
} else {
|
|
||||||
rowNode.show();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize variables for the group
|
//zaroven nastavime u vsech childu
|
||||||
var itemCount = 0;
|
|
||||||
var period = '';
|
|
||||||
var profit = '';
|
|
||||||
var started = null;
|
|
||||||
var stratinId = null;
|
|
||||||
|
|
||||||
// // Process each item only once
|
// Construct the GROUP HEADER - sem pripadna tlačítka atp.
|
||||||
// archiveRecords.rows({ search: 'applied' }).every(function (rowIdx, tableLoop, rowLoop) {
|
//var groupHeaderContent = '<strong>' + (group ? 'Batch ID: ' + group : 'No Batch') + '</strong>';
|
||||||
// var data = this.data();
|
var tools = ''
|
||||||
|
var icon = ''
|
||||||
|
icon_color = ''
|
||||||
|
profit_icon_color = ''
|
||||||
|
exp_coll_icon_name = ''
|
||||||
|
exp_coll_icon_name = (state == 'collapsed') ? 'expand_more' : 'expand_less'
|
||||||
|
if (group) {
|
||||||
|
tools = '<span class="batchtool">'
|
||||||
|
tools += '<span id="batchtool_report_button" class="material-symbols-outlined tool-icon" title="Batch Report">lab_profile</span>'
|
||||||
|
tools += '<span id="batchtool_delete_button" class="material-symbols-outlined tool-icon" title="Delete Batch">delete</span>'
|
||||||
|
tools += '<span id="batchtool_exportcsv_button" class="material-symbols-outlined tool-icon" title="Export batch to csv">csv</span>'
|
||||||
|
tools += '<span id="batchtool_exportxml_button" class="material-symbols-outlined tool-icon" title="Export batch to xml">insert_drive_file</span>'
|
||||||
|
tools += '<span id="batchtool_cutoff_button" class="material-symbols-outlined tool-icon" title="Cutoff heatmap for batch">cut</span>'
|
||||||
|
//dynamic button placeholder
|
||||||
|
//tools += '<div class="dropdown"><button class="btn btn-outline-success btn-sm dropdown-toggle" type="button" id="actionDropdown" data-bs-toggle="dropdown" aria-expanded="false">Choose analyzer</button><ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="actionDropdown"></ul></div>'
|
||||||
|
tools += '<div class="batch_buttons_container" id="bb'+group+'" data-batch-id="'+group+'"></div>'
|
||||||
|
|
||||||
// if ((group && data.batch_id == group)) {
|
//final closure
|
||||||
// itemCount++;
|
tools += '</span>'
|
||||||
// if (itemCount === 1 ) {
|
icon_color = getColorForId(stratinId)
|
||||||
// firstNote = data.note ? data.note.substring(0, 14) : '';
|
profit_icon_color = (profit>0) ? "#4f8966" : "#bb2f5e" //"#d42962"
|
||||||
|
|
||||||
// if (data.note) {
|
|
||||||
// better_counter = extractNumbersFromString(data.note);
|
|
||||||
// }
|
|
||||||
// try {
|
|
||||||
// profit = data.metrics.profit.batch_sum_profit;
|
|
||||||
// } catch (e) {
|
|
||||||
// profit = 'N/A';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
//pokud mame batch_id podivame se zda jeho nastaveni uz nema a pokud ano pouzijeme to
|
|
||||||
//pokud nemame tak si ho loadneme
|
|
||||||
if (group) {
|
|
||||||
const existingBatch = batchHeaders.find(batch => batch.batch_id == group);
|
|
||||||
//jeste neni v poli batchu - udelame hlavicku
|
|
||||||
if (!existingBatch) {
|
|
||||||
itemCount = extractNumbersFromString(firstRowData.note);
|
|
||||||
profit = firstRowData.metrics.profit.batch_sum_profit;
|
|
||||||
period = firstRowData.note ? firstRowData.note.substring(0, 14) : '';
|
|
||||||
started = firstRowData.started
|
|
||||||
stratinId = firstRowData.strat_id
|
|
||||||
var newBatchHeader = {batch_id:group, profit:profit, itemCount:itemCount, period:period, started:started, stratinId:stratinId}
|
|
||||||
batchHeaders.push(newBatchHeader)
|
|
||||||
}
|
}
|
||||||
//uz je v poli, ale mame novejsi (pribyl v ramci backtestu napr.) - updatujeme
|
|
||||||
else if (new Date(existingBatch.started) < new Date(firstRowData.started)) {
|
|
||||||
itemCount = extractNumbersFromString(firstRowData.note);
|
|
||||||
profit = firstRowData.metrics.profit.batch_sum_profit;
|
|
||||||
period = firstRowData.note ? firstRowData.note.substring(0, 14) : '';
|
|
||||||
started = firstRowData.started
|
|
||||||
stratinId = firstRowData.id
|
|
||||||
existingBatch.itemCount = itemCount;
|
|
||||||
existingBatch.profit = profit;
|
|
||||||
existingBatch.period = period;
|
|
||||||
existingBatch.started = started;
|
|
||||||
}
|
|
||||||
//uz je v poli batchu vytahneme
|
|
||||||
else {
|
else {
|
||||||
profit = existingBatch.profit
|
//def color for no batch - semi transparent
|
||||||
itemCount = existingBatch.itemCount
|
icon_color = "#ced4da17"
|
||||||
period = existingBatch.period
|
|
||||||
started = existingBatch.started
|
|
||||||
stratinId = existingBatch.stratinId
|
|
||||||
}
|
}
|
||||||
}
|
icon = '<span class="material-symbols-outlined expand-icon" style="background-color:' + icon_color + ';" title="Expand">'+exp_coll_icon_name+'</span>'
|
||||||
|
|
||||||
//zaroven nastavime u vsech childu
|
//console.log(group, groupId, stratinId)
|
||||||
|
//var groupHeaderContent = '<span class="batchheader-batch-id">'+(group ? '<span class="color-tag" style="background-color:' + getColorForId(stratinId) + ';"></span>Batch ID: ' + group: 'No Batch')+'</span>';
|
||||||
// Construct the GROUP HEADER - sem pripadna tlačítka atp.
|
var groupHeaderContent = '<span class="batchheader-batch-id">'+ icon + (group ? 'Batch ID: ' + group: 'No Batch')+'</span>';
|
||||||
//var groupHeaderContent = '<strong>' + (group ? 'Batch ID: ' + group : 'No Batch') + '</strong>';
|
groupHeaderContent += (group ? ' <span class="batchheader-count-info">(' + itemCount + ')</span>' + ' <span class="batchheader-period-info">' + period + '</span> <span class="batchheader-profit-info" style="color:'+profit_icon_color+'">Profit: ' + profit + '</span>' : '');
|
||||||
var tools = ''
|
groupHeaderContent += group ? tools : ""
|
||||||
var icon = ''
|
return $('<tr/>')
|
||||||
icon_color = ''
|
.append('<td colspan="18">' + groupHeaderContent + '</td>')
|
||||||
profit_icon_color = ''
|
.attr('data-name', groupId)
|
||||||
exp_coll_icon_name = ''
|
.addClass('group-header')
|
||||||
exp_coll_icon_name = (state == 'collapsed') ? 'expand_more' : 'expand_less'
|
.addClass(state);
|
||||||
if (group) {
|
|
||||||
tools = '<span class="batchtool">'
|
|
||||||
tools += '<span id="batchtool_report_button" class="material-symbols-outlined tool-icon" title="Batch Report">lab_profile</span>'
|
|
||||||
tools += '<span id="batchtool_delete_button" class="material-symbols-outlined tool-icon" title="Delete Batch">delete</span>'
|
|
||||||
tools += '<span id="batchtool_exportcsv_button" class="material-symbols-outlined tool-icon" title="Export batch to csv">csv</span>'
|
|
||||||
tools += '<span id="batchtool_exportxml_button" class="material-symbols-outlined tool-icon" title="Export batch to xml">insert_drive_file</span>'
|
|
||||||
tools += '<span id="batchtool_cutoff_button" class="material-symbols-outlined tool-icon" title="Cutoff heatmap for batch">cut</span>'
|
|
||||||
//final closure
|
|
||||||
tools += '</span>'
|
|
||||||
icon_color = getColorForId(stratinId)
|
|
||||||
profit_icon_color = (profit>0) ? "#4f8966" : "#bb2f5e" //"#d42962"
|
|
||||||
}
|
}
|
||||||
else {
|
},
|
||||||
//def color for no batch - semi transparent
|
drawCallback: function (settings) {
|
||||||
icon_color = "#ced4da17"
|
//console.log("drawcallback", configData)
|
||||||
}
|
setTimeout(function(){
|
||||||
icon = '<span class="material-symbols-outlined expand-icon" style="background-color:' + icon_color + ';" title="Expand">'+exp_coll_icon_name+'</span>'
|
|
||||||
|
|
||||||
//console.log(group, groupId, stratinId)
|
//populate all tool buttons on batch header
|
||||||
//var groupHeaderContent = '<span class="batchheader-batch-id">'+(group ? '<span class="color-tag" style="background-color:' + getColorForId(stratinId) + ';"></span>Batch ID: ' + group: 'No Batch')+'</span>';
|
// Loop over all divs with the class 'batch-buttons-container'
|
||||||
var groupHeaderContent = '<span class="batchheader-batch-id">'+ icon + (group ? 'Batch ID: ' + group: 'No Batch')+'</span>';
|
if (configData["dynamic_buttons"]) {
|
||||||
groupHeaderContent += (group ? ' <span class="batchheader-count-info">(' + itemCount + ')</span>' + ' <span class="batchheader-period-info">' + period + '</span> <span class="batchheader-profit-info" style="color:'+profit_icon_color+'">Profit: ' + profit + '</span>' : '');
|
//console.log("jsme tu po cekani")
|
||||||
groupHeaderContent += group ? tools : ""
|
//console.log("pred loopem")
|
||||||
return $('<tr/>')
|
$('.batch_buttons_container').each((index, element) => {
|
||||||
.append('<td colspan="18">' + groupHeaderContent + '</td>')
|
//console.log("jsme uvnitr foreach");
|
||||||
.attr('data-name', groupId)
|
idecko = $(element).attr('id')
|
||||||
.addClass('group-header')
|
//console.log("idecko", idecko)
|
||||||
.addClass(state);
|
var batchId = $(element).data('batch-id'); // Get the data-batch-id attribute
|
||||||
|
//console.log("nalezeno pred", batchId, $(element));
|
||||||
|
populate_dynamic_buttons($(element), configData["dynamic_buttons"], batchId);
|
||||||
|
//console.log("po", $(element));
|
||||||
|
});
|
||||||
|
}else {
|
||||||
|
console.log("no dynamic_buttons configuration loaded")
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
// var api = this.api();
|
||||||
|
// var rows = api.rows({ page: 'current' }).nodes();
|
||||||
|
|
||||||
|
// api.column(17, { page: 'current' }).data().each(function (group, i) {
|
||||||
|
// console.log("drawCallabck i",i)
|
||||||
|
// console.log("rows", $(rows).eq(i))
|
||||||
|
// var groupName = group ? group : $(rows).eq(i).attr('data-name');
|
||||||
|
// console.log("groupName", groupName)
|
||||||
|
// var stateKey = 'dt-group-state-' + groupName;
|
||||||
|
// var state = localStorage.getItem(stateKey);
|
||||||
|
|
||||||
|
// if (state === 'collapsed') {
|
||||||
|
// $(rows).eq(i).hide();
|
||||||
|
// } else {
|
||||||
|
// $(rows).eq(i).show();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Set the unique identifier as a data attribute on each row
|
||||||
|
//$(rows).eq(i).attr('data-group-name', groupName);
|
||||||
|
|
||||||
|
// // Add or remove the 'collapsed' class based on the state
|
||||||
|
// if (groupName.startsWith('no-batch-id-')) {
|
||||||
|
// $('tr[data-name="' + groupName + '"]').toggleClass('collapsed', state === 'collapsed');
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
// drawCallback: function (settings) {
|
|
||||||
// var api = this.api();
|
|
||||||
// var rows = api.rows({ page: 'current' }).nodes();
|
|
||||||
|
|
||||||
// api.column(17, { page: 'current' }).data().each(function (group, i) {
|
|
||||||
// console.log("drawCallabck i",i)
|
|
||||||
// console.log("rows", $(rows).eq(i))
|
|
||||||
// var groupName = group ? group : $(rows).eq(i).attr('data-name');
|
|
||||||
// console.log("groupName", groupName)
|
|
||||||
// var stateKey = 'dt-group-state-' + groupName;
|
|
||||||
// var state = localStorage.getItem(stateKey);
|
|
||||||
|
|
||||||
// if (state === 'collapsed') {
|
|
||||||
// $(rows).eq(i).hide();
|
|
||||||
// } else {
|
|
||||||
// $(rows).eq(i).show();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Set the unique identifier as a data attribute on each row
|
|
||||||
// //$(rows).eq(i).attr('data-group-name', groupName);
|
|
||||||
|
|
||||||
// // // Add or remove the 'collapsed' class based on the state
|
|
||||||
// // if (groupName.startsWith('no-batch-id-')) {
|
|
||||||
// // $('tr[data-name="' + groupName + '"]').toggleClass('collapsed', state === 'collapsed');
|
|
||||||
// // }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -35,7 +35,9 @@
|
|||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
color: var(--bs-secondary);
|
color: var(--bs-secondary);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 2px;
|
/* padding: 2px; */
|
||||||
|
padding-left: 2px;
|
||||||
|
padding-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-icon:hover {
|
.tool-icon:hover {
|
||||||
@ -43,9 +45,72 @@
|
|||||||
color: var(--bs-dark-bg-subtle);
|
color: var(--bs-dark-bg-subtle);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 2px;
|
/* padding: 2px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat_div {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom styles for dark mode and form offset */
|
||||||
|
.dropdown-menu-dark .form-control, .dropdown-menu-dark .btn {
|
||||||
|
background-color: #343a40;
|
||||||
|
border-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.dropdown-menu-dark .form-control:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: #5cb85c;
|
||||||
|
}
|
||||||
|
.dropdown-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* Align play icon vertically */
|
||||||
|
}
|
||||||
|
.hover-icon {
|
||||||
|
margin-left: auto; /* Push play icon to the right */
|
||||||
|
cursor: pointer; /* Change cursor on hover */
|
||||||
|
}
|
||||||
|
.action-form {
|
||||||
|
z-index: 500000;
|
||||||
|
display: none; /* Hide form by default */
|
||||||
|
position: absolute;
|
||||||
|
left: 100%; /* Position form to the right of the dropdown item */
|
||||||
|
top: 0;
|
||||||
|
white-space: nowrap; /* Prevent wrapping on small screens */
|
||||||
|
width: max-content;
|
||||||
|
/* Add some space between the dropdown item and the form */
|
||||||
|
background: #343a40; /* Match the dropdown background color */
|
||||||
|
border-radius: 0.25rem; /* Match Bootstrap's border radius */
|
||||||
|
border: 1px solid #6c757d; /* Slight border for the form */
|
||||||
|
}
|
||||||
|
/* .form-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
} */
|
||||||
|
/* Floating label styles */
|
||||||
|
.form-label-group {
|
||||||
|
position: relative;
|
||||||
|
/* padding-top: 15px; */
|
||||||
|
}
|
||||||
|
.form-label-group label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 12px;
|
||||||
|
font-size: 75%;
|
||||||
|
/* transform: translateY(-50%); */
|
||||||
|
margin-top: 0; /* Adjusted for font size */
|
||||||
|
color: #6c757d;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.form-label-group input,
|
||||||
|
.form-label-group select {
|
||||||
|
padding-top: 18px;
|
||||||
|
/* padding-bottom: 2px; */
|
||||||
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
--bs-pagination-padding-x: 0.45rem;
|
--bs-pagination-padding-x: 0.45rem;
|
||||||
--bs-pagination-padding-y: 0.15rem;
|
--bs-pagination-padding-y: 0.15rem;
|
||||||
@ -271,7 +336,7 @@ table.dataTable thead>tr>th.sorting_asc:before, table.dataTable thead>tr>th.sort
|
|||||||
border-bottom: 10px solid #838E65; /* Triangle pointing down when expanded */
|
border-bottom: 10px solid #838E65; /* Triangle pointing down when expanded */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-group { margin-top: 10px; display: none; }
|
||||||
|
|
||||||
.group-header {
|
.group-header {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -280,33 +345,47 @@ table.dataTable thead>tr>th.sorting_asc:before, table.dataTable thead>tr>th.sort
|
|||||||
/* font-weight: bold; */
|
/* font-weight: bold; */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide all .batchtool_ elements by default */
|
/* Hide all .batchtool elements by default */
|
||||||
.batchtool {
|
.batchtool {
|
||||||
display: none;
|
display: inline-flex; /* Maintain the desired display type */
|
||||||
|
opacity: 0; /* Initially fully transparent */
|
||||||
|
visibility: hidden; /* Initially hidden */
|
||||||
|
transition: opacity 0.5s, visibility 0s 0.5s; /* Transition for opacity and delay for visibility */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show .batchtool elements when hovering over a .group-header row */
|
/* Show .batchtool elements immediately when hovering over a .group-header row */
|
||||||
.group-header:hover .batchtool {
|
.group-header:hover .batchtool {
|
||||||
display: inline-block; /* or whatever display type suits your layout */
|
opacity: 1; /* Fully opaque when hovered */
|
||||||
|
visibility: visible; /* Visible when hovered */
|
||||||
|
transition: opacity 0.5s, visibility 0s; /* Immediate transition for visibility */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delay the transition when mouse leaves */
|
||||||
|
.group-header .batchtool {
|
||||||
|
transition-delay: 0.5s; /* Delay the transition */
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-header .batchheader-profit-info {
|
.group-header .batchheader-profit-info {
|
||||||
color: #3e999e; /* Highlight profit info */
|
color: #3e999e; /* Highlight profit info */
|
||||||
|
vertical-align: super;
|
||||||
/* font-weight: bold; */
|
/* font-weight: bold; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-header .batchheader-count-info {
|
.group-header .batchheader-count-info {
|
||||||
color: #a1a1a1; /* Highlight count info */
|
color: #a1a1a1; /* Highlight count info */
|
||||||
|
vertical-align: super;
|
||||||
/* font-weight: bold; */
|
/* font-weight: bold; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-header .batchheader-batch-id {
|
.group-header .batchheader-batch-id {
|
||||||
color: #a1a1a1; /* Highlight period info */
|
color: #a1a1a1; /* Highlight period info */
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
vertical-align: super;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-header .batchheader-period-info {
|
.group-header .batchheader-period-info {
|
||||||
color: #a1a1a1; /* Highlight period info */
|
color: #a1a1a1; /* Highlight period info */
|
||||||
|
vertical-align: super;
|
||||||
/* font-weight: bold; */
|
/* font-weight: bold; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user