refactor archiveRunner js files + priprava genanal
This commit is contained in:
@ -52,6 +52,14 @@ class DataTablesRequest(BaseModel):
|
||||
# return user.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):
|
||||
batch_id: Optional[str] = None
|
||||
runner_ids: Optional[List[UUID]] = None
|
||||
#additional parameter
|
||||
params: Optional[dict] = {}
|
||||
|
||||
class RunDay(BaseModel):
|
||||
"""
|
||||
Helper object for batch run - carries list of days in format required by run batch manager
|
||||
|
||||
@ -11,7 +11,7 @@ import uvicorn
|
||||
from uuid import UUID
|
||||
import v2realbot.controller.services as cs
|
||||
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.responses import FileResponse, StreamingResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@ -590,10 +590,14 @@ def _generate_report_image(runner_ids: list[UUID]):
|
||||
|
||||
#TODO toto bude zaklad pro obecnou funkci, ktera bude volat ruzne analyzy
|
||||
#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": {}}}})
|
||||
def _generate_analysis(batch_id: str):
|
||||
@app.post("/batches/optimizecutoff", dependencies=[Depends(api_key_auth)], responses={200: {"content": {"image/png": {}}}})
|
||||
def _generate_analysis(analyzerInputs: AnalyzerInputs):
|
||||
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 = 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"})
|
||||
elif res < 0:
|
||||
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error: {res}:{id}")
|
||||
|
||||
@ -10,6 +10,7 @@ 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
|
||||
@ -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.gca().invert_yaxis()
|
||||
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.xlabel("Loss Cutoff")
|
||||
plt.ylabel("Profit Cutoff")
|
||||
@ -235,6 +236,7 @@ if __name__ == '__main__':
|
||||
# id_list = ["e8938b2e-8462-441a-8a82-d823c6a025cb"]
|
||||
# generate_trading_report_image(runner_ids=id_list)
|
||||
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, rem_outliers=True, file="optimal_cutoff_vectorized_nooutliers.png")
|
||||
|
||||
|
||||
@ -309,18 +309,18 @@
|
||||
<div class="legend" id="legendArchive"></div>
|
||||
</div> -->
|
||||
<div id="controls">
|
||||
<button 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 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="Edit selected days" id="button_edit_arch" class="btn btn-outline-success btn-sm">Edit(a)</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 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_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 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 id="button_export_csv" class="btn btn-outline-success btn-sm">Export csv</button>
|
||||
<button title="Compare selected days" id="button_compare_arch" class="refresh btn btn-outline-success btn-sm">Compare</button>
|
||||
<button title="Run selected day" id="button_runagain_arch" class="refresh btn btn-outline-success btn-sm">Run Again(r)</button>
|
||||
<button title="Select all days on the page" id="button_selpage" class="btn btn-outline-success btn-sm">Select all</button>
|
||||
<button title="Export selected days to XML" id="button_export_xml" class="btn btn-outline-success btn-sm">Export xml</button>
|
||||
<button title="Export selected days to CSV" id="button_export_csv" class="btn btn-outline-success btn-sm">Export csv</button>
|
||||
<button title="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_refresh" class="btn btn-outline-success btn-sm">Refresh</button> -->
|
||||
<div id="buttons-container"></div>
|
||||
@ -853,10 +853,26 @@
|
||||
|
||||
|
||||
|
||||
<script src="/static/js/utils.js?v=1.01"></script>
|
||||
<!-- <script src="/static/js/utils.js?v=1.01"></script> -->
|
||||
<!-- new util structure and exports and colors -->
|
||||
<script src="/static/js/utils/utils.js?v=1.01"></script>
|
||||
<script src="/static/js/utils/exports.js?v=1.01"></script>
|
||||
<script src="/static/js/utils/colors.js?v=1.01"></script>
|
||||
|
||||
|
||||
<script src="/static/js/instantindicators.js?v=1.01"></script>
|
||||
<script src="/static/js/archivechart.js?v=1.03"></script>
|
||||
<script src="/static/js/archivetables.js?v=1.05"></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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -302,6 +302,7 @@ $(document).ready(function () {
|
||||
runnerRecords.ajax.reload();
|
||||
stratinRecords.ajax.reload();
|
||||
archiveRecords.ajax.reload();
|
||||
disable_arch_buttons();
|
||||
})
|
||||
|
||||
//button copy
|
||||
@ -927,12 +928,24 @@ var runnerRecords =
|
||||
],
|
||||
paging: false,
|
||||
processing: false,
|
||||
columnDefs: [ {
|
||||
targets: [0,1],
|
||||
columnDefs: [
|
||||
{
|
||||
targets: [0],
|
||||
render: function ( data, type, row ) {
|
||||
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],
|
||||
render: function ( data, type, row ) {
|
||||
|
||||
535
v2realbot/static/js/tables/archivetable/functions.js
Normal file
535
v2realbot/static/js/tables/archivetable/functions.js
Normal file
@ -0,0 +1,535 @@
|
||||
//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 = {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'
|
||||
},
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
477
v2realbot/static/js/tables/archivetable/handlers.js
Normal file
477
v2realbot/static/js/tables/archivetable/handlers.js
Normal file
@ -0,0 +1,477 @@
|
||||
//event handlers for archiveTables
|
||||
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
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);
|
||||
|
||||
})
|
||||
|
||||
381
v2realbot/static/js/tables/archivetable/init.js
Normal file
381
v2realbot/static/js/tables/archivetable/init.js
Normal file
@ -0,0 +1,381 @@
|
||||
//archive table
|
||||
var archiveRecords =
|
||||
$('#archiveTable').DataTable( {
|
||||
ajax: {
|
||||
url: '/archived_runners_p/',
|
||||
dataSrc: 'data',
|
||||
method:"POST",
|
||||
contentType: "application/json",
|
||||
// dataType: "json",
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader('X-API-Key',
|
||||
API_KEY); },
|
||||
data: function (d) {
|
||||
return JSON.stringify(d);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
//var err = eval("(" + xhr.responseText + ")");
|
||||
//window.alert(JSON.stringify(xhr));
|
||||
console.log(JSON.stringify(xhr));
|
||||
}
|
||||
},
|
||||
columns: [{ data: 'id' },
|
||||
{data: 'strat_id'},
|
||||
{data: 'name'},
|
||||
{data: 'symbol'},
|
||||
{data: 'note'},
|
||||
{data: 'started'},
|
||||
{data: 'stopped'},
|
||||
{data: 'mode'},
|
||||
{data: 'account', visible: true},
|
||||
{data: 'bt_from', visible: true},
|
||||
{data: 'bt_to', visible: true},
|
||||
{data: 'ilog_save', visible: true},
|
||||
{data: 'profit'},
|
||||
{data: 'trade_count', visible: true},
|
||||
{data: 'end_positions', visible: true},
|
||||
{data: 'end_positions_avgp', visible: true},
|
||||
{data: 'metrics', visible: true},
|
||||
{data: 'batch_id', visible: true},
|
||||
],
|
||||
paging: true,
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 1,
|
||||
render: function ( data, type, row ) {
|
||||
if (type === 'display') {
|
||||
//console.log("arch")
|
||||
var color = getColorForId(data);
|
||||
return '<div class="tdnowrap" data-bs-toggle="tooltip" data-bs-placement="top" title="'+data+'"><span class="color-tag" style="background-color:' + color + ';"></span>'+data+'</div>';
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [0,17],
|
||||
render: function ( data, type, row ) {
|
||||
if (!data) return data
|
||||
return '<div class="tdnowrap" title="'+data+'">'+data+'</i>'
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [5],
|
||||
render: function ( data, type, row ) {
|
||||
now = new Date(data)
|
||||
if (type == "sort") {
|
||||
return new Date(data).getTime();
|
||||
}
|
||||
var date = new Date(data);
|
||||
tit = date.toLocaleString('cs-CZ', {
|
||||
timeZone: 'America/New_York',
|
||||
})
|
||||
|
||||
if (isToday(now)) {
|
||||
//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>'
|
||||
//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) {
|
||||
// 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")
|
||||
var chart = null
|
||||
@ -280,6 +280,16 @@ table.dataTable thead>tr>th.sorting_asc:before, table.dataTable thead>tr>th.sort
|
||||
/* font-weight: bold; */
|
||||
}
|
||||
|
||||
/* Hide all .batchtool_ elements by default */
|
||||
.batchtool {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show .batchtool elements when hovering over a .group-header row */
|
||||
.group-header:hover .batchtool {
|
||||
display: inline-block; /* or whatever display type suits your layout */
|
||||
}
|
||||
|
||||
.group-header .batchheader-profit-info {
|
||||
color: #3e999e; /* Highlight profit info */
|
||||
/* font-weight: bold; */
|
||||
|
||||
Reference in New Issue
Block a user