backend: slope support, gui:add json

This commit is contained in:
David Brazda
2023-04-17 15:26:30 +02:00
parent c7d67fdf65
commit f4fbda7daf
20 changed files with 196 additions and 26 deletions

15
testy/testSlope.py Normal file
View File

@ -0,0 +1,15 @@
sma = list
sma = [28.90, 28.91, 28.91, 28.92, 28.97, 28.99]
slope_lookback = 4
roc_lookback = 4
slope = (sma[-1] - sma[-slope_lookback])/slope_lookback
roc = ((sma[-1] - sma[-roc_lookback])/sma[-roc_lookback])*100
print(slope)
# -1 až 0 klesání
# 0 až 1 stoupání

3
testy/testTelegram.py Normal file
View File

@ -0,0 +1,3 @@
from v2realbot.utils.utils import send_to_telegram
send_to_telegram("nazdarek")

View File

@ -45,7 +45,10 @@ stratvars = AttributeDict(maxpozic = 250,
curve = [0.01, 0.01, 0.01, 0, 0.02, 0.02, 0.01,0.01, 0.01,0.03, 0.01, 0.01, 0.01,0.04, 0.01,0.01, 0.01,0.05, 0.01,0.01, 0.01,0.01, 0.06,0.01, 0.01,0.01, 0.01], curve = [0.01, 0.01, 0.01, 0, 0.02, 0.02, 0.01,0.01, 0.01,0.03, 0.01, 0.01, 0.01,0.04, 0.01,0.01, 0.01,0.05, 0.01,0.01, 0.01,0.01, 0.06,0.01, 0.01,0.01, 0.01],
blockbuy = 0, blockbuy = 0,
ticks2reset = 0.04, ticks2reset = 0.04,
consolidation_bar_count = 10) consolidation_bar_count = 10,
slope_lookback = 20,
minimum_slope = -0.23
)
##toto rozparsovat a strategii spustit stejne jako v main ##toto rozparsovat a strategii spustit stejne jako v main
toml_string = """ toml_string = """
[[strategies]] [[strategies]]
@ -106,7 +109,7 @@ def next(data, state: StrategyState):
price = last_price price = last_price
##prvni se vyklada na aktualni cenu, další jdou podle krivky, nula v krivce zvyšuje množství pro následující iteraci ##prvni se vyklada na aktualni cenu, další jdou podle krivky, nula v krivce zvyšuje množství pro následující iteraci
state.buy_l(price=price, size=qty) state.buy_l(price=price, size=qty)
print("prvni limitka na aktuální cenu. Další podle křicvky", price, qty) print("prvni limitka na aktuální cenu. Další podle křivky", price, qty)
for i in range(0,vykladka-1): for i in range(0,vykladka-1):
price = price2dec(float(price - curve[i])) price = price2dec(float(price - curve[i]))
if price == last_price: if price == last_price:
@ -133,24 +136,55 @@ def next(data, state: StrategyState):
return 0 return 0
try: try:
## slope vyresi rychlé sesupy - jeste je treba podchytit pomalejsi sesupy
slope = 99
#minimum slope disabled if -1
#roc_lookback = 20
#print(state.vars.MA, "MACKO") #print(state.vars.MA, "MACKO")
#print(state.bars.hlcc4) #print(state.bars.hlcc4)
state.indicators.ema = ema(state.bars.close, state.vars.MA) #state.bars.vwap state.indicators.ema = ema(state.bars.close, state.vars.MA) #state.bars.vwap
#trochu prasarna, EMAcko trunc na 3 mista - kdyz se osvedci, tak udelat efektivne #trochu prasarna, EMAcko trunc na 3 mista - kdyz se osvedci, tak udelat efektivne
state.indicators.ema = [trunc(i,3) for i in state.indicators.ema] state.indicators.ema = [trunc(i,3) for i in state.indicators.ema]
ic(state.vars.MA, state.vars.Trend, state.indicators.ema[-5:]) ic(state.vars.MA, state.vars.Trend, state.indicators.ema[-5:])
slope_lookback = int(state.vars.slope_lookback)
minimum_slope = float(state.vars.minimum_slope)
if len(state.bars.close) > slope_lookback:
#slope = ((state.indicators.ema[-1] - state.indicators.ema[-slope_lookback])/slope_lookback)*100
#PUVODNI slope = ((state.bars.close[-1] - state.bars.close[-slope_lookback])/slope_lookback)*100
slope = ((state.bars.close[-1] - state.bars.close[-slope_lookback])/state.bars.close[-slope_lookback])*100
#roc = ((state.indicators.ema[-1] - state.indicators.ema[-roc_lookback])/state.indicators.ema[-roc_lookback])*100
state.indicators.slope.append(slope)
#state.indicators.roc.append(roc)
ic(state.indicators.slope[-5:])
#ic(state.indicators.roc[-5:])
except Exception as e: except Exception as e:
print("No data for MA yet", str(e)) print("Exception in NEXT Indicator section", str(e))
print("is falling",isfalling(state.indicators.ema,state.vars.Trend)) print("is falling",isfalling(state.indicators.ema,state.vars.Trend))
print("is rising",isrising(state.indicators.ema,state.vars.Trend)) print("is rising",isrising(state.indicators.ema,state.vars.Trend))
#and data['index'] > state.vars.lastbuyindex+state.vars.Trend: #SLOPE ANGLE PROTECTION
#neni vylozeno muzeme nakupovat if slope < minimum_slope:
print("OCHRANA SLOPE TOO HIGH")
if len(state.vars.pendingbuys)>0:
print("CANCEL PENDINGBUYS")
ic(state.vars.pendingbuys)
res = asyncio.run(state.cancel_pending_buys())
ic(state.vars.pendingbuys)
print("slope", slope)
print("min slope", minimum_slope)
if ic(state.vars.jevylozeno) == 0: if ic(state.vars.jevylozeno) == 0:
print("Neni vylozeno, muzeme testovat nakup") print("Neni vylozeno, muzeme testovat nakup")
if isfalling(state.indicators.ema,state.vars.Trend): if isfalling(state.indicators.ema,state.vars.Trend) and slope > minimum_slope:
print("BUY MARKET") print("BUY MARKET")
ic(data['updated']) ic(data['updated'])
ic(state.time) ic(state.time)
@ -167,7 +201,7 @@ def next(data, state: StrategyState):
#TODO predelat mechanismus ticků (zrelativizovat), aby byl pouzitelny na tituly s ruznou cenou #TODO predelat mechanismus ticků (zrelativizovat), aby byl pouzitelny na tituly s ruznou cenou
#TODO spoustet 1x za X iteraci nebo cas #TODO spoustet 1x za X iteraci nebo cas
if state.vars.jevylozeno == 1: if state.vars.jevylozeno == 1:
#pokud mame vylozeno a cena je vetsi nez 0.04 #pokud mame vylozeno a cena je vetsi nez tick2reset
if len(state.vars.pendingbuys)>0: if len(state.vars.pendingbuys)>0:
maxprice = max(state.vars.pendingbuys.values()) maxprice = max(state.vars.pendingbuys.values())
print("max cena v orderbuys", maxprice) print("max cena v orderbuys", maxprice)
@ -264,6 +298,8 @@ def init(state: StrategyState):
#place to declare new vars #place to declare new vars
print("INIT v main",state.name) print("INIT v main",state.name)
state.indicators['ema'] = [] state.indicators['ema'] = []
state.indicators['slope'] = []
#state.indicators['roc'] = []
def main(): def main():

View File

@ -517,7 +517,7 @@ class Backtester:
#with lock: #with lock:
for o in self.open_orders: for o in self.open_orders:
#print(o) #print(o)
if o.symbol == symbol: if o.symbol == symbol and o.canceled_at is None:
if side is None or o.side == side: if side is None or o.side == side:
res.append(o) res.append(o)
return res return res

View File

@ -235,6 +235,7 @@ def capsule(target: object, db: object):
reason = "SHUTDOWN OK" reason = "SHUTDOWN OK"
except Exception as e: except Exception as e:
reason = "SHUTDOWN Exception:" + str(e) reason = "SHUTDOWN Exception:" + str(e)
print(str(e))
print(reason) print(reason)
finally: finally:
# remove runners after thread is stopped and save results to stratin history # remove runners after thread is stopped and save results to stratin history

View File

@ -147,7 +147,7 @@ class LiveInterface(GeneralInterface):
return a return a
except APIError as e: except APIError as e:
#order doesnt exist #order doesnt exist
if e.code == 40410000: return 0,0 if e.code == 40410000: return 0
else: else:
print("nepovedlo se zrusit objednavku", str(e)) print("nepovedlo se zrusit objednavku", str(e))
#raise Exception(e) #raise Exception(e)

View File

@ -11,7 +11,7 @@ from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import APIKeyHeader from fastapi.security import APIKeyHeader
import uvicorn import uvicorn
from uuid import UUID from uuid import UUID
import controller.services as cs import v2realbot.controller.services as cs
from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query
from fastapi.responses import HTMLResponse, FileResponse from fastapi.responses import HTMLResponse, FileResponse

View File

@ -79,7 +79,13 @@
</div> </div>
<div id="stratin-table" class="flex-items"> <div id="stratin-table" class="flex-items">
<div id="stratin-table"> <div id="stratin-table">
<button id="button_add" class="btn btn-success">Add</button><button id="button_edit" class="btn btn-success">Edit</button><button id="button_dup" class="btn btn-success">Duplicate</button><button id="button_delete" class="btn btn-success">Delete</button><button id="button_run" class="btn btn-success">Run Strategy</button> <button id="button_add" class="btn btn-success">Add</button>
<button id="button_add_json" class="btn btn-success">Add JSON</button>
<button id="button_edit" class="btn btn-success">Edit</button>
<button id="button_dup" class="btn btn-success">Duplicate</button>
<button id="button_copy" class="btn btn-success">Copy JSON</button>
<button id="button_delete" class="btn btn-success">Delete</button>
<button id="button_run" class="btn btn-success">Run Strategy</button>
<table id="stratinTable" class="display" style="width:100%"> <table id="stratinTable" class="display" style="width:100%">
<thead> <thead>
<tr> <tr>
@ -192,6 +198,28 @@
</form> </form>
</div> </div>
</div> </div>
<div id="jsonModal" class="modal fade">
<div class="modal-dialog">
<form method="post" id="jsonForm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title_json"><i class="fa fa-plus"></i> Add JSON Record</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="jsontext" class="control-label">JSON</label>
<textarea class="form-control" rows="7" id="jsontext" name="jsontext" required></textarea>
</div>
</div>
<div class="modal-footer">
<input type="submit" name="json_add" id="json_add" class="btn btn-info" value="Add" />
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</form>
</div>
</div>
<div id="runModal" class="modal fade"> <div id="runModal" class="modal fade">
<div class="modal-dialog"> <div class="modal-dialog">
<form method="post" id="runForm"> <form method="post" id="runForm">

View File

@ -1,5 +1,5 @@
//const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } }; //const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } };
const chartOptions = { width: 1200, height: 600} const chartOptions = { width: 1200, height: 600, leftPriceScale: {visible: true}}
const chart = LightweightCharts.createChart(document.getElementById('chart'), chartOptions); const chart = LightweightCharts.createChart(document.getElementById('chart'), chartOptions);
chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: { chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: {
mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true

View File

@ -64,6 +64,7 @@ $(document).ready(function () {
$('#button_stop').attr('disabled','disabled'); $('#button_stop').attr('disabled','disabled');
$('#button_edit').attr('disabled','disabled'); $('#button_edit').attr('disabled','disabled');
$('#button_dup').attr('disabled','disabled'); $('#button_dup').attr('disabled','disabled');
$('#button_copy').attr('disabled','disabled');
$('#button_delete').attr('disabled','disabled'); $('#button_delete').attr('disabled','disabled');
$('#button_run').attr('disabled','disabled'); $('#button_run').attr('disabled','disabled');
@ -72,6 +73,7 @@ $(document).ready(function () {
if ($(this).hasClass('selected')) { if ($(this).hasClass('selected')) {
$(this).removeClass('selected'); $(this).removeClass('selected');
$('#button_dup').attr('disabled','disabled'); $('#button_dup').attr('disabled','disabled');
$('#button_copy').attr('disabled','disabled');
$('#button_edit').attr('disabled','disabled'); $('#button_edit').attr('disabled','disabled');
$('#button_delete').attr('disabled','disabled'); $('#button_delete').attr('disabled','disabled');
$('#button_run').attr('disabled','disabled'); $('#button_run').attr('disabled','disabled');
@ -79,6 +81,7 @@ $(document).ready(function () {
stratinRecords.$('tr.selected').removeClass('selected'); stratinRecords.$('tr.selected').removeClass('selected');
$(this).addClass('selected'); $(this).addClass('selected');
$('#button_dup').attr('disabled',false); $('#button_dup').attr('disabled',false);
$('#button_copy').attr('disabled',false);
$('#button_edit').attr('disabled',false); $('#button_edit').attr('disabled',false);
$('#button_delete').attr('disabled',false); $('#button_delete').attr('disabled',false);
$('#button_run').attr('disabled',false); $('#button_run').attr('disabled',false);
@ -105,6 +108,28 @@ $(document).ready(function () {
stratinRecords.ajax.reload(); stratinRecords.ajax.reload();
}) })
//button refresh
$('#button_copy').click(function () {
event.preventDefault();
$('#button_copy').attr('disabled','disabled');
row = stratinRecords.row('.selected').data();
const rec = new Object()
rec.id2 = parseInt(row.id2);
rec.name = row.name;
rec.symbol = row.symbol;
rec.class_name = row.class_name;
rec.script = row.script;
rec.open_rush = row.open_rush;
rec.close_rush = row.close_rush;
rec.stratvars_conf = row.stratvars_conf;
rec.add_data_conf = row.add_data_conf;
rec.note = row.note;
rec.history = "";
jsonString = JSON.stringify(rec);
navigator.clipboard.writeText(jsonString);
$('#button_copy').attr('disabled', false);
})
//button duplicate //button duplicate
$('#button_dup').click(function () { $('#button_dup').click(function () {
row = stratinRecords.row('.selected').data(); row = stratinRecords.row('.selected').data();
@ -250,6 +275,7 @@ $(document).ready(function () {
$('#action').val('addRecord'); $('#action').val('addRecord');
$('#save').val('Add'); $('#save').val('Add');
}); });
//edit button //edit button
$('#button_edit').click(function () { $('#button_edit').click(function () {
row = stratinRecords.row('.selected').data(); row = stratinRecords.row('.selected').data();
@ -279,6 +305,10 @@ $(document).ready(function () {
$('#save').val('Delete'); $('#save').val('Delete');
}); });
//json add button
$('#button_add_json').click(function () {
window.$('#jsonModal').modal('show');
});
} ); } );
//stratin table //stratin table
@ -441,7 +471,7 @@ $("#recordModal").on('submit','#recordForm', function(event){
}) })
} }
else { else {
//code for add //code for edit
event.preventDefault(); event.preventDefault();
$('#save').attr('disabled','disabled'); $('#save').attr('disabled','disabled');
var formData = $(this).serializeJSON(); var formData = $(this).serializeJSON();
@ -474,7 +504,40 @@ $("#recordModal").on('submit','#recordForm', function(event){
}); });
//delete //add json modal
$("#jsonModal").on('submit','#jsonForm', function(event){
event.preventDefault();
$('#json_add').attr('disabled','disabled');
jsonString = $('#jsontext').val();
$.ajax({
url:"/stratins/",
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
method:"POST",
contentType: "application/json",
dataType: "json",
data: jsonString,
success:function(data){
$('#jsonForm')[0].reset();
window.$('#jsonModal').modal('hide');
$('#json_add').attr('disabled', false);
setTimeout(function () {
runnerRecords.ajax.reload();
stratinRecords.ajax.reload();
}, 750)
},
error: function(xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
window.alert(JSON.stringify(xhr));
console.log(JSON.stringify(xhr));
$('#json_add').attr('disabled', false);
}
})
});
//delete modal
$("#delModal").on('submit','#delForm', function(event){ $("#delModal").on('submit','#delForm', function(event){
event.preventDefault(); event.preventDefault();
$('#delete').attr('disabled','disabled'); $('#delete').attr('disabled','disabled');

View File

@ -1,3 +1,4 @@
const momentumIndicatorNames = ["roc", "slope"]
var indList = [] var indList = []
var ws = null; var ws = null;
function connect(event) { function connect(event) {
@ -54,7 +55,7 @@ function connect(event) {
if (parsed_data.hasOwnProperty("indicators")) { if (parsed_data.hasOwnProperty("indicators")) {
var indicators = parsed_data.indicators var indicators = parsed_data.indicators
//if there are indicators it means there must be at least two keys (first time is there everytime) //if there are indicators it means there must be at least two keys (except time which is always present)
if (Object.keys(indicators).length > 1) { if (Object.keys(indicators).length > 1) {
for (const [key, value] of Object.entries(indicators)) { for (const [key, value] of Object.entries(indicators)) {
if (key !== "time") { if (key !== "time") {
@ -63,10 +64,19 @@ function connect(event) {
if (searchObject == undefined) { if (searchObject == undefined) {
console.log("object new - init and add") console.log("object new - init and add")
var obj = {name: key, series: null} var obj = {name: key, series: null}
obj.series = chart.addLineSeries({ if (momentumIndicatorNames.includes(key)) {
title: key, obj.series = chart.addLineSeries({
lineWidth: 1, priceScaleId: 'left',
}) title: key,
lineWidth: 1,
})
}
else {
obj.series = chart.addLineSeries({
title: key,
lineWidth: 1,
})
}
obj.series.update({ obj.series.update({
time: indicators.time, time: indicators.time,
value: value}); value: value});

View File

@ -24,7 +24,7 @@
.legend { .legend {
position: absolute; position: absolute;
color: #050505; color: #050505;
left: 60px; left: 115px;
top: 99px; top: 99px;
z-index: 1; z-index: 1;
font-size: 16px; font-size: 16px;

View File

@ -116,7 +116,9 @@ class StrategyOrderLimitVykladaci(Strategy):
ic(key) ic(key)
#nejprve vyhodime z pendingbuys #nejprve vyhodime z pendingbuys
self.state.vars.pendingbuys.pop(key, False) self.state.vars.pendingbuys.pop(key, False)
self.interface.cancel(key) res = self.interface.cancel(key)
if res < 0:
print("ERROR CANCEL PENDING BUYS")
self.state.vars.pendingbuys={} self.state.vars.pendingbuys={}
self.state.vars.jevylozeno = 0 self.state.vars.jevylozeno = 0
print("cancel pending buys end") print("cancel pending buys end")

View File

@ -334,7 +334,11 @@ class Strategy:
if len(self.state.indicators) > 0: if len(self.state.indicators) > 0:
rt_out["indicators"] = dict() rt_out["indicators"] = dict()
for key, value in self.state.indicators.items(): for key, value in self.state.indicators.items():
rt_out["indicators"][key] = value[-1] #odchyceny pripad, kdy indikatory jsou inicializovane, ale jeste v nich nejsou data, pak do WS nic neposilame
try:
rt_out["indicators"][key]= value[-1]
except IndexError:
pass
print(rt_out) print(rt_out)
print("RTQUEUE INSERT") print("RTQUEUE INSERT")
@ -344,10 +348,6 @@ class Strategy:
print("RTQUEUE", self.rtqueue) print("RTQUEUE", self.rtqueue)
# inicializace poplatna typu strategie (např. u LIMITu dotažení existující limitky) # inicializace poplatna typu strategie (např. u LIMITu dotažení existující limitky)
def strat_init(self): def strat_init(self):
pass pass

View File

@ -14,6 +14,18 @@ from v2realbot.common.model import StrategyInstance, Runner
from typing import List from typing import List
import tomli import tomli
from v2realbot.config import DATA_DIR from v2realbot.config import DATA_DIR
import requests
def send_to_telegram(message):
apiToken = '5836666362:AAGPuzwp03tczMQTwTBiHW6VsZZ-1RCMAEE'
chatID = '5029424778'
apiURL = f'https://api.telegram.org/bot{apiToken}/sendMessage'
try:
response = requests.post(apiURL, json={'chat_id': chatID, 'text': message})
print(response.text)
except Exception as e:
print(e)
#datetime to timestamp #datetime to timestamp
def json_serial(obj): def json_serial(obj):