bugfixy ,rozdeleni js do souboru,rozsireni runneru

This commit is contained in:
David Brazda
2023-04-29 20:40:17 +02:00
parent 7a3567a8a6
commit 60b8457b4f
13 changed files with 653 additions and 527 deletions

View File

@ -357,7 +357,7 @@ def next(data, state: StrategyState):
#HLAVNI ITERACNI LOG JESTE PRED AKCI - obsahuje aktualni hodnoty vetsiny parametru
lp = state.interface.get_last_price(symbol=state.symbol)
state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),2)} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)} DEF:{str(is_defensive_mode())}", last_price=lp, stratvars=state.vars)
state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)} DEF:{str(is_defensive_mode())}", last_price=lp, stratvars=state.vars)
#maxSlopeMA = -0.03
#SLOPE ANGLE PROTECTIONs

View File

@ -72,6 +72,10 @@ class RunnerView(BaseModel):
run_name: Optional[str] = None
run_note: Optional[str] = None
run_account: Account
run_trade_count: Optional[int] = 0
run_profit: Optional[float] = 0
run_positions: Optional[int] = 0
run_avgp: Optional[float] = 0
run_stopped: Optional[datetime] = None
run_paused: Optional[datetime] = None
@ -83,6 +87,10 @@ class Runner(BaseModel):
run_account: Account
run_name: Optional[str] = None
run_note: Optional[str] = None
run_trade_count: Optional[int]
run_profit: Optional[float]
run_positions: Optional[int]
run_avgp: Optional[float]
run_stopped: Optional[datetime] = None
run_paused: Optional[datetime] = None
run_thread: Optional[object] = None

View File

@ -34,7 +34,12 @@ def get_all_threads():
def get_all_runners():
if len(db.runners) > 0:
print(db.runners)
#print(db.runners)
for i in db.runners:
i.run_profit = round(i.run_instance.state.profit,2)
i.run_trade_count = len(i.run_instance.state.tradeList)
i.run_positions = i.run_instance.state.positions
i.run_avgp = round(i.run_instance.state.avgp,3)
return (0, db.runners)
else:
return (0, [])
@ -54,6 +59,10 @@ def get_stratin(id: UUID):
def get_runner(id: UUID):
for i in db.runners:
if str(i.id) == str(id):
i.run_profit = round(i.run_instance.state.profit,2)
i.run_trade_count = len(i.run_instance.state.tradeList)
i.run_positions = i.run_instance.state.positions
i.run_avgp = round(i.run_instance.state.avgp,3)
return (0, i)
return (-2, "not found")

View File

@ -8,11 +8,13 @@
<link rel="manifest" href="/static/site.webmanifest">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<script src="/static/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" href="/static/main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.4.6/mousetrap.min.js"></script>
<!-- <script src="https://cdn.datatables.net/select/1.6.2/js/dataTables.select.min.js"></script> -->
</head>
<body>
<div id="main" class="mainConteiner flex-container">
@ -29,6 +31,13 @@
<button onclick="sendMessage(event)" id="bt.send" class="btn btn-success">Send</button>
</form>
</div>
<div id="statusHeader" data-toggle="collapse" data-target="#statusStratvars">
<div id="statusRegime" class="headerItem"></div>
<div id="statusName" class="headerItem"></div>
<div id="statusMode" class="headerItem"></div>
<div id="statusAccount" class="headerItem"></div>
<pre id="statusStratvars" class="headerItem collapse"></pre>
</div>
<div id="chart" style="display: None; float: left; "></div>
<div class="legend" id="legend"></div>
<div id="msgContainer">
@ -70,7 +79,11 @@
<th>Started</th>
<th>Mode</th>
<th>Account</th>
<th>Paused</th>
<th>Paused</th>
<th>Profit</th>
<th>Trades</th>
<th>Pos</th>
<th>AVGP</th>
</tr>
</thead>
<tbody></tbody>
@ -362,9 +375,12 @@
</div>
</div>
<script type="text/javascript" src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
<script src="/static/js/mywebsocket.js"></script>
<script src="/static/js/mychart.js"></script>
<script src="/static/js/mytables.js"></script>
<script src="/static/js/jquery.serializejson.js"></script>
<script src="/static/js/utils.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>
</body>
</html>

View File

@ -0,0 +1,296 @@
var tradeDetails = new Map();
var toolTip = null
//TRANSFORM object returned from RESTA PI get_arch_run_detail
//to series and markers required by lightweigth chart
//input array object bars = { high: [1,2,3], time: [1,2,3], close: [2,2,2]...}
//output array [{ time: 111, open: 11, high: 33, low: 333, close: 333},..]
function transform_data(data) {
transformed = []
//get basic bars, volume and vvwap
var bars = []
var volume = []
var vwap = []
data.bars.time.forEach((element, index, array) => {
sbars = {};
svolume = {};
svwap = {};
sbars["time"] = element;
sbars["close"] = data.bars.close[index]
sbars["open"] = data.bars.open[index]
sbars["high"] = data.bars.high[index]
sbars["low"] = data.bars.low[index]
svwap["time"] = element
svwap["value"] = data.bars.vwap[index]
svolume["time"] = element
svolume["value"] = data.bars.volume[index]
bars.push(sbars)
vwap.push(svwap)
volume.push(svolume)
});
transformed["bars"] = bars
transformed["vwap"] = vwap
transformed["volume"] = volume
//get markers - avgp line for all buys
var avgp_buy_line = []
var avgp_markers = []
var markers = []
var markers_line = []
data.trades.forEach((trade, index, array) => {
obj = {};
a_markers = {}
timestamp = Date.parse(trade.order.filled_at)/1000
if (trade.order.side == "buy") {
//line pro avgp markers
obj["time"] = timestamp;
obj["value"] = trade.pos_avg_price;
avgp_buy_line.push(obj)
//avgp markers pro prumernou cenu aktualnich pozic
a_markers["time"] = timestamp
a_markers["position"] = "aboveBar"
a_markers["color"] = "#e8c76d"
a_markers["shape"] = "arrowDown"
a_markers["text"] = trade.position_qty + " " + parseFloat(trade.pos_avg_price).toFixed(3)
avgp_markers.push(a_markers)
}
//buy sell markery
marker = {}
marker["time"] = timestamp;
// marker["position"] = (trade.order.side == "buy") ? "belowBar" : "aboveBar"
marker["position"] = (trade.order.side == "buy") ? "inBar" : "aboveBar"
marker["color"] = (trade.order.side == "buy") ? "blue" : "red"
//marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown"
marker["shape"] = (trade.order.side == "buy") ? "circle" : "arrowDown"
marker["text"] = trade.qty + " " + trade.price
markers.push(marker)
//prevedeme iso data na timestampy
trade.order.submitted_at = Date.parse(trade.order.submitted_at)/1000
trade.order.filled_at = Date.parse(trade.order.filled_at)/1000
trade.timestamp = Date.parse(trade.order.timestamp)/1000
tradeDetails.set(timestamp, trade)
//line pro buy/sell markery
mline = {}
mline["time"] = timestamp
mline["value"] = trade.price
markers_line.push(mline)
// time: datesForMarkers[i].time,
// position: 'aboveBar',
// color: '#e91e63',
// shape: 'arrowDown',
// text: 'Sell @ ' + Math.floor(datesForMarkers[i].high + 2),
});
transformed["avgp_buy_line"] = avgp_buy_line
transformed["markers"] = markers
transformed["markers_line"] = markers_line
transformed["avgp_markers"] = avgp_markers
//get additional indicators
//TBD
return transformed
}
//render chart of archived runs
function chart_archived_run(archRecord, data) {
if (chart !== null) {
chart.remove()
clear_status_header()
if (toolTip !== null) {
toolTip.style.display = 'none';
}
}
//console.log("inside")
var transformed_data = transform_data(data)
//console.log(transformed_data)
//tbd transform indicators
//var markersData = transform_trades(data)
// time: datesForMarkers[i].time,
// position: 'aboveBar',
// color: '#e91e63',
// shape: 'arrowDown',
// text: 'Sell @ ' + Math.floor(datesForMarkers[i].high + 2),
document.getElementById("chart").style.display = "block"
//initialize chart
var chartOptions = { width: 1300, height: 600, leftPriceScale: {visible: true}}
chart = LightweightCharts.createChart(document.getElementById('chart'), chartOptions);
chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: {
mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true
}})
var archCandlestickSeries = chart.addCandlestickSeries({ lastValueVisible: true, priceLineWidth:2, priceLineColor: "red", priceFormat: { type: 'price', precision: 2, minMove: 0.01 }});
archCandlestickSeries.priceScale().applyOptions({
scaleMargins: {
top: 0.1, // highest point of the series will be 10% away from the top
bottom: 0.4, // lowest point will be 40% away from the bottom
},
});
var archVwapSeries = chart.addLineSeries({
// title: "vwap",
color: '#2962FF',
lineWidth: 1,
lastValueVisible: false
});
var archVolumeSeries = chart.addHistogramSeries({title: "Volume", color: '#26a69a', priceFormat: {type: 'volume'}, priceScaleId: ''});
archVolumeSeries.priceScale().applyOptions({
// set the positioning of the volume series
scaleMargins: {
top: 0.7, // highest point of the series will be 70% away from the top
bottom: 0,
},
});
archVwapSeries.setData(transformed_data["vwap"])
archCandlestickSeries.setData(transformed_data["bars"])
archVolumeSeries.setData(transformed_data["volume"])
var avgBuyLine = chart.addLineSeries({
// title: "avgpbuyline",
color: '#e8c76d',
// color: 'transparent',
lineWidth: 1,
lastValueVisible: false
});
avgBuyLine.setData(transformed_data["avgp_buy_line"]);
avgBuyLine.setMarkers(transformed_data["avgp_markers"])
var markersLine = chart.addLineSeries({
// title: "avgpbuyline",
// color: '#d6d1c3',
color: 'transparent',
lineWidth: 1,
lastValueVisible: false
});
markersLine.setData(transformed_data["markers_line"]);
//console.log("markers")
//console.log(transformed_data["markers"])
markersLine.setMarkers(transformed_data["markers"])
//TBD dynamicky
//pokud je nazev atributu X_candles vytvorit candles
//pokud je objekt Y_line pak vytvorit lajnu
//pokud je objekt Z_markers pak vytvorit markers
//pokud je Z = X nebo Y, pak markers dat na danou lajnu (priklad vvwap_line, avgp_line, avgp_markers)
//udelat si nahodny vyber barev z listu
//DO BUDOUCNA MARKERS
// chart.subscribeCrosshairMove(param => {
// console.log(param.hoveredObjectId);
// });
//define tooltip
const container1 = document.getElementById('chart');
const toolTipWidth = 90;
const toolTipHeight = 90;
const toolTipMargin = 15;
// Create and style the tooltip html element
toolTip = document.createElement('div');
//width: 90px; , height: 80px;
toolTip.style = `position: absolute; display: none; padding: 8px; box-sizing: border-box; font-size: 12px; text-align: left; z-index: 1000; top: 12px; left: 12px; pointer-events: none; border: 1px solid; border-radius: 2px;font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;`;
toolTip.style.background = 'white';
toolTip.style.color = 'black';
toolTip.style.borderColor = '#2962FF';
container1.appendChild(toolTip);
//TODO onlick zkopirovat timestamp param.time
// chart.subscribeClick(param => {
// $('#trade-timestamp').val(param.time)
// //alert(JSON.safeStringify(param))
// //console.log(param.hoveredObjectId);
// });
//chart.subscribeCrosshairMove(param => {
chart.subscribeClick(param => {
$('#trade-timestamp').val(param.time)
if (
param.point === undefined ||
!param.time ||
param.point.x < 0 ||
param.point.x > container1.clientWidth ||
param.point.y < 0 ||
param.point.y > container1.clientHeight
) {
toolTip.style.display = 'none';
} else {
//vyber serie s jakou chci pracovat - muzu i dynamicky
//je to mapa https://tradingview.github.io/lightweight-charts/docs/api/interfaces/MouseEventParams
//key = series (key.seriestype vraci Line/Candlestick atp.) https://tradingview.github.io/lightweight-charts/docs/api/interfaces/SeriesOptionsMap
toolTip.style.display = 'none';
toolTip.innerHTML = "";
var data = param.seriesData.get(markersLine);
if (data !== undefined) {
//param.seriesData.forEach((value, key) => {
//console.log("key",key)
//console.log("value",value)
//data = value
//DOCASNE VYPNUTO
toolTip.style.display = 'block';
//console.log(JSON.safeStringify(key))
if (toolTip.innerHTML == "") {
toolTip.innerHTML = `<div>${param.time}</div>`
}
var price = data.value
// !== undefined ? data.value : data.close;
toolTip.innerHTML += `<pre>${JSON.stringify(tradeDetails.get(param.time),null,2)}</pre><div>${price.toFixed(3)}</div>`;
//inspirace
// toolTip.innerHTML = `<div style="color: ${'#2962FF'}">Apple Inc.</div><div style="font-size: 24px; margin: 4px 0px; color: ${'black'}">
// ${Math.round(100 * price) / 100}
// </div><div style="color: ${'black'}">
// ${dateStr}
// </div>`;
// Position tooltip according to mouse cursor position
toolTip.style.left = param.point.x+120 + 'px';
toolTip.style.top = param.point.y-100 + 'px';
}
//});
}
});
//add status
$("#statusRegime").text("ARCHIVED RUN")
$("#statusName").text(archRecord.name)
$("#statusMode").text(archRecord.mode)
$("#statusAccount").text(archRecord.account)
$("#statusStratvars").text(JSON.stringify(archRecord.stratvars,null,2))
chart.timeScale().fitContent();
//TBD other dynamically created indicators
}

View File

@ -0,0 +1,135 @@
//ARCHIVE TABLES
$(document).ready(function () {
archiveRecords.ajax.reload();
//disable buttons (enable on row selection)
$('#button_show_arch').attr('disabled','disabled');
$('#button_delete_arch').attr('disabled','disabled');
//selectable rows in archive table
$('#archiveTable tbody').on('click', 'tr', function () {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
$('#button_show_arch').attr('disabled','disabled');
$('#button_delete_arch').attr('disabled','disabled');
} else {
stratinRecords.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
$('#button_show_arch').attr('disabled',false);
$('#button_delete_arch').attr('disabled',false);
}
});
//delete button
$('#button_delete_arch').click(function () {
row = archiveRecords.row('.selected').data();
window.$('#delModalArchive').modal('show');
$('#delidarchive').val(row.id);
});
//show button
$('#button_show_arch').click(function () {
row = archiveRecords.row('.selected').data();
$('#button_show_arch').attr('disabled',true);
$.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);
//$('#chartArchive').append(JSON.stringify(data,null,2));
console.log(JSON.stringify(data,null,2));
chart_archived_run(row, 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);
}
})
});
})
//delete modal
$("#delModalArchive").on('submit','#delFormArchive', function(event){
event.preventDefault();
$('#deletearchive').attr('disabled','disabled');
id = $('#delidarchive').val()
//var formData = $(this).serializeJSON();
$.ajax({
url:"/archived_runners/"+id,
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
method:"DELETE",
contentType: "application/json",
dataType: "json",
success:function(data){
$('#delFormArchive')[0].reset();
window.$('#delModalArchive').modal('hide');
$('#deletearchive').attr('disabled', false);
archiveRecords.ajax.reload();
},
error: function(xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
window.alert(JSON.stringify(xhr));
console.log(JSON.stringify(xhr));
$('#deletearchive').attr('disabled', false);
}
})
});
//archive table
var archiveRecords =
$('#archiveTable').DataTable( {
ajax: {
url: '/archived_runners/',
dataSrc: '',
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
error: function(xhr, status, error) {
//var err = eval("(" + xhr.responseText + ")");
//window.alert(JSON.stringify(xhr));
console.log(JSON.stringify(xhr));
}
},
columns: [{ data: 'id' },
{data: 'name'},
{data: 'note'},
{data: 'started'},
{data: 'stopped'},
{data: 'mode'},
{data: 'account', visible: true},
{data: 'bt_from', visible: true},
{data: 'bt_to', visible: true},
{data: 'stratvars', visible: true},
{data: 'profit'},
{data: 'trade_count', visible: true},
{data: 'end_positions', visible: true},
{data: 'end_positions_avgp', visible: true},
{data: 'open_orders', visible: true}
],
columnDefs: [{
targets: [3,4,7,8],
render: function ( data, type, row ) {
return format_date(data)
},
}],
order: [[4, 'desc']],
paging: true,
lengthChange: false,
// createdRow: function( row, data, dataIndex){
// if (is_running(data.id) ){
// alert("runner");
// $(row).addClass('highlight');
// }
//}
} );

View File

@ -6,6 +6,37 @@ var logcnt = 0
var positionsPriceLine = null
var limitkaPriceLine = null
var angleSeries = 1
var candlestickSeries
var volumeSeries
var vwapSeries
//get details of runner to populate chart status
//fetch necessary - it could be initiated by manually inserting runnerId
function populate_rt_status_header(runnerId) {
$.ajax({
url:"/runners/"+runnerId,
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
method:"GET",
contentType: "application/json",
success:function(data){
console.log(JSON.stringify(data))
//add status on chart
$("#statusRegime").text("REALTIME")
$("#statusName").text(data.run_name)
$("#statusMode").text(data.run_mode)
$("#statusAccount").text(data.run_account)
//$("#statusStratvars").text(JSON.stringify(data.stratvars,null,2))
},
error: function(xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
window.alert(JSON.stringify(xhr));
console.log(JSON.stringify(xhr));
}
})
}
function connect(event) {
var runnerId = document.getElementById("runnerId")
@ -21,6 +52,7 @@ function connect(event) {
document.getElementById("bt-disc").style.display = "initial"
document.getElementById("bt-conn").style.display = "none"
document.getElementById("chart").style.display = "block"
populate_rt_status_header(runnerId.value)
}
ws.onmessage = function(event) {
var parsed_data = JSON.parse(event.data)

View File

@ -1,67 +1,3 @@
API_KEY = localStorage.getItem("api-key")
var chart = null
//Date.prototype.toJSON = function(){ return Date.parse(this)/1000 }
// safely handles circular references https://stackoverflow.com/questions/11616630/how-can-i-print-a-circular-structure-in-a-json-like-format
JSON.safeStringify = (obj, indent = 2) => {
let cache = [];
const retVal = JSON.stringify(
obj,
(key, value) =>
typeof value === "object" && value !== null
? cache.includes(value)
? undefined // Duplicate reference found, discard key
: cache.push(value) && value // Store value in our collection
: value,
indent
);
cache = null;
return retVal;
};
// Iterate through each element in the
// first array and if some of them
// include the elements in the second
// array then return true.
function findCommonElements3(arr1, arr2) {
return arr1.some(item => arr2.includes(item))
}
function set_timestamp(timestamp) {
//console.log(timestamp);
$('#trade-timestamp').val(timestamp);
}
//KEY shortcuts
Mousetrap.bind('e', function() {
$( "#button_edit" ).trigger( "click" );
});
Mousetrap.bind('a', function() {
$( "#button_add" ).trigger( "click" );
});
Mousetrap.bind('d', function() {
$( "#button_dup" ).trigger( "click" );
});
Mousetrap.bind('c', function() {
$( "#button_copy" ).trigger( "click" );
});
Mousetrap.bind('r', function() {
$( "#button_run" ).trigger( "click" );
});
Mousetrap.bind('p', function() {
$( "#button_pause" ).trigger( "click" );
});
Mousetrap.bind('s', function() {
$( "#button_stop" ).trigger( "click" );
});
Mousetrap.bind('j', function() {
$( "#button_add_json" ).trigger( "click" );
});
Mousetrap.bind('x', function() {
$( "#button_delete" ).trigger( "click" );
});
//on button
function store_api_key(event) {
key = document.getElementById("api-key").value;
@ -101,456 +37,7 @@ function is_running(id) {
});
return running
}
// alert(JSON.stringify(stratinRecords.data()))
// arr = stratinRecords.data()
// foreach(row in arr.rows) {
// alert(row.id)
// }
// //let obj = arr.find(o => o.id2 === '2');
// //console.log(obj);
// //alert(JSON.stringify(obj))
var tradeDetails = new Map();
//CHART ARCHIVED RUN - move to own file
//input array object bars = { high: [1,2,3], time: [1,2,3], close: [2,2,2]...}
//output array [{ time: 111, open: 11, high: 33, low: 333, close: 333},..]
function transform_data(data) {
transformed = []
//get basic bars, volume and vvwap
var bars = []
var volume = []
var vwap = []
data.bars.time.forEach((element, index, array) => {
sbars = {};
svolume = {};
svwap = {};
sbars["time"] = element;
sbars["close"] = data.bars.close[index]
sbars["open"] = data.bars.open[index]
sbars["high"] = data.bars.high[index]
sbars["low"] = data.bars.low[index]
svwap["time"] = element
svwap["value"] = data.bars.vwap[index]
svolume["time"] = element
svolume["value"] = data.bars.volume[index]
bars.push(sbars)
vwap.push(svwap)
volume.push(svolume)
});
transformed["bars"] = bars
transformed["vwap"] = vwap
transformed["volume"] = volume
//get markers - avgp line for all buys
var avgp_buy_line = []
var avgp_markers = []
var markers = []
var markers_line = []
data.trades.forEach((trade, index, array) => {
obj = {};
a_markers = {}
timestamp = Date.parse(trade.order.filled_at)/1000
if (trade.order.side == "buy") {
//line pro avgp markers
obj["time"] = timestamp;
obj["value"] = trade.pos_avg_price;
avgp_buy_line.push(obj)
//avgp markers pro prumernou cenu aktualnich pozic
a_markers["time"] = timestamp
a_markers["position"] = "aboveBar"
a_markers["color"] = "#e8c76d"
a_markers["shape"] = "arrowDown"
a_markers["text"] = trade.position_qty + " " + parseFloat(trade.pos_avg_price).toFixed(3)
avgp_markers.push(a_markers)
}
//buy sell markery
marker = {}
marker["time"] = timestamp;
// marker["position"] = (trade.order.side == "buy") ? "belowBar" : "aboveBar"
marker["position"] = (trade.order.side == "buy") ? "inBar" : "aboveBar"
marker["color"] = (trade.order.side == "buy") ? "blue" : "red"
//marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown"
marker["shape"] = (trade.order.side == "buy") ? "circle" : "arrowDown"
marker["text"] = trade.qty + " " + trade.price
markers.push(marker)
//prevedeme iso data na timestampy
trade.order.submitted_at = Date.parse(trade.order.submitted_at)/1000
trade.order.filled_at = Date.parse(trade.order.filled_at)/1000
trade.timestamp = Date.parse(trade.order.timestamp)/1000
tradeDetails.set(timestamp, trade)
//line pro buy/sell markery
mline = {}
mline["time"] = timestamp
mline["value"] = trade.price
markers_line.push(mline)
// time: datesForMarkers[i].time,
// position: 'aboveBar',
// color: '#e91e63',
// shape: 'arrowDown',
// text: 'Sell @ ' + Math.floor(datesForMarkers[i].high + 2),
});
transformed["avgp_buy_line"] = avgp_buy_line
transformed["markers"] = markers
transformed["markers_line"] = markers_line
transformed["avgp_markers"] = avgp_markers
//get additional indicators
//TBD
return transformed
}
function chart_archived_run(data) {
if (chart !== null) {
chart.remove()
}
//console.log("inside")
var transformed_data = transform_data(data)
//console.log(transformed_data)
//tbd transform indicators
//var markersData = transform_trades(data)
// time: datesForMarkers[i].time,
// position: 'aboveBar',
// color: '#e91e63',
// shape: 'arrowDown',
// text: 'Sell @ ' + Math.floor(datesForMarkers[i].high + 2),
document.getElementById("chart").style.display = "block"
//initialize chart
var chartOptions = { width: 1300, height: 600, leftPriceScale: {visible: true}}
chart = LightweightCharts.createChart(document.getElementById('chart'), chartOptions);
chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: {
mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true
}})
var archCandlestickSeries = chart.addCandlestickSeries({ lastValueVisible: true, priceLineWidth:2, priceLineColor: "red", priceFormat: { type: 'price', precision: 2, minMove: 0.01 }});
archCandlestickSeries.priceScale().applyOptions({
scaleMargins: {
top: 0.1, // highest point of the series will be 10% away from the top
bottom: 0.4, // lowest point will be 40% away from the bottom
},
});
var archVwapSeries = chart.addLineSeries({
// title: "vwap",
color: '#2962FF',
lineWidth: 1,
lastValueVisible: false
});
var archVolumeSeries = chart.addHistogramSeries({title: "Volume", color: '#26a69a', priceFormat: {type: 'volume'}, priceScaleId: ''});
archVolumeSeries.priceScale().applyOptions({
// set the positioning of the volume series
scaleMargins: {
top: 0.7, // highest point of the series will be 70% away from the top
bottom: 0,
},
});
archVwapSeries.setData(transformed_data["vwap"])
archCandlestickSeries.setData(transformed_data["bars"])
archVolumeSeries.setData(transformed_data["volume"])
var avgBuyLine = chart.addLineSeries({
// title: "avgpbuyline",
color: '#e8c76d',
// color: 'transparent',
lineWidth: 1,
lastValueVisible: false
});
avgBuyLine.setData(transformed_data["avgp_buy_line"]);
avgBuyLine.setMarkers(transformed_data["avgp_markers"])
var markersLine = chart.addLineSeries({
// title: "avgpbuyline",
// color: '#d6d1c3',
color: 'transparent',
lineWidth: 1,
lastValueVisible: false
});
markersLine.setData(transformed_data["markers_line"]);
//console.log("markers")
//console.log(transformed_data["markers"])
markersLine.setMarkers(transformed_data["markers"])
//TBD dynamicky
//pokud je nazev atributu X_candles vytvorit candles
//pokud je objekt Y_line pak vytvorit lajnu
//pokud je objekt Z_markers pak vytvorit markers
//pokud je Z = X nebo Y, pak markers dat na danou lajnu (priklad vvwap_line, avgp_line, avgp_markers)
//udelat si nahodny vyber barev z listu
//DO BUDOUCNA MARKERS
// chart.subscribeCrosshairMove(param => {
// console.log(param.hoveredObjectId);
// });
//define tooltip
const container1 = document.getElementById('chart');
const toolTipWidth = 90;
const toolTipHeight = 90;
const toolTipMargin = 15;
// Create and style the tooltip html element
const toolTip = document.createElement('div');
//width: 90px; , height: 80px;
toolTip.style = `position: absolute; display: none; padding: 8px; box-sizing: border-box; font-size: 12px; text-align: left; z-index: 1000; top: 12px; left: 12px; pointer-events: none; border: 1px solid; border-radius: 2px;font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;`;
toolTip.style.background = 'white';
toolTip.style.color = 'black';
toolTip.style.borderColor = '#2962FF';
container1.appendChild(toolTip);
//TODO onlick zkopirovat timestamp param.time
// chart.subscribeClick(param => {
// $('#trade-timestamp').val(param.time)
// //alert(JSON.safeStringify(param))
// //console.log(param.hoveredObjectId);
// });
//chart.subscribeCrosshairMove(param => {
chart.subscribeClick(param => {
$('#trade-timestamp').val(param.time)
if (
param.point === undefined ||
!param.time ||
param.point.x < 0 ||
param.point.x > container1.clientWidth ||
param.point.y < 0 ||
param.point.y > container1.clientHeight
) {
toolTip.style.display = 'none';
} else {
//vyber serie s jakou chci pracovat - muzu i dynamicky
//je to mapa https://tradingview.github.io/lightweight-charts/docs/api/interfaces/MouseEventParams
//key = series (key.seriestype vraci Line/Candlestick atp.) https://tradingview.github.io/lightweight-charts/docs/api/interfaces/SeriesOptionsMap
toolTip.style.display = 'none';
toolTip.innerHTML = "";
var data = param.seriesData.get(markersLine);
if (data !== undefined) {
//param.seriesData.forEach((value, key) => {
//console.log("key",key)
//console.log("value",value)
//data = value
//DOCASNE VYPNUTO
toolTip.style.display = 'block';
//console.log(JSON.safeStringify(key))
if (toolTip.innerHTML == "") {
toolTip.innerHTML = `<div>${param.time}</div>`
}
var price = data.value
// !== undefined ? data.value : data.close;
toolTip.innerHTML += `<pre>${JSON.stringify(tradeDetails.get(param.time),null,2)}</pre><div>${price.toFixed(3)}</div>`;
//inspirace
// toolTip.innerHTML = `<div style="color: ${'#2962FF'}">Apple Inc.</div><div style="font-size: 24px; margin: 4px 0px; color: ${'black'}">
// ${Math.round(100 * price) / 100}
// </div><div style="color: ${'black'}">
// ${dateStr}
// </div>`;
// Position tooltip according to mouse cursor position
toolTip.style.left = param.point.x+120 + 'px';
toolTip.style.top = param.point.y-100 + 'px';
}
//});
}
});
chart.timeScale().fitContent();
//TBD other dynamically created indicators
}
//ARCHIVE TABLES
$(document).ready(function () {
archiveRecords.ajax.reload();
//disable buttons (enable on row selection)
$('#button_show_arch').attr('disabled','disabled');
$('#button_delete_arch').attr('disabled','disabled');
//selectable rows in archive table
$('#archiveTable tbody').on('click', 'tr', function () {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
$('#button_show_arch').attr('disabled','disabled');
$('#button_delete_arch').attr('disabled','disabled');
} else {
stratinRecords.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
$('#button_show_arch').attr('disabled',false);
$('#button_delete_arch').attr('disabled',false);
}
});
//delete button
$('#button_delete_arch').click(function () {
row = archiveRecords.row('.selected').data();
window.$('#delModalArchive').modal('show');
$('#delidarchive').val(row.id);
});
//show button
$('#button_show_arch').click(function () {
row = archiveRecords.row('.selected').data();
$('#button_show_arch').attr('disabled',true);
$.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);
//$('#chartArchive').append(JSON.stringify(data,null,2));
console.log(JSON.stringify(data,null,2));
chart_archived_run(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);
}
})
});
})
//delete modal
$("#delModalArchive").on('submit','#delFormArchive', function(event){
event.preventDefault();
$('#deletearchive').attr('disabled','disabled');
id = $('#delidarchive').val()
//var formData = $(this).serializeJSON();
$.ajax({
url:"/archived_runners/"+id,
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
method:"DELETE",
contentType: "application/json",
dataType: "json",
success:function(data){
$('#delFormArchive')[0].reset();
window.$('#delModalArchive').modal('hide');
$('#deletearchive').attr('disabled', false);
archiveRecords.ajax.reload();
},
error: function(xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
window.alert(JSON.stringify(xhr));
console.log(JSON.stringify(xhr));
$('#deletearchive').attr('disabled', false);
}
})
});
//https://www.w3schools.com/jsref/jsref_tolocalestring.asp
function format_date(datum) {
//const options = { weekday: 'long', year: 'numeric', month: 'numeric', day: 'numeric', };
const options = {dateStyle: "short", timeStyle: "short"}
const date = new Date(datum);
return date.toLocaleString('cs-CZ', options);
}
//stratin table
var archiveRecords =
$('#archiveTable').DataTable( {
ajax: {
url: '/archived_runners/',
dataSrc: '',
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
error: function(xhr, status, error) {
//var err = eval("(" + xhr.responseText + ")");
//window.alert(JSON.stringify(xhr));
console.log(JSON.stringify(xhr));
}
},
columns: [{ data: 'id' },
{data: 'name'},
{data: 'note'},
{data: 'started'},
{data: 'stopped'},
{data: 'mode'},
{data: 'account', visible: true},
{data: 'bt_from', visible: true},
{data: 'bt_to', visible: true},
{data: 'stratvars', visible: true},
{data: 'profit'},
{data: 'trade_count', visible: true},
{data: 'end_positions', visible: true},
{data: 'end_positions_avgp', visible: true},
{data: 'open_orders', visible: true}
],
columnDefs: [{
targets: [3,4,7,8],
render: function ( data, type, row ) {
return format_date(data)
},
}],
order: [[4, 'desc']],
paging: true,
lengthChange: false,
// createdRow: function( row, data, dataIndex){
// if (is_running(data.id) ){
// alert("runner");
// $(row).addClass('highlight');
// }
//}
} );
//STRATIN and RUNNERS TABELS
$(document).ready(function () {
@ -903,6 +390,7 @@ $(document).ready(function () {
});
} );
//stratin table
var stratinRecords =
$('#stratinTable').DataTable( {
@ -930,7 +418,7 @@ var stratinRecords =
{data: 'add_data_conf', visible: false},
{data: 'note'},
{data: 'history', visible: false},
{data: 'id', visible: true}
{data: 'id', visible: true},
],
columnDefs: [{
targets: 12,
@ -941,6 +429,10 @@ var stratinRecords =
}],
order: [[1, 'asc']],
paging: false,
// select: {
// style: 'multi'
// },
processing: false
// createdRow: function( row, data, dataIndex){
// if (is_running(data.id) ){
// alert("runner");
@ -976,10 +468,17 @@ var runnerRecords =
{data: 'run_started'},
{data: 'run_mode'},
{data: 'run_account'},
{data: 'run_paused'}
{data: 'run_paused'},
{data: 'run_profit'},
{data: 'run_trade_count'},
{data: 'run_positions'},
{data: 'run_avgp'},
],
paging: false,
processing: false
processing: false,
// select: {
// style: 'multi'
// },
} );
//modal na run

View File

@ -1,17 +1,19 @@
//it is called after population
function populate_real_time_chart() {
if (chart !== null) {
chart.remove()
chart.remove();
clear_status_header();
}
//const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } };
const chartOptions = { width: 1045, height: 600, leftPriceScale: {visible: true}}
var chartOptions = { width: 1045, height: 600, leftPriceScale: {visible: true}}
chart = LightweightCharts.createChart(document.getElementById('chart'), chartOptions);
chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: {
mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true
}})
const candlestickSeries = chart.addCandlestickSeries({ lastValueVisible: true, priceLineWidth:2, priceLineColor: "red", priceFormat: { type: 'price', precision: 2, minMove: 0.01 }});
candlestickSeries = chart.addCandlestickSeries({ lastValueVisible: true, priceLineWidth:2, priceLineColor: "red", priceFormat: { type: 'price', precision: 2, minMove: 0.01 }});
candlestickSeries.priceScale().applyOptions({
scaleMargins: {
top: 0.1, // highest point of the series will be 10% away from the top
@ -20,7 +22,7 @@ function populate_real_time_chart() {
});
const volumeSeries = chart.addHistogramSeries({title: "Volume", color: '#26a69a', priceFormat: {type: 'volume'}, priceScaleId: ''});
volumeSeries = chart.addHistogramSeries({title: "Volume", color: '#26a69a', priceFormat: {type: 'volume'}, priceScaleId: ''});
volumeSeries.priceScale().applyOptions({
// set the positioning of the volume series
scaleMargins: {
@ -29,7 +31,7 @@ function populate_real_time_chart() {
},
});
const vwapSeries = chart.addLineSeries({
vwapSeries = chart.addLineSeries({
// title: "vwap",
color: '#2962FF',
lineWidth: 1,

View File

@ -0,0 +1,81 @@
API_KEY = localStorage.getItem("api-key")
var chart = null
// safely handles circular references https://stackoverflow.com/questions/11616630/how-can-i-print-a-circular-structure-in-a-json-like-format
JSON.safeStringify = (obj, indent = 2) => {
let cache = [];
const retVal = JSON.stringify(
obj,
(key, value) =>
typeof value === "object" && value !== null
? cache.includes(value)
? undefined // Duplicate reference found, discard key
: cache.push(value) && value // Store value in our collection
: value,
indent
);
cache = null;
return retVal;
};
//https://www.w3schools.com/jsref/jsref_tolocalestring.asp
function format_date(datum) {
//const options = { weekday: 'long', year: 'numeric', month: 'numeric', day: 'numeric', };
const options = {dateStyle: "short", timeStyle: "short"}
const date = new Date(datum);
return date.toLocaleString('cs-CZ', options);
}
function clear_status_header() {
$("#statusRegime").text("")
$("#statusName").text("")
$("#statusMode").text("")
$("#statusAccount").text("")
$("#statusStratvars").text("")
//clear previous logs from rt
$("#lines").empty()
}
// Iterate through each element in the
// first array and if some of them
// include the elements in the second
// array then return true.
function findCommonElements3(arr1, arr2) {
return arr1.some(item => arr2.includes(item))
}
function set_timestamp(timestamp) {
//console.log(timestamp);
$('#trade-timestamp').val(timestamp);
}
//KEY shortcuts
Mousetrap.bind('e', function() {
$( "#button_edit" ).trigger( "click" );
});
Mousetrap.bind('a', function() {
$( "#button_add" ).trigger( "click" );
});
Mousetrap.bind('d', function() {
$( "#button_dup" ).trigger( "click" );
});
Mousetrap.bind('c', function() {
$( "#button_copy" ).trigger( "click" );
});
Mousetrap.bind('r', function() {
$( "#button_run" ).trigger( "click" );
});
Mousetrap.bind('p', function() {
$( "#button_pause" ).trigger( "click" );
});
Mousetrap.bind('s', function() {
$( "#button_stop" ).trigger( "click" );
});
Mousetrap.bind('j', function() {
$( "#button_add_json" ).trigger( "click" );
});
Mousetrap.bind('x', function() {
$( "#button_delete" ).trigger( "click" );
});

View File

@ -85,6 +85,54 @@ pre {
overflow: auto;
}
#statusHeader {
margin-left: 55px;
font-size: normal;
font-weight: bold;
display: flex;
}
.headerItem {
padding-right: 30px;
}
.highlighted {
font-weight: bold;
}
.switcher {
display: flex;
align-items: center;
height: 30px;
margin-top: 8px;
color: #2196F3;
}
.switcher-item {
cursor: pointer;
text-decoration: none;
display: inline-block;
padding: 6px 8px;
font-size: 14px;
color: #262b3e;
background-color: transparent;
margin-right: 8px;
border: none;
border-radius: 4px;
outline: none;
}
.switcher-item:hover {
background-color: #f2f3f5;
}
.switcher-active-item {
text-decoration: none;
cursor: default;
color: #262b3e;
}
.switcher-active-item,
.switcher-active-item:hover {
background-color: #e1eff9;
}