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],
blockbuy = 0,
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
toml_string = """
[[strategies]]
@ -106,7 +109,7 @@ def next(data, state: StrategyState):
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
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):
price = price2dec(float(price - curve[i]))
if price == last_price:
@ -133,24 +136,55 @@ def next(data, state: StrategyState):
return 0
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.bars.hlcc4)
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
state.indicators.ema = [trunc(i,3) for i in state.indicators.ema]
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:
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 rising",isrising(state.indicators.ema,state.vars.Trend))
#and data['index'] > state.vars.lastbuyindex+state.vars.Trend:
#neni vylozeno muzeme nakupovat
#SLOPE ANGLE PROTECTION
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:
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")
ic(data['updated'])
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 spoustet 1x za X iteraci nebo cas
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:
maxprice = max(state.vars.pendingbuys.values())
print("max cena v orderbuys", maxprice)
@ -264,6 +298,8 @@ def init(state: StrategyState):
#place to declare new vars
print("INIT v main",state.name)
state.indicators['ema'] = []
state.indicators['slope'] = []
#state.indicators['roc'] = []
def main():

View File

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

View File

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

View File

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

View File

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

View File

@ -79,7 +79,13 @@
</div>
<div id="stratin-table" class="flex-items">
<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%">
<thead>
<tr>
@ -192,6 +198,28 @@
</form>
</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 class="modal-dialog">
<form method="post" id="runForm">

View File

@ -1,5 +1,5 @@
//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);
chart.applyOptions({ timeScale: { visible: true, timeVisible: true, secondsVisible: true }, crosshair: {
mode: LightweightCharts.CrosshairMode.Normal, labelVisible: true

View File

@ -64,6 +64,7 @@ $(document).ready(function () {
$('#button_stop').attr('disabled','disabled');
$('#button_edit').attr('disabled','disabled');
$('#button_dup').attr('disabled','disabled');
$('#button_copy').attr('disabled','disabled');
$('#button_delete').attr('disabled','disabled');
$('#button_run').attr('disabled','disabled');
@ -72,6 +73,7 @@ $(document).ready(function () {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
$('#button_dup').attr('disabled','disabled');
$('#button_copy').attr('disabled','disabled');
$('#button_edit').attr('disabled','disabled');
$('#button_delete').attr('disabled','disabled');
$('#button_run').attr('disabled','disabled');
@ -79,6 +81,7 @@ $(document).ready(function () {
stratinRecords.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
$('#button_dup').attr('disabled',false);
$('#button_copy').attr('disabled',false);
$('#button_edit').attr('disabled',false);
$('#button_delete').attr('disabled',false);
$('#button_run').attr('disabled',false);
@ -105,6 +108,28 @@ $(document).ready(function () {
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_dup').click(function () {
row = stratinRecords.row('.selected').data();
@ -250,6 +275,7 @@ $(document).ready(function () {
$('#action').val('addRecord');
$('#save').val('Add');
});
//edit button
$('#button_edit').click(function () {
row = stratinRecords.row('.selected').data();
@ -279,6 +305,10 @@ $(document).ready(function () {
$('#save').val('Delete');
});
//json add button
$('#button_add_json').click(function () {
window.$('#jsonModal').modal('show');
});
} );
//stratin table
@ -441,7 +471,7 @@ $("#recordModal").on('submit','#recordForm', function(event){
})
}
else {
//code for add
//code for edit
event.preventDefault();
$('#save').attr('disabled','disabled');
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){
event.preventDefault();
$('#delete').attr('disabled','disabled');

View File

@ -1,3 +1,4 @@
const momentumIndicatorNames = ["roc", "slope"]
var indList = []
var ws = null;
function connect(event) {
@ -54,7 +55,7 @@ function connect(event) {
if (parsed_data.hasOwnProperty("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) {
for (const [key, value] of Object.entries(indicators)) {
if (key !== "time") {
@ -63,10 +64,19 @@ function connect(event) {
if (searchObject == undefined) {
console.log("object new - init and add")
var obj = {name: key, series: null}
if (momentumIndicatorNames.includes(key)) {
obj.series = chart.addLineSeries({
priceScaleId: 'left',
title: key,
lineWidth: 1,
})
}
else {
obj.series = chart.addLineSeries({
title: key,
lineWidth: 1,
})
}
obj.series.update({
time: indicators.time,
value: value});

View File

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

View File

@ -116,7 +116,9 @@ class StrategyOrderLimitVykladaci(Strategy):
ic(key)
#nejprve vyhodime z pendingbuys
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.jevylozeno = 0
print("cancel pending buys end")

View File

@ -334,7 +334,11 @@ class Strategy:
if len(self.state.indicators) > 0:
rt_out["indicators"] = dict()
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("RTQUEUE INSERT")
@ -344,10 +348,6 @@ class Strategy:
print("RTQUEUE", self.rtqueue)
# inicializace poplatna typu strategie (např. u LIMITu dotažení existující limitky)
def strat_init(self):
pass

View File

@ -14,6 +14,18 @@ from v2realbot.common.model import StrategyInstance, Runner
from typing import List
import tomli
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
def json_serial(obj):