8 Commits

15 changed files with 1600 additions and 1394 deletions

View File

@ -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

View File

@ -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}")

View File

@ -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")

View File

@ -57,7 +57,7 @@
<!-- <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.04">
<!-- <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>
@ -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,15 +853,31 @@
<script src="/static/js/utils.js"></script>
<script src="/static/js/instantindicators.js"></script>
<script src="/static/js/archivechart.js"></script>
<script src="/static/js/archivetables.js"></script>
<script src="/static/js/livewebsocket.js"></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/configform.js"></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> -->
<!-- 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>
<!-- <script src="/static/js/dynamicbuttons.js"></script> -->
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -302,6 +302,7 @@ $(document).ready(function () {
runnerRecords.ajax.reload();
stratinRecords.ajax.reload();
archiveRecords.ajax.reload();
disable_arch_buttons();
})
//button copy
@ -927,11 +928,23 @@ 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],

View 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);
}

View 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);
})

View 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');
// // }
// });
// }
});

View 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)
}
});

View File

View 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;
}

View File

@ -1,3 +1,5 @@
//general util functions all accross
API_KEY = localStorage.getItem("api-key")
var chart = null

View File

@ -20,6 +20,15 @@
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 {
margin-right: 0px;
vertical-align: middle;
@ -254,6 +263,15 @@ table.dataTable thead>tr>th.sorting_asc:before, table.dataTable thead>tr>th.sort
margin-right: 5px;
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 */
}
.group-header {
cursor: pointer;
@ -262,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; */
@ -274,7 +302,7 @@ table.dataTable thead>tr>th.sorting_asc:before, table.dataTable thead>tr>th.sort
.group-header .batchheader-batch-id {
color: #a1a1a1; /* Highlight period info */
/* font-weight: bold; */
font-weight: 400;
}
.group-header .batchheader-period-info {
@ -409,6 +437,14 @@ html {
width: 35%;
}
}
/*
.even {
display: block;
}
.odd {
display: block;
} */
@media (min-width: 2001px) {
.msgContainerInner {