diff --git a/v2realbot/ENTRY_backtest_strategyVykladaci.py b/v2realbot/ENTRY_backtest_strategyVykladaci.py index 6e06116..ce8098d 100644 --- a/v2realbot/ENTRY_backtest_strategyVykladaci.py +++ b/v2realbot/ENTRY_backtest_strategyVykladaci.py @@ -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 diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index 586477c..db8f7b6 100644 Binary files a/v2realbot/__pycache__/config.cpython-310.pyc and b/v2realbot/__pycache__/config.cpython-310.pyc differ diff --git a/v2realbot/common/__pycache__/model.cpython-310.pyc b/v2realbot/common/__pycache__/model.cpython-310.pyc index 23bcca9..65d4564 100644 Binary files a/v2realbot/common/__pycache__/model.cpython-310.pyc and b/v2realbot/common/__pycache__/model.cpython-310.pyc differ diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index 1ca88a0..1d4151b 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -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 diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 098ecf2..ea9905f 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -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") diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index c8278ae..b9bc884 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -8,11 +8,13 @@ + +
@@ -29,6 +31,13 @@
+
+
+
+
+
+

+                    
@@ -70,7 +79,11 @@ Started Mode Account - Paused + Paused + Profit + Trades + Pos + AVGP @@ -362,9 +375,12 @@
- - - + + + + + + \ No newline at end of file diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js new file mode 100644 index 0000000..374ac72 --- /dev/null +++ b/v2realbot/static/js/archivechart.js @@ -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 = `
${param.time}
` + } + var price = data.value + // !== undefined ? data.value : data.close; + + + toolTip.innerHTML += `
${JSON.stringify(tradeDetails.get(param.time),null,2)}
${price.toFixed(3)}
`; + + //inspirace + // toolTip.innerHTML = `
Apple Inc.
+ // ${Math.round(100 * price) / 100} + //
+ // ${dateStr} + //
`; + + + // 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 + +} \ No newline at end of file diff --git a/v2realbot/static/js/archivetables.js b/v2realbot/static/js/archivetables.js new file mode 100644 index 0000000..e7c8c99 --- /dev/null +++ b/v2realbot/static/js/archivetables.js @@ -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'); + // } + //} + } ); \ No newline at end of file diff --git a/v2realbot/static/js/mywebsocket.js b/v2realbot/static/js/livewebsocket.js similarity index 92% rename from v2realbot/static/js/mywebsocket.js rename to v2realbot/static/js/livewebsocket.js index f26ab3c..37d0999 100644 --- a/v2realbot/static/js/mywebsocket.js +++ b/v2realbot/static/js/livewebsocket.js @@ -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) diff --git a/v2realbot/static/js/mytables.js b/v2realbot/static/js/mytables.js index 8c326a3..7b30bc1 100644 --- a/v2realbot/static/js/mytables.js +++ b/v2realbot/static/js/mytables.js @@ -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 = `
${param.time}
` - } - var price = data.value - // !== undefined ? data.value : data.close; - - - toolTip.innerHTML += `
${JSON.stringify(tradeDetails.get(param.time),null,2)}
${price.toFixed(3)}
`; - - //inspirace - // toolTip.innerHTML = `
Apple Inc.
- // ${Math.round(100 * price) / 100} - //
- // ${dateStr} - //
`; - - - // 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 diff --git a/v2realbot/static/js/mychart.js b/v2realbot/static/js/realtimechart.js similarity index 84% rename from v2realbot/static/js/mychart.js rename to v2realbot/static/js/realtimechart.js index 104941b..e2d6178 100644 --- a/v2realbot/static/js/mychart.js +++ b/v2realbot/static/js/realtimechart.js @@ -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, diff --git a/v2realbot/static/js/utils.js b/v2realbot/static/js/utils.js new file mode 100644 index 0000000..91fea3d --- /dev/null +++ b/v2realbot/static/js/utils.js @@ -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" ); +}); \ No newline at end of file diff --git a/v2realbot/static/main.css b/v2realbot/static/main.css index 8e2458c..1cdb456 100644 --- a/v2realbot/static/main.css +++ b/v2realbot/static/main.css @@ -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; } \ No newline at end of file