Compare commits
13 Commits
feature-gu
...
feature-ac
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ecb90d83f | |||
| 648489b0f4 | |||
| b54861bb62 | |||
| 804f4450a8 | |||
| c6504043ed | |||
| c7d7ca96a3 | |||
| 6a459cd745 | |||
| 208a1acae5 | |||
| 5fab264493 | |||
| a1e4d8d726 | |||
| 78c40f6d1a | |||
| a520c2fd2f | |||
| e9c3849bbc |
@ -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):
|
||||||
@ -52,6 +51,15 @@ class DataTablesRequest(BaseModel):
|
|||||||
# return user.id
|
# return user.id
|
||||||
# raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}")
|
# raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}")
|
||||||
|
|
||||||
|
|
||||||
|
#obecny vstup pro analyzera (vstupem muze byt bud batch_id nebo seznam runneru)
|
||||||
|
class AnalyzerInputs(BaseModel):
|
||||||
|
function: str
|
||||||
|
batch_id: Optional[str] = None
|
||||||
|
runner_ids: Optional[List[UUID]] = None
|
||||||
|
#additional parameter
|
||||||
|
params: Optional[dict] = {}
|
||||||
|
|
||||||
class RunDay(BaseModel):
|
class RunDay(BaseModel):
|
||||||
"""
|
"""
|
||||||
Helper object for batch run - carries list of days in format required by run batch manager
|
Helper object for batch run - carries list of days in format required by run batch manager
|
||||||
|
|||||||
@ -134,13 +134,21 @@ ACCOUNT1_LIVE_MAX_BATCH_SIZE = 1
|
|||||||
ACCOUNT1_LIVE_PAPER = False
|
ACCOUNT1_LIVE_PAPER = False
|
||||||
ACCOUNT1_LIVE_FEED = DataFeed.SIP
|
ACCOUNT1_LIVE_FEED = DataFeed.SIP
|
||||||
|
|
||||||
#SECONDARY PAPER
|
|
||||||
ACCOUNT2_PAPER_API_KEY = 'PK0OQHZG03PUZ1SC560V'
|
#SECONDARY PAPER - Martin
|
||||||
ACCOUNT2_PAPER_SECRET_KEY = 'cTglhm7kwRcZfFT27fQWz31sXaxadzQApFDW6Lat'
|
ACCOUNT2_PAPER_API_KEY = 'PKPDTCQLNHCBC2D9GQFB'
|
||||||
|
ACCOUNT2_PAPER_SECRET_KEY = 'c1Z2V0gBleQmwHYCreqqTs45Jy33RqPGrofuSayz'
|
||||||
ACCOUNT2_PAPER_MAX_BATCH_SIZE = 1
|
ACCOUNT2_PAPER_MAX_BATCH_SIZE = 1
|
||||||
ACCOUNT2_PAPER_PAPER = True
|
ACCOUNT2_PAPER_PAPER = True
|
||||||
ACCOUNT2_PAPER_FEED = DataFeed.IEX
|
ACCOUNT2_PAPER_FEED = DataFeed.IEX
|
||||||
|
|
||||||
|
# #SECONDARY PAPER
|
||||||
|
# ACCOUNT2_PAPER_API_KEY = 'PK0OQHZG03PUZ1SC560V'
|
||||||
|
# ACCOUNT2_PAPER_SECRET_KEY = 'cTglhm7kwRcZfFT27fQWz31sXaxadzQApFDW6Lat'
|
||||||
|
# ACCOUNT2_PAPER_MAX_BATCH_SIZE = 1
|
||||||
|
# ACCOUNT2_PAPER_PAPER = True
|
||||||
|
# ACCOUNT2_PAPER_FEED = DataFeed.IEX
|
||||||
|
|
||||||
class KW:
|
class KW:
|
||||||
activate: str = "activate"
|
activate: str = "activate"
|
||||||
dont_go: str = "dont_go"
|
dont_go: str = "dont_go"
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import uvicorn
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
import v2realbot.controller.services as cs
|
import v2realbot.controller.services as cs
|
||||||
from v2realbot.utils.ilog import get_log_window
|
from v2realbot.utils.ilog import get_log_window
|
||||||
from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest
|
from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest, AnalyzerInputs
|
||||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query
|
||||||
from fastapi.responses import FileResponse, StreamingResponse
|
from fastapi.responses import FileResponse, StreamingResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
@ -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()
|
||||||
@ -590,16 +591,38 @@ 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/{batch_id}", 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(batch_id: str):
|
def _optimize_cutoff(analyzerInputs: AnalyzerInputs):
|
||||||
try:
|
try:
|
||||||
res, stream = find_optimal_cutoff(batch_id=batch_id, steps=50, stream=True)
|
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")
|
||||||
|
|
||||||
|
#bude predelano na obecny analyzator s obecnym rozhrannim
|
||||||
|
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)
|
||||||
@ -10,6 +10,7 @@ from enum import Enum
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import v2realbot.controller.services as cs
|
import v2realbot.controller.services as cs
|
||||||
from rich import print
|
from rich import print
|
||||||
|
from v2realbot.common.model import AnalyzerInputs
|
||||||
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
|
||||||
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -211,7 +212,7 @@ def find_optimal_cutoff(runner_ids: list = None, batch_id: str = None, stream: b
|
|||||||
plt.yticks(rotation=0) # Keep y-axis labels horizontal
|
plt.yticks(rotation=0) # Keep y-axis labels horizontal
|
||||||
plt.gca().invert_yaxis()
|
plt.gca().invert_yaxis()
|
||||||
plt.gca().invert_xaxis()
|
plt.gca().invert_xaxis()
|
||||||
plt.suptitle("Total Profit for Combinations of Profit and Loss Cutoffs", fontsize=16)
|
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.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.xlabel("Loss Cutoff")
|
||||||
plt.ylabel("Profit Cutoff")
|
plt.ylabel("Profit Cutoff")
|
||||||
@ -235,6 +236,7 @@ if __name__ == '__main__':
|
|||||||
# id_list = ["e8938b2e-8462-441a-8a82-d823c6a025cb"]
|
# id_list = ["e8938b2e-8462-441a-8a82-d823c6a025cb"]
|
||||||
# generate_trading_report_image(runner_ids=id_list)
|
# generate_trading_report_image(runner_ids=id_list)
|
||||||
batch_id = "c76b4414"
|
batch_id = "c76b4414"
|
||||||
|
vstup = AnalyzerInputs(**params)
|
||||||
res, val = find_optimal_cutoff(batch_id=batch_id, file="optimal_cutoff_vectorized.png",steps=20)
|
res, val = find_optimal_cutoff(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")
|
#res, val = find_optimal_cutoff(batch_id=batch_id, rem_outliers=True, file="optimal_cutoff_vectorized_nooutliers.png")
|
||||||
|
|
||||||
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
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<!-- <script src="https://code.jquery.com/jquery-3.5.1.js"></script> -->
|
<!-- <script src="https://code.jquery.com/jquery-3.5.1.js"></script> -->
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/main.css">
|
<link rel="stylesheet" href="/static/main.css?v=1.05">
|
||||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.4.6/mousetrap.min.js"></script> -->
|
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.4.6/mousetrap.min.js"></script> -->
|
||||||
|
|
||||||
<script src="/static/js/libs/mousetrap.min.js"></script>
|
<script src="/static/js/libs/mousetrap.min.js"></script>
|
||||||
@ -309,21 +309,22 @@
|
|||||||
<div class="legend" id="legendArchive"></div>
|
<div class="legend" id="legendArchive"></div>
|
||||||
</div> -->
|
</div> -->
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<button id="button_edit_arch" class="btn btn-outline-success btn-sm">Edit(a)</button>
|
<button title="Edit selected days" id="button_edit_arch" class="btn btn-outline-success btn-sm">Edit(a)</button>
|
||||||
<button id="button_delete_arch" class="btn btn-outline-success btn-sm">Delete(d)</button>
|
<button title="Delete selected days" id="button_delete_arch" class="btn btn-outline-success btn-sm">Delete(d)</button>
|
||||||
<button id="button_delete_batch" class="btn btn-outline-success btn-sm">Delete Batch(b)</button>
|
<!-- <button id="button_delete_batch" class="btn btn-outline-success btn-sm">Delete Batch(b)</button> -->
|
||||||
<button id="button_show_arch" class="btn btn-outline-success btn-sm">Show(w)</button>
|
<button title="Show selected day on the chart" id="button_show_arch" class="btn btn-outline-success btn-sm">Show(w)</button>
|
||||||
<button id="button_refresh" class="refresh btn btn-outline-success btn-sm">Refresh</button>
|
<button id="button_refresh" class="refresh btn btn-outline-success btn-sm">Refresh</button>
|
||||||
<button id="button_compare_arch" class="refresh btn btn-outline-success btn-sm">Compare</button>
|
<button title="Compare selected days" id="button_compare_arch" class="refresh btn btn-outline-success btn-sm">Compare</button>
|
||||||
<button id="button_runagain_arch" class="refresh btn btn-outline-success btn-sm">Run Again(r)</button>
|
<button title="Run selected day" id="button_runagain_arch" class="refresh btn btn-outline-success btn-sm">Run Again(r)</button>
|
||||||
<button id="button_selpage" class="btn btn-outline-success btn-sm">Select all</button>
|
<button title="Select all days on the page" id="button_selpage" class="btn btn-outline-success btn-sm">Select all</button>
|
||||||
<button 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 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 batch creates heatmap for optimal profit/loss cutoffs" id="button_analyze" class="btn btn-outline-success btn-sm">Optimal cutoffs</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,18 +851,33 @@
|
|||||||
<!-- 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"></script>
|
<!-- <script src="/static/js/utils.js?v=1.01"></script> -->
|
||||||
<script src="/static/js/instantindicators.js"></script>
|
<!-- new util structure and exports and colors -->
|
||||||
<script src="/static/js/archivechart.js"></script>
|
<script src="/static/js/utils/utils.js?v=1.01"></script>
|
||||||
<script src="/static/js/archivetables.js"></script>
|
<script src="/static/js/utils/exports.js?v=1.01"></script>
|
||||||
<script src="/static/js/livewebsocket.js"></script>
|
<script src="/static/js/utils/colors.js?v=1.01"></script>
|
||||||
<script src="/static/js/realtimechart.js"></script>
|
|
||||||
<script src="/static/js/mytables.js"></script>
|
|
||||||
<script src="/static/js/testlist.js"></script>
|
<script src="/static/js/instantindicators.js?v=1.01"></script>
|
||||||
<script src="/static/js/configform.js"></script>
|
<script src="/static/js/archivechart.js?v=1.03"></script>
|
||||||
<!-- <script src="/static/js/dynamicbuttons.js"></script> -->
|
|
||||||
|
<!-- <script src="/static/js/archivetables.js?v=1.05"></script> -->
|
||||||
|
<!-- archiveTables split into separate files -->
|
||||||
|
<script src="/static/js/tables/archivetable/init.js?v=1.03"></script>
|
||||||
|
<script src="/static/js/tables/archivetable/functions.js?v=1.03"></script>
|
||||||
|
<script src="/static/js/tables/archivetable/modals.js?v=1.03"></script>
|
||||||
|
<script src="/static/js/tables/archivetable/handlers.js?v=1.03"></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/mytables.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>
|
||||||
|
|
||||||
</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>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
|
|
||||||
|
$submitButton.attr('disabled', true);
|
||||||
|
$spinner.removeClass('d-none');
|
||||||
|
|
||||||
// Event listener for button
|
console.log("toto jsou transformovana data", obj)
|
||||||
$btn.on('click', function(event) {
|
var apiEndpoint = $(this).data('endpoint');
|
||||||
event.preventDefault();
|
// console.log("formdata", formData)
|
||||||
|
// API call (adjust as needed for your backend)
|
||||||
var formData = $form.serializeArray().reduce(function(obj, item) {
|
$.ajax({
|
||||||
obj[item.name] = item.value;
|
url: apiEndpoint,
|
||||||
return obj;
|
beforeSend: function (xhr) {
|
||||||
}, {});
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
$.ajax({
|
method: 'POST',
|
||||||
url: button.apiEndpoint,
|
//menime hlavicku podle toho jestli je uspesne nebo ne, abychom mohli precist chybovou hlasku
|
||||||
method: 'POST',
|
xhr: function() {
|
||||||
data: formData,
|
var xhr = new XMLHttpRequest();
|
||||||
success: function(response) {
|
xhr.onreadystatechange = function() {
|
||||||
console.log('API Call Successful:', response);
|
if (xhr.readyState === 2) { // Headers have been received
|
||||||
},
|
if (xhr.status === 200) {
|
||||||
error: function(error) {
|
xhr.responseType = "blob"; // Set responseType to 'blob' for successful image responses
|
||||||
console.error('API Call Failed:', error);
|
} 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;
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@ -302,6 +302,7 @@ $(document).ready(function () {
|
|||||||
runnerRecords.ajax.reload();
|
runnerRecords.ajax.reload();
|
||||||
stratinRecords.ajax.reload();
|
stratinRecords.ajax.reload();
|
||||||
archiveRecords.ajax.reload();
|
archiveRecords.ajax.reload();
|
||||||
|
disable_arch_buttons();
|
||||||
})
|
})
|
||||||
|
|
||||||
//button copy
|
//button copy
|
||||||
@ -927,11 +928,23 @@ var runnerRecords =
|
|||||||
],
|
],
|
||||||
paging: false,
|
paging: false,
|
||||||
processing: false,
|
processing: false,
|
||||||
columnDefs: [ {
|
columnDefs: [
|
||||||
targets: [0,1],
|
{
|
||||||
|
targets: [0],
|
||||||
render: function ( data, type, row ) {
|
render: function ( data, type, row ) {
|
||||||
return '<div class="tdnowrap" title="'+data+'">'+data+'</i>'
|
return '<div class="tdnowrap" title="'+data+'">'+data+'</i>'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
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: [2],
|
targets: [2],
|
||||||
|
|||||||
550
v2realbot/static/js/tables/archivetable/functions.js
Normal file
550
v2realbot/static/js/tables/archivetable/functions.js
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
//funkce a promenne specificke pro archiveTable
|
||||||
|
//usually work with archiveRecords
|
||||||
|
|
||||||
|
//ARCHIVE TABLES
|
||||||
|
let editor_diff_arch1
|
||||||
|
let editor_diff_arch2
|
||||||
|
var archData = null
|
||||||
|
var batchHeaders = []
|
||||||
|
|
||||||
|
function refresh_arch_and_callback(row, callback) {
|
||||||
|
//console.log("entering refresh")
|
||||||
|
var request = $.ajax({
|
||||||
|
url: "/archived_runners/"+row.id,
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
success:function(data){
|
||||||
|
//console.log("fetched data ok")
|
||||||
|
//console.log(JSON.stringify(data,null,2));
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handling the responses of both requests
|
||||||
|
$.when(request).then(function(response) {
|
||||||
|
// Both requests have completed successfully
|
||||||
|
//console.log("Result from request:", response);
|
||||||
|
//console.log("Response received. calling callback")
|
||||||
|
//call callback function
|
||||||
|
callback(response)
|
||||||
|
|
||||||
|
}, function(error) {
|
||||||
|
// Handle errors from either request here
|
||||||
|
// Example:
|
||||||
|
console.error("Error from first request:", error);
|
||||||
|
console.log("requesting id error")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//triggers charting
|
||||||
|
function get_detail_and_chart(row) {
|
||||||
|
$.ajax({
|
||||||
|
url:"/archived_runners_detail/"+row.id,
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
success:function(data){
|
||||||
|
$('#button_show_arch').attr('disabled',false);
|
||||||
|
$('#chartContainerInner').addClass("show");
|
||||||
|
//$("#lines").html("<pre>"+JSON.stringify(row.stratvars,null,2)+"</pre>")
|
||||||
|
|
||||||
|
//$('#chartArchive').append(JSON.stringify(data,null,2));
|
||||||
|
//console.log(JSON.stringify(data,null,2));
|
||||||
|
//if lower res is required call prepare_data otherwise call chart_archived_run()
|
||||||
|
//get other base resolutions
|
||||||
|
prepare_data(row, 1, "Min", data)
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
//console.log(JSON.stringify(xhr));
|
||||||
|
$('#button_show_arch').attr('disabled',false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//rerun stratin
|
||||||
|
function run_day_again() {
|
||||||
|
row = archiveRecords.row('.selected').data();
|
||||||
|
$('#button_runagain_arch').attr('disabled',true);
|
||||||
|
|
||||||
|
var record1 = new Object()
|
||||||
|
//console.log(JSON.stringify(rows))
|
||||||
|
|
||||||
|
//record1 = JSON.parse(rows[0].strat_json)
|
||||||
|
//record1.json = rows[0].json
|
||||||
|
|
||||||
|
//TBD mozna zkopirovat jen urcite?
|
||||||
|
|
||||||
|
//getting required data (detail of the archived runner + stratin to be run)
|
||||||
|
var request1 = $.ajax({
|
||||||
|
url: "/archived_runners/"+row.id,
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
success:function(data){
|
||||||
|
//console.log("fetched data ok")
|
||||||
|
//console.log(JSON.stringify(data,null,2));
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//nalaodovat data pro strategii
|
||||||
|
var request2 = $.ajax({
|
||||||
|
url: "/stratins/"+row.strat_id,
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
success:function(data){
|
||||||
|
//console.log("fetched data ok")
|
||||||
|
//console.log(JSON.stringify(data,null,2));
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Handling the responses of both requests
|
||||||
|
$.when(request1, request2).then(function(response1, response2) {
|
||||||
|
// Both requests have completed successfully
|
||||||
|
var result1 = response1[0];
|
||||||
|
var result2 = response2[0];
|
||||||
|
|
||||||
|
//console.log("Result from first request:", result1);
|
||||||
|
//console.log("Result from second request:", result2);
|
||||||
|
|
||||||
|
//console.log("calling compare")
|
||||||
|
rerun_strategy(result1, result2)
|
||||||
|
// Perform your action with the results from both requests
|
||||||
|
// Example:
|
||||||
|
|
||||||
|
}, function(error1, error2) {
|
||||||
|
// Handle errors from either request here
|
||||||
|
// Example:
|
||||||
|
console.error("Error from first request:", error1);
|
||||||
|
console.error("Error from second request:", error2);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function rerun_strategy(archRunner, stratData) {
|
||||||
|
record1 = archRunner
|
||||||
|
//console.log(record1)
|
||||||
|
|
||||||
|
//smazeneme nepotrebne a pridame potrebne
|
||||||
|
//do budoucna predelat na vytvoreni noveho objektu
|
||||||
|
//nebudeme muset odstanovat pri kazdem pridani noveho atributu v budoucnu
|
||||||
|
delete record1["end_positions"];
|
||||||
|
delete record1["end_positions_avgp"];
|
||||||
|
delete record1["profit"];
|
||||||
|
delete record1["trade_count"];
|
||||||
|
delete record1["stratvars_toml"];
|
||||||
|
delete record1["started"];
|
||||||
|
delete record1["stopped"];
|
||||||
|
delete record1["metrics"];
|
||||||
|
delete record1["settings"];
|
||||||
|
delete record1["stratvars"];
|
||||||
|
|
||||||
|
record1.note = "RERUN " + record1.note
|
||||||
|
|
||||||
|
if (record1.bt_from == "") {delete record1["bt_from"];}
|
||||||
|
if (record1.bt_to == "") {delete record1["bt_to"];}
|
||||||
|
|
||||||
|
//mazeme, pouze rerunujeme single
|
||||||
|
delete record1["test_batch_id"];
|
||||||
|
delete record1["batch_id"];
|
||||||
|
|
||||||
|
const rec = new Object()
|
||||||
|
rec.id2 = parseInt(stratData.id2);
|
||||||
|
rec.name = stratData.name;
|
||||||
|
rec.symbol = stratData.symbol;
|
||||||
|
rec.class_name = stratData.class_name;
|
||||||
|
rec.script = stratData.script;
|
||||||
|
rec.open_rush = stratData.open_rush;
|
||||||
|
rec.close_rush = stratData.close_rush;
|
||||||
|
rec.stratvars_conf = stratData.stratvars_conf;
|
||||||
|
rec.add_data_conf = stratData.add_data_conf;
|
||||||
|
rec.note = stratData.note;
|
||||||
|
rec.history = "";
|
||||||
|
strat_json = JSON.stringify(rec, null, 2);
|
||||||
|
record1.strat_json = strat_json
|
||||||
|
|
||||||
|
//zkopirujeme strat_id do id a smazeme strat_id
|
||||||
|
record1.id = record1.strat_id
|
||||||
|
delete record1["strat_id"];
|
||||||
|
|
||||||
|
//console.log("record1 pred odeslanim", record1)
|
||||||
|
jsonString = JSON.stringify(record1);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url:"/stratins/"+record1.id+"/run",
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"PUT",
|
||||||
|
contentType: "application/json",
|
||||||
|
data: jsonString,
|
||||||
|
success:function(data){
|
||||||
|
$('#button_runagain_arch').attr('disabled',false);
|
||||||
|
setTimeout(function () {
|
||||||
|
runnerRecords.ajax.reload();
|
||||||
|
stratinRecords.ajax.reload();
|
||||||
|
}, 1500);
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
//console.log(JSON.stringify(xhr));
|
||||||
|
$('#button_runagain_arch').attr('disabled',false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function expand_collapse_rows(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
var headerRow = $(this);
|
||||||
|
var name = headerRow.data('name');
|
||||||
|
var collapsed = headerRow.hasClass('collapsed');
|
||||||
|
|
||||||
|
// Toggle the expand icon name
|
||||||
|
var expandIcon = headerRow.find('.expand-icon');
|
||||||
|
if (collapsed) {
|
||||||
|
expandIcon.text('expand_less');
|
||||||
|
} else {
|
||||||
|
expandIcon.text('expand_more');
|
||||||
|
}
|
||||||
|
|
||||||
|
headerRow.toggleClass('collapsed');
|
||||||
|
|
||||||
|
archiveRecords.rows().every(function () {
|
||||||
|
var row = $(this.node());
|
||||||
|
var rowGroup = row.attr('data-group-name');
|
||||||
|
if (rowGroup == name) {
|
||||||
|
row.toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the state
|
||||||
|
if (collapsed) {
|
||||||
|
localStorage.setItem('dt-group-state-' + name, 'expanded');
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('dt-group-state-' + name, 'collapsed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_batch(event){
|
||||||
|
event.preventDefault();
|
||||||
|
batch_id = $('#batch_id_del').val();
|
||||||
|
$('#deletebatch').attr('disabled', 'disabled');
|
||||||
|
$.ajax({
|
||||||
|
url:"/archived_runners/batch/"+batch_id,
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"DELETE",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
data: JSON.stringify(batch_id),
|
||||||
|
success:function(data){
|
||||||
|
$('#delFormBatch')[0].reset();
|
||||||
|
window.$('#delModalBatch').modal('hide');
|
||||||
|
$('#deletebatch').attr('disabled', false);
|
||||||
|
$('#button_delete_batch').attr('disabled', false);
|
||||||
|
//console.log(data)
|
||||||
|
archiveRecords.ajax.reload();
|
||||||
|
disable_arch_buttons();
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
$('#deletebatch').attr('disabled', false);
|
||||||
|
$('#button_delete_batch').attr('disabled', false);
|
||||||
|
archiveRecords.ajax.reload();
|
||||||
|
disable_arch_buttons();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function analyze_optimal_cutoff(batch_id = null) {
|
||||||
|
//definice parametru
|
||||||
|
param_obj = { rem_outliers:false, steps:50}
|
||||||
|
obj = {function: "analyze_optimal_cutoff", runner_ids:[], batch_id:null, params:param_obj}
|
||||||
|
//bereme bud selected runners
|
||||||
|
if (!batch_id) {
|
||||||
|
rows = archiveRecords.rows('.selected').data();
|
||||||
|
if (rows == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$('#button_analyze').attr('disabled','disabled');
|
||||||
|
// Extract IDs from each row's data and store them in an array
|
||||||
|
obj.runner_ids = [];
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
obj.runner_ids.push(rows[i].id); // Assuming 'id' is the property that contains the row ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//nebo batch
|
||||||
|
else {
|
||||||
|
obj.batch_id = batch_id
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log("analyze cutoff objekt", obj)
|
||||||
|
// batch_id: Optional[str] = None
|
||||||
|
// runner_ids: Optional[List[UUID]] = None
|
||||||
|
// #additional parameter
|
||||||
|
// params: Optional[dict] = None
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url:"/batches/optimizecutoff/",
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"POST",
|
||||||
|
xhrFields: {
|
||||||
|
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",
|
||||||
|
processData: false,
|
||||||
|
data: JSON.stringify(obj),
|
||||||
|
success:function(blob){
|
||||||
|
var url = window.URL || window.webkitURL;
|
||||||
|
console.log("vraceny obraz", blob)
|
||||||
|
console.log("url",url.createObjectURL(blob))
|
||||||
|
display_image(url.createObjectURL(blob))
|
||||||
|
if (!batch_id) {
|
||||||
|
$('#button_analyze').attr('disabled',false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.log("proc to skace do erroru?")
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
$('#button_analyze').attr('disabled',false);
|
||||||
|
if (!batch_id) {
|
||||||
|
$('#button_analyze').attr('disabled',false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//pomocna funkce, ktera vraci filtrovane radky tabulky (bud oznacene nebo batchove)
|
||||||
|
function get_selected_or_batch(batch_id = null) {
|
||||||
|
if (!batch_id) {
|
||||||
|
rows = archiveRecords.rows('.selected');
|
||||||
|
} else {
|
||||||
|
rows = archiveRecords.rows( function ( idx, data, node ) {
|
||||||
|
return data.batch_id == batch_id;
|
||||||
|
});
|
||||||
|
//console.log("batch rows",batch_id, rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//prepares export data, either for selected rows or based on batch_id
|
||||||
|
function prepare_export(batch_id = null) {
|
||||||
|
rows = get_selected_or_batch(batch_id)
|
||||||
|
var trdList = []
|
||||||
|
if(rows.data().length > 0 ) {
|
||||||
|
//console.log(rows.data())
|
||||||
|
// Loop through the selected rows and display an alert with each row's ID
|
||||||
|
rows.every(function (rowIdx, tableLoop, rowLoop ) {
|
||||||
|
var data = this.data()
|
||||||
|
data.metrics.prescr_trades.forEach((trade) => {
|
||||||
|
new_obj = {}
|
||||||
|
new_obj["entry_time"] = (trade.entry_time) ? new Date(trade.entry_time * 1000) : null
|
||||||
|
new_obj["entry_time"] = (new_obj["entry_time"]) ? new_obj["entry_time"].toLocaleString('cs-CZ', {
|
||||||
|
timeZone: 'America/New_York',
|
||||||
|
}) : null
|
||||||
|
new_obj["exit_time"] = (trade.exit_time) ? new Date(trade.exit_time * 1000):null
|
||||||
|
new_obj["exit_time"] = (new_obj["exit_time"]) ? new_obj["exit_time"].toLocaleString('cs-CZ', {
|
||||||
|
timeZone: 'America/New_York',
|
||||||
|
}) : null
|
||||||
|
new_obj["direction"] = trade.direction
|
||||||
|
new_obj["profit"] = trade.profit
|
||||||
|
new_obj["rel_profit"] = trade.rel_profit
|
||||||
|
trdList.push(new_obj)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return trdList
|
||||||
|
}
|
||||||
|
|
||||||
|
function download_exported_data(type, batch_id = null) {
|
||||||
|
filename = batch_id ? "batch"+batch_id+"-trades" : "trades"
|
||||||
|
if (type == "xml") {
|
||||||
|
response_type = "application/xml"
|
||||||
|
output = convertToXml(prepare_export(batch_id))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response_type = "text/csv"
|
||||||
|
output = convertToCsv(prepare_export(batch_id))
|
||||||
|
}
|
||||||
|
console.log(output)
|
||||||
|
downloadFile(response_type,type, filename, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
function display_image(imageUrl) {
|
||||||
|
// Attempt to load the image
|
||||||
|
var img = new Image();
|
||||||
|
img.src = imageUrl;
|
||||||
|
img.onload = function() {
|
||||||
|
// If the image loads successfully, display it
|
||||||
|
$('#previewImg').attr('src', imageUrl);
|
||||||
|
//$('#imagePreview').show();
|
||||||
|
window.$('#imageModal').modal('show');
|
||||||
|
};
|
||||||
|
img.onerror = function(e) {
|
||||||
|
console.log("Image load error", e);
|
||||||
|
console.log("Image object:", img);
|
||||||
|
console.log("no image available")
|
||||||
|
// If the image fails to load, do nothing
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function display_batch_report(batch_id) {
|
||||||
|
//var imageUrl = '/media/report_'+data.id+".png"; // Replace with your logic to get image URL
|
||||||
|
var imageUrl = '/media/basic/'+batch_id+'.png'; // Replace with your logic to get image URL
|
||||||
|
//console.log(imageUrl)
|
||||||
|
display_image(imageUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh_logfile() {
|
||||||
|
$.ajax({
|
||||||
|
url:"/log?lines=30",
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
success:function(response){
|
||||||
|
if (response.lines.length == 0) {
|
||||||
|
$('#log-content').html("no records");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#log-content').html(response.lines.join('\n'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_arch_rows(ids) {
|
||||||
|
$.ajax({
|
||||||
|
url:"/archived_runners/",
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"DELETE",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
data: JSON.stringify(ids),
|
||||||
|
success:function(data){
|
||||||
|
$('#delFormArchive')[0].reset();
|
||||||
|
window.$('#delModalArchive').modal('hide');
|
||||||
|
$('#deletearchive').attr('disabled', false);
|
||||||
|
//console.log(data)
|
||||||
|
archiveRecords.ajax.reload();
|
||||||
|
disable_arch_buttons()
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
$('#deletearchive').attr('disabled', false);
|
||||||
|
//archiveRecords.ajax.reload();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractNumbersFromString(str) {
|
||||||
|
// Regular expression to match the pattern #number1/number2
|
||||||
|
const pattern = /#(\d+)\/(\d+)/;
|
||||||
|
const match = str.match(pattern);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
// Extract number1 and number2 from the match
|
||||||
|
const number1 = parseInt(match[1], 10);
|
||||||
|
const number2 = parseInt(match[2], 10);
|
||||||
|
|
||||||
|
//return { number1, number2 };
|
||||||
|
return number2;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to generate a unique key for localStorage based on batch_id
|
||||||
|
function generateStorageKey(batchId) {
|
||||||
|
return 'dt-group-state-' + batchId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function disable_arch_buttons() {
|
||||||
|
//disable buttons (enable on row selection)
|
||||||
|
$('#button_runagain_arch').attr('disabled','disabled');
|
||||||
|
$('#button_show_arch').attr('disabled','disabled');
|
||||||
|
$('#button_delete_arch').attr('disabled','disabled');
|
||||||
|
$('#button_delete_batch').attr('disabled','disabled');
|
||||||
|
$('#button_analyze').attr('disabled','disabled');
|
||||||
|
$('#button_edit_arch').attr('disabled','disabled');
|
||||||
|
$('#button_compare_arch').attr('disabled','disabled');
|
||||||
|
$('#button_report').attr('disabled','disabled');
|
||||||
|
$('#button_export_xml').attr('disabled','disabled');
|
||||||
|
$('#button_export_csv').attr('disabled','disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
function enable_arch_buttons() {
|
||||||
|
$('#button_analyze').attr('disabled',false);
|
||||||
|
$('#button_show_arch').attr('disabled',false);
|
||||||
|
$('#button_runagain_arch').attr('disabled',false);
|
||||||
|
$('#button_delete_arch').attr('disabled',false);
|
||||||
|
$('#button_delete_batch').attr('disabled',false);
|
||||||
|
$('#button_edit_arch').attr('disabled',false);
|
||||||
|
$('#button_compare_arch').attr('disabled',false);
|
||||||
|
$('#button_report').attr('disabled',false);
|
||||||
|
$('#button_export_xml').attr('disabled',false);
|
||||||
|
$('#button_export_csv').attr('disabled',false);
|
||||||
|
}
|
||||||
478
v2realbot/static/js/tables/archivetable/handlers.js
Normal file
478
v2realbot/static/js/tables/archivetable/handlers.js
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
//event handlers for archiveTables
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
initialize_archiveRecords();
|
||||||
|
archiveRecords.ajax.reload();
|
||||||
|
disable_arch_buttons();
|
||||||
|
|
||||||
|
|
||||||
|
// Use 'td:nth-child(2)' to target the second column
|
||||||
|
$('#archiveTable tbody').on('click', 'td:nth-child(2)', function () {
|
||||||
|
var data = archiveRecords.row(this).data();
|
||||||
|
//var imageUrl = '/media/report_'+data.id+".png"; // Replace with your logic to get image URL
|
||||||
|
var imageUrl = '/media/basic/'+data.id+'.png'; // Replace with your logic to get image URL
|
||||||
|
//console.log(imageUrl)
|
||||||
|
display_image(imageUrl)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use 'td:nth-child(2)' to target the second column
|
||||||
|
$('#archiveTable tbody').on('click', 'td:nth-child(18)', function () {
|
||||||
|
var data = archiveRecords.row(this).data();
|
||||||
|
if (data.batch_id) {
|
||||||
|
display_batch_report(data.batch_id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//selectable rows in archive table
|
||||||
|
$('#archiveTable tbody').on('click', 'tr[data-group-name]', function () {
|
||||||
|
if ($(this).hasClass('selected')) {
|
||||||
|
//$(this).removeClass('selected');
|
||||||
|
//aadd here condition that disable is called only when there is no other selected class on tr[data-group-name]
|
||||||
|
// Check if there are no other selected rows before disabling buttons
|
||||||
|
if ($('#archiveTable tr[data-group-name].selected').length === 1) {
|
||||||
|
disable_arch_buttons();
|
||||||
|
}
|
||||||
|
//disable_arch_buttons()
|
||||||
|
} else {
|
||||||
|
//archiveRecords.$('tr.selected').removeClass('selected');
|
||||||
|
$(this).addClass('selected');
|
||||||
|
enable_arch_buttons()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//TOOL BUTTONs on BATCH HEADER
|
||||||
|
|
||||||
|
// Event listener for click to display batch report
|
||||||
|
$('#archiveTable tbody').on('click', 'tr.group-header #batchtool_report_button', function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
// Get the parent <tr> element
|
||||||
|
var parentTr = $(this).closest('tr');
|
||||||
|
// Retrieve the 'data-name' attribute from the parent <tr>
|
||||||
|
var batch_id = parentTr.data('name');
|
||||||
|
display_batch_report(batch_id)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener for click to delete batch
|
||||||
|
$('#archiveTable tbody').on('click', 'tr.group-header #batchtool_delete_button', function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
// Get the parent <tr> element
|
||||||
|
var parentTr = $(this).closest('tr');
|
||||||
|
// Retrieve the 'data-name' attribute from the parent <tr>
|
||||||
|
var batch_id = parentTr.data('name');
|
||||||
|
$('#batch_id_del').val(batch_id);
|
||||||
|
$('#listofids').html("");
|
||||||
|
window.$('#delModalBatch').modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener for click to xml export batch
|
||||||
|
$('#archiveTable tbody').on('click', 'tr.group-header #batchtool_exportxml_button', function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
// Get the parent <tr> element
|
||||||
|
var parentTr = $(this).closest('tr');
|
||||||
|
// Retrieve the 'data-name' attribute from the parent <tr>
|
||||||
|
var batch_id = parentTr.data('name');
|
||||||
|
download_exported_data("xml", batch_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener for click to csv export batch
|
||||||
|
$('#archiveTable tbody').on('click', 'tr.group-header #batchtool_exportcsv_button', function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
// Get the parent <tr> element
|
||||||
|
var parentTr = $(this).closest('tr');
|
||||||
|
// Retrieve the 'data-name' attribute from the parent <tr>
|
||||||
|
var batch_id = parentTr.data('name');
|
||||||
|
console.log(batch_id)
|
||||||
|
download_exported_data("csv", batch_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener for optimal batch cutoff
|
||||||
|
$('#archiveTable tbody').on('click', 'tr.group-header #batchtool_cutoff_button', function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
// Get the parent <tr> element
|
||||||
|
var parentTr = $(this).closest('tr');
|
||||||
|
// Retrieve the 'data-name' attribute from the parent <tr>
|
||||||
|
var batch_id = parentTr.data('name');
|
||||||
|
console.log(batch_id)
|
||||||
|
analyze_optimal_cutoff(batch_id)
|
||||||
|
});
|
||||||
|
|
||||||
|
//TOOL BUTTONs above the TABLE - for selected days
|
||||||
|
//button export
|
||||||
|
$('#button_export_xml').click(function(event) {
|
||||||
|
download_exported_data("xml");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//button export
|
||||||
|
$('#button_export_csv').click(function(event) {
|
||||||
|
download_exported_data("csv");
|
||||||
|
});
|
||||||
|
|
||||||
|
//button select page
|
||||||
|
$('#button_selpage').click(function () {
|
||||||
|
if ($('#button_selpage').hasClass('active')) {
|
||||||
|
$('#button_selpage').removeClass('active');
|
||||||
|
archiveRecords.rows().deselect();
|
||||||
|
disable_arch_buttons();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#button_selpage').addClass('active');
|
||||||
|
archiveRecords.rows( { page: 'current' } ).select();
|
||||||
|
enable_arch_buttons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//button clear log
|
||||||
|
$('#button_clearlog').click(function () {
|
||||||
|
$('#lines').empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
//button compare arch
|
||||||
|
$('#button_compare_arch').click(function () {
|
||||||
|
if (editor_diff_arch1) {editor_diff_arch1.dispose()}
|
||||||
|
if (editor_diff_stratin1) {editor_diff_stratin1.dispose()}
|
||||||
|
if (editor_diff_arch2) {editor_diff_arch2.dispose()}
|
||||||
|
if (editor_diff_stratin2) {editor_diff_stratin2.dispose()}
|
||||||
|
window.$('#diffModal').modal('show');
|
||||||
|
rows = archiveRecords.rows('.selected').data();
|
||||||
|
|
||||||
|
id1 = rows[0].id
|
||||||
|
id2 = rows[1].id
|
||||||
|
|
||||||
|
var request1 = $.ajax({
|
||||||
|
url: "/archived_runners/"+id1,
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
success:function(data){
|
||||||
|
//console.log("first request ok")
|
||||||
|
//console.log(JSON.stringify(data,null,2));
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
console.log("first request error")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var request2 = $.ajax({
|
||||||
|
url: "/archived_runners/"+id2,
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
success:function(data){
|
||||||
|
//console.log("first request ok")
|
||||||
|
//console.log(JSON.stringify(data,null,2));
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
console.log("first request error")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handling the responses of both requests
|
||||||
|
$.when(request1, request2).then(function(response1, response2) {
|
||||||
|
// Both requests have completed successfully
|
||||||
|
var result1 = response1[0];
|
||||||
|
var result2 = response2[0];
|
||||||
|
//console.log("Result from first request:", result1);
|
||||||
|
//console.log("Result from second request:", result2);
|
||||||
|
//console.log("calling compare")
|
||||||
|
perform_compare(result1, result2)
|
||||||
|
// Perform your action with the results from both requests
|
||||||
|
// Example:
|
||||||
|
|
||||||
|
}, function(error1, error2) {
|
||||||
|
// Handle errors from either request here
|
||||||
|
// Example:
|
||||||
|
console.error("Error from first request:", error1);
|
||||||
|
console.error("Error from second request:", error2);
|
||||||
|
});
|
||||||
|
|
||||||
|
//sem vstupuji dva nove natahnute objekty
|
||||||
|
function perform_compare(data1, data2) {
|
||||||
|
|
||||||
|
var record1 = new Object()
|
||||||
|
//console.log(JSON.stringify(rows))
|
||||||
|
|
||||||
|
record1 = JSON.parse(data1.strat_json)
|
||||||
|
//record1.json = rows[0].json
|
||||||
|
//record1.id = rows[0].id;
|
||||||
|
// record1.id2 = parseInt(rows[0].id2);
|
||||||
|
//record1.name = rows[0].name;
|
||||||
|
// record1.symbol = rows[0].symbol;
|
||||||
|
// record1.class_name = rows[0].class_name;
|
||||||
|
// record1.script = rows[0].script;
|
||||||
|
// record1.open_rush = rows[0].open_rush;
|
||||||
|
// record1.close_rush = rows[0].close_rush;
|
||||||
|
//console.log(record1.stratvars_conf)
|
||||||
|
|
||||||
|
//ELEMENTS TO COMPARE
|
||||||
|
|
||||||
|
//profit sekce
|
||||||
|
//console.log(data1.metrics)
|
||||||
|
|
||||||
|
try {
|
||||||
|
record1["profit"] = JSON.parse(data1.metrics.profit)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
//record1.stratvars_conf = TOML.parse(record1.stratvars_conf);
|
||||||
|
//record1.add_data_conf = TOML.parse(record1.add_data_conf);
|
||||||
|
// record1.note = rows[0].note;
|
||||||
|
// record1.history = "";
|
||||||
|
//jsonString1 = JSON.stringify(record1, null, 2);
|
||||||
|
|
||||||
|
var record2 = new Object()
|
||||||
|
record2 = JSON.parse(data2.strat_json)
|
||||||
|
|
||||||
|
// record2.id = rows[1].id;
|
||||||
|
// record2.id2 = parseInt(rows[1].id2);
|
||||||
|
//record2.name = rows[1].name;
|
||||||
|
// record2.symbol = rows[1].symbol;
|
||||||
|
// record2.class_name = rows[1].class_name;
|
||||||
|
// record2.script = rows[1].script;
|
||||||
|
// record2.open_rush = rows[1].open_rush;
|
||||||
|
// record2.close_rush = rows[1].close_rush;
|
||||||
|
|
||||||
|
//ELEMENTS TO COMPARE
|
||||||
|
//console.log(data2.metrics)
|
||||||
|
|
||||||
|
try {
|
||||||
|
record2["profit"] = JSON.parse(data2.metrics.profit)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e.message)
|
||||||
|
}
|
||||||
|
//record2.stratvars_conf = TOML.parse(record2.stratvars_conf);
|
||||||
|
//record2.add_data_conf = TOML.parse(record2.add_data_conf);
|
||||||
|
// record2.note = rows[1].note;
|
||||||
|
// record2.history = "";
|
||||||
|
//jsonString2 = JSON.stringify(record2, null, 2);
|
||||||
|
|
||||||
|
$('#diff_first').text(record1.name);
|
||||||
|
$('#diff_second').text(record2.name);
|
||||||
|
$('#diff_first_id').text(data1.id);
|
||||||
|
$('#diff_second_id').text(data2.id);
|
||||||
|
|
||||||
|
//monaco
|
||||||
|
require(["vs/editor/editor.main"], () => {
|
||||||
|
editor_diff_arch1 = monaco.editor.createDiffEditor(document.getElementById('diff_content1'),
|
||||||
|
{
|
||||||
|
language: 'toml',
|
||||||
|
theme: 'tomlTheme-dark',
|
||||||
|
originalEditable: false,
|
||||||
|
automaticLayout: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log(record1.stratvars_conf)
|
||||||
|
console.log(record2.stratvars_conf)
|
||||||
|
editor_diff_arch1.setModel({
|
||||||
|
original: monaco.editor.createModel(record1.stratvars_conf, 'toml'),
|
||||||
|
modified: monaco.editor.createModel(record2.stratvars_conf, 'toml'),
|
||||||
|
});
|
||||||
|
editor_diff_arch2 = monaco.editor.createDiffEditor(document.getElementById('diff_content2'),
|
||||||
|
{
|
||||||
|
language: 'toml',
|
||||||
|
theme: 'tomlTheme-dark',
|
||||||
|
originalEditable: false,
|
||||||
|
automaticLayout: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
editor_diff_arch2.setModel({
|
||||||
|
original: monaco.editor.createModel(record1.add_data_conf, 'toml'),
|
||||||
|
modified: monaco.editor.createModel(record2.add_data_conf, 'toml'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// var delta = compareObjects(record1, record2)
|
||||||
|
// const htmlMarkup2 = `<pre>{\n${generateHTML(record2, delta)}}\n</pre>`;
|
||||||
|
// document.getElementById('second').innerHTML = htmlMarkup2;
|
||||||
|
|
||||||
|
// const htmlMarkup1 = `<pre>{\n${generateHTML(record1, delta)}}\n</pre>`;
|
||||||
|
// document.getElementById('first').innerHTML = htmlMarkup1;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
//$('#button_compare').attr('disabled','disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//generate batch optimization cutoff (predelat na button pro obecne analyzy batche)
|
||||||
|
$('#button_analyze').click(function () {
|
||||||
|
analyze_optimal_cutoff();
|
||||||
|
});
|
||||||
|
|
||||||
|
//generate report button
|
||||||
|
$('#button_report').click(function () {
|
||||||
|
rows = archiveRecords.rows('.selected');
|
||||||
|
if (rows == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$('#button_report').attr('disabled','disabled');
|
||||||
|
runnerIds = []
|
||||||
|
if(rows.data().length > 0 ) {
|
||||||
|
// Loop through the selected rows and display an alert with each row's ID
|
||||||
|
rows.every(function (rowIdx, tableLoop, rowLoop ) {
|
||||||
|
var data = this.data()
|
||||||
|
runnerIds.push(data.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url:"/archived_runners/generatereportimage",
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"POST",
|
||||||
|
xhrFields: {
|
||||||
|
responseType: 'blob'
|
||||||
|
},
|
||||||
|
contentType: "application/json",
|
||||||
|
processData: false,
|
||||||
|
data: JSON.stringify(runnerIds),
|
||||||
|
success:function(blob){
|
||||||
|
var url = window.URL || window.webkitURL;
|
||||||
|
console.log("vraceny obraz", blob)
|
||||||
|
console.log("url",url.createObjectURL(blob))
|
||||||
|
display_image(url.createObjectURL(blob))
|
||||||
|
$('#button_report').attr('disabled',false);
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.log("proc to skace do erroru?")
|
||||||
|
//window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
$('#button_report').attr('disabled',false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
//button to query log
|
||||||
|
$('#logRefreshButton').click(function () {
|
||||||
|
refresh_logfile()
|
||||||
|
});
|
||||||
|
|
||||||
|
//button to open log modal
|
||||||
|
$('#button_show_log').click(function () {
|
||||||
|
window.$('#logModal').modal('show');
|
||||||
|
refresh_logfile()
|
||||||
|
});
|
||||||
|
|
||||||
|
//delete batch button - open modal - DECOMISS - dostupne jen na batche
|
||||||
|
// $('#button_delete_batch').click(function () {
|
||||||
|
// row = archiveRecords.row('.selected').data();
|
||||||
|
// if (row == undefined || row.batch_id == undefined) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// $('#batch_id_del').val(row.batch_id);
|
||||||
|
|
||||||
|
// rows = archiveRecords.rows('.selected');
|
||||||
|
// if (rows == undefined) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// $('#listofids').html("");
|
||||||
|
// window.$('#delModalBatch').modal('show');
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
//delete batch submit modal
|
||||||
|
$("#delModalBatch").on('submit','#delFormBatch', delete_batch);
|
||||||
|
|
||||||
|
//delete arch button - open modal
|
||||||
|
$('#button_delete_arch').click(function () {
|
||||||
|
rows = archiveRecords.rows('.selected');
|
||||||
|
if (rows == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$('#listofids').html("");
|
||||||
|
|
||||||
|
if(rows.data().length > 0 ) {
|
||||||
|
ids_to_del = ""
|
||||||
|
// Loop through the selected rows and display an alert with each row's ID
|
||||||
|
rows.every(function (rowIdx, tableLoop, rowLoop ) {
|
||||||
|
var data = this.data()
|
||||||
|
ids_to_del = ids_to_del + data.id + "<br>"
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#listofids').html(ids_to_del);
|
||||||
|
window.$('#delModalArchive').modal('show');
|
||||||
|
//$('#delidarchive').val(row.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//edit button
|
||||||
|
$('#button_edit_arch').click(function () {
|
||||||
|
row = archiveRecords.row('.selected').data();
|
||||||
|
if (row == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_arch_and_callback(row, display_edit_modal)
|
||||||
|
|
||||||
|
function display_edit_modal(row) {
|
||||||
|
window.$('#editModalArchive').modal('show');
|
||||||
|
$('#editidarchive').val(row.id);
|
||||||
|
$('#editnote').val(row.note);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
metrics = JSON.parse(row.metrics)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
metrics = row.metrics
|
||||||
|
}
|
||||||
|
$('#metrics').val(JSON.stringify(metrics,null,2));
|
||||||
|
//$('#metrics').val(TOML.parse(row.metrics));
|
||||||
|
if (row.stratvars_toml) {
|
||||||
|
$('#editstratvars').val(row.stratvars_toml);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$('#editstratvars').val(JSON.stringify(row.stratvars,null,2));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$('#editstratjson').val(row.strat_json);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//show button
|
||||||
|
$('#button_show_arch').click(function () {
|
||||||
|
row = archiveRecords.row('.selected').data();
|
||||||
|
if (row == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
refresh_arch_and_callback(row, get_detail_and_chart)
|
||||||
|
});
|
||||||
|
|
||||||
|
//run again button
|
||||||
|
$('#button_runagain_arch').click(run_day_again)
|
||||||
|
|
||||||
|
//workaround pro spatne oznacovani selectu i pro group-headery
|
||||||
|
// $('#archiveTable tbody').on('click', 'tr.group-header', function(event) {
|
||||||
|
// var $row = $(this);
|
||||||
|
|
||||||
|
// // Schedule the class removal/addition for the next event loop
|
||||||
|
// setTimeout(function() {
|
||||||
|
// if ($row.hasClass("selected")) {
|
||||||
|
// console.log("Header selected, removing selection");
|
||||||
|
// $row.removeClass("selected");
|
||||||
|
// }
|
||||||
|
// }, 0);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Expand/Collapse functionality
|
||||||
|
$('#archiveTable tbody').on('click', 'tr.group-header', expand_collapse_rows);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
412
v2realbot/static/js/tables/archivetable/init.js
Normal file
412
v2realbot/static/js/tables/archivetable/init.js
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
var archiveRecords = null
|
||||||
|
|
||||||
|
//ekvivalent to ready
|
||||||
|
function initialize_archiveRecords() {
|
||||||
|
|
||||||
|
//archive table
|
||||||
|
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)) {
|
||||||
|
//return local time only
|
||||||
|
return '<div title="'+tit+'">'+ 'dnes ' + format_date(data,false,true)+'</div>'
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//return local datetime
|
||||||
|
return '<div title="'+tit+'">'+ format_date(data,false,false)+'</div>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [6],
|
||||||
|
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)) {
|
||||||
|
//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)
|
||||||
|
//market datetime
|
||||||
|
return data ? format_date(data, true) : data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 {
|
||||||
|
profit = existingBatch.profit
|
||||||
|
itemCount = existingBatch.itemCount
|
||||||
|
period = existingBatch.period
|
||||||
|
started = existingBatch.started
|
||||||
|
stratinId = existingBatch.stratinId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//zaroven nastavime u vsech childu
|
||||||
|
|
||||||
|
// Construct the GROUP HEADER - sem pripadna tlačítka atp.
|
||||||
|
//var groupHeaderContent = '<strong>' + (group ? 'Batch ID: ' + group : 'No Batch') + '</strong>';
|
||||||
|
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>'
|
||||||
|
|
||||||
|
//final closure
|
||||||
|
tools += '</span>'
|
||||||
|
icon_color = getColorForId(stratinId)
|
||||||
|
profit_icon_color = (profit>0) ? "#4f8966" : "#bb2f5e" //"#d42962"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//def color for no batch - semi transparent
|
||||||
|
icon_color = "#ced4da17"
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
//var groupHeaderContent = '<span class="batchheader-batch-id">'+(group ? '<span class="color-tag" style="background-color:' + getColorForId(stratinId) + ';"></span>Batch ID: ' + group: 'No Batch')+'</span>';
|
||||||
|
var groupHeaderContent = '<span class="batchheader-batch-id">'+ icon + (group ? 'Batch ID: ' + group: 'No Batch')+'</span>';
|
||||||
|
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>' : '');
|
||||||
|
groupHeaderContent += group ? tools : ""
|
||||||
|
return $('<tr/>')
|
||||||
|
.append('<td colspan="18">' + groupHeaderContent + '</td>')
|
||||||
|
.attr('data-name', groupId)
|
||||||
|
.addClass('group-header')
|
||||||
|
.addClass(state);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drawCallback: function (settings) {
|
||||||
|
//console.log("drawcallback", configData)
|
||||||
|
setTimeout(function(){
|
||||||
|
|
||||||
|
//populate all tool buttons on batch header
|
||||||
|
// Loop over all divs with the class 'batch-buttons-container'
|
||||||
|
if (configData["dynamic_buttons"]) {
|
||||||
|
//console.log("jsme tu po cekani")
|
||||||
|
//console.log("pred loopem")
|
||||||
|
$('.batch_buttons_container').each((index, element) => {
|
||||||
|
//console.log("jsme uvnitr foreach");
|
||||||
|
idecko = $(element).attr('id')
|
||||||
|
//console.log("idecko", idecko)
|
||||||
|
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');
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
55
v2realbot/static/js/tables/archivetable/modals.js
Normal file
55
v2realbot/static/js/tables/archivetable/modals.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//mozna dat do dokument ready a rozdelit na handlers a funkce
|
||||||
|
|
||||||
|
|
||||||
|
//edit modal
|
||||||
|
$("#editModalArchive").on('submit','#editFormArchive', function(event){
|
||||||
|
event.preventDefault();
|
||||||
|
$('#editarchive').attr('disabled','disabled');
|
||||||
|
trow = archiveRecords.row('.selected').data();
|
||||||
|
note = $('#editnote').val()
|
||||||
|
var formData = $(this).serializeJSON();
|
||||||
|
row = {}
|
||||||
|
row["id"] = trow.id
|
||||||
|
row["note"] = note
|
||||||
|
jsonString = JSON.stringify(row);
|
||||||
|
//console.log("pred odeslanim json string", jsonString)
|
||||||
|
$.ajax({
|
||||||
|
url:"/archived_runners/"+trow.id,
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('X-API-Key',
|
||||||
|
API_KEY); },
|
||||||
|
method:"PATCH",
|
||||||
|
contentType: "application/json",
|
||||||
|
// dataType: "json",
|
||||||
|
data: jsonString,
|
||||||
|
success:function(data){
|
||||||
|
$('#editFormArchive')[0].reset();
|
||||||
|
window.$('#editModalArchive').modal('hide');
|
||||||
|
$('#editarchive').attr('disabled', false);
|
||||||
|
archiveRecords.ajax.reload();
|
||||||
|
disable_arch_buttons();
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var err = eval("(" + xhr.responseText + ")");
|
||||||
|
window.alert(JSON.stringify(xhr));
|
||||||
|
console.log(JSON.stringify(xhr));
|
||||||
|
$('#editarchive').attr('disabled', false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
//delete modal
|
||||||
|
$("#delModalArchive").on('submit','#delFormArchive', function(event){
|
||||||
|
event.preventDefault();
|
||||||
|
$('#deletearchive').attr('disabled','disabled');
|
||||||
|
//rows = archiveRecords.rows('.selected');
|
||||||
|
if(rows.data().length > 0 ) {
|
||||||
|
runnerIds = []
|
||||||
|
// Loop through the selected rows and display an alert with each row's ID
|
||||||
|
rows.every(function (rowIdx, tableLoop, rowLoop ) {
|
||||||
|
var data = this.data()
|
||||||
|
runnerIds.push(data.id);
|
||||||
|
});
|
||||||
|
delete_arch_rows(runnerIds)
|
||||||
|
}
|
||||||
|
});
|
||||||
0
v2realbot/static/js/utils/colors.js
Normal file
0
v2realbot/static/js/utils/colors.js
Normal file
41
v2realbot/static/js/utils/exports.js
Normal file
41
v2realbot/static/js/utils/exports.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Function to convert a JavaScript object to CSV
|
||||||
|
function convertToCsv(data) {
|
||||||
|
var csv = '';
|
||||||
|
// Get the headers
|
||||||
|
var headers = Object.keys(data[0]);
|
||||||
|
csv += headers.join(',') + '\n';
|
||||||
|
|
||||||
|
// Iterate over the data
|
||||||
|
data.forEach(function (item) {
|
||||||
|
var row = headers.map(function (header) {
|
||||||
|
return item[header];
|
||||||
|
});
|
||||||
|
csv += row.join(',') + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
return csv;
|
||||||
|
}
|
||||||
|
|
||||||
|
//type ("text/csv","application/xml"), filetype (csv), filename
|
||||||
|
function downloadFile(type, filetype, filename, content) {
|
||||||
|
var blob = new Blob([content], { type: type });
|
||||||
|
var url = window.URL.createObjectURL(blob);
|
||||||
|
var link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename +"."+filetype;
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to convert a JavaScript object to XML
|
||||||
|
function convertToXml(data) {
|
||||||
|
var xml = '<?xml version="1.0" encoding="UTF-8"?>\n<trades>\n';
|
||||||
|
data.forEach(function (item) {
|
||||||
|
xml += ' <trade>\n';
|
||||||
|
Object.keys(item).forEach(function (key) {
|
||||||
|
xml += ' <' + key + '>' + item[key] + '</' + key + '>\n';
|
||||||
|
});
|
||||||
|
xml += ' </trade>\n';
|
||||||
|
});
|
||||||
|
xml += '</trades>';
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
//general util functions all accross
|
||||||
|
|
||||||
|
|
||||||
API_KEY = localStorage.getItem("api-key")
|
API_KEY = localStorage.getItem("api-key")
|
||||||
var chart = null
|
var chart = null
|
||||||
@ -20,13 +20,24 @@
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expand-icon {
|
||||||
|
margin-right: 3px;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--bs-gray-400);
|
||||||
|
border-radius: 4px;
|
||||||
|
/* padding: 2px; */
|
||||||
|
}
|
||||||
|
|
||||||
.tool-icon {
|
.tool-icon {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
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 {
|
||||||
@ -34,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;
|
||||||
@ -254,6 +328,15 @@ table.dataTable thead>tr>th.sorting_asc:before, table.dataTable thead>tr>th.sort
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
/* Base style for color-tag */
|
||||||
|
|
||||||
|
/* Additional class to change the triangle direction when the row is expanded */
|
||||||
|
.collapsed .color-tag {
|
||||||
|
border-top: none;
|
||||||
|
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;
|
||||||
@ -262,23 +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 */
|
||||||
|
.batchtool {
|
||||||
|
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 immediately when hovering over a .group-header row */
|
||||||
|
.group-header:hover .batchtool {
|
||||||
|
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: bold; */
|
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; */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +449,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-container {
|
.flex-container {
|
||||||
display: inline-grid;
|
/* display: inline-grid; */
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@ -409,6 +516,14 @@ html {
|
|||||||
width: 35%;
|
width: 35%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
.even {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.odd {
|
||||||
|
display: block;
|
||||||
|
} */
|
||||||
|
|
||||||
@media (min-width: 2001px) {
|
@media (min-width: 2001px) {
|
||||||
.msgContainerInner {
|
.msgContainerInner {
|
||||||
|
|||||||
Reference in New Issue
Block a user