frontend config added

This commit is contained in:
David Brazda
2023-09-04 20:10:46 +02:00
parent 7fabc5887c
commit a22aedf978
10 changed files with 446 additions and 18 deletions

View File

@ -24,6 +24,12 @@ from alpaca.data.enums import Exchange
# return user.id # return user.id
# raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}") # raise HTTPException(status_code=404, detail=f"Could not find user with id: {id}")
# Define a Pydantic model for input data
class ConfigItem(BaseModel):
id: Optional[int] = None
item_name: str
json_data: str
class Intervals(BaseModel): class Intervals(BaseModel):
start: str start: str
end: str end: str

View File

@ -6,7 +6,7 @@ from alpaca.data.requests import StockTradesRequest, StockBarsRequest
from alpaca.data.enums import DataFeed from alpaca.data.enums import DataFeed
from alpaca.data.timeframe import TimeFrame from alpaca.data.timeframe import TimeFrame
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem
from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram
from v2realbot.utils.ilog import delete_logs from v2realbot.utils.ilog import delete_logs
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType
@ -761,6 +761,7 @@ def get_all_archived_runners_detail():
c = conn.cursor() c = conn.cursor()
res = c.execute(f"SELECT data FROM runner_detail") res = c.execute(f"SELECT data FROM runner_detail")
finally: finally:
conn.row_factory = None
pool.release_connection(conn) pool.release_connection(conn)
return 0, res.fetchall() return 0, res.fetchall()
@ -780,6 +781,7 @@ def get_archived_runner_details_byID(id: UUID):
result = c.execute(f"SELECT data FROM runner_detail WHERE runner_id='{str(id)}'") result = c.execute(f"SELECT data FROM runner_detail WHERE runner_id='{str(id)}'")
res= result.fetchone() res= result.fetchone()
finally: finally:
conn.row_factory = None
pool.release_connection(conn) pool.release_connection(conn)
if res==None: if res==None:
return -2, "not found" return -2, "not found"
@ -797,6 +799,114 @@ def insert_archive_detail(archdetail: RunArchiveDetail):
pool.release_connection(conn) pool.release_connection(conn)
return res.rowcount return res.rowcount
# region TESTLISTS db services
def get_testlists():
try:
conn = pool.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT id, name, dates FROM test_list")
rows = cursor.fetchall()
finally:
pool.release_connection(conn)
testlists = []
for row in rows:
print(row)
testlist = TestList(id=row[0], name=row[1], dates=json.loads(row[2]))
testlists.append(testlist)
return 0, testlists
# endregion
# region CONFIG db services
def get_all_config_items():
conn = pool.get_connection()
try:
cursor = conn.cursor()
cursor.execute('SELECT id, item_name, json_data FROM config_table')
config_items = [{"id": row[0], "item_name": row[1], "json_data": row[2]} for row in cursor.fetchall()]
finally:
pool.release_connection(conn)
return 0, config_items
# Function to get a config item by ID
def get_config_item_by_id(item_id):
conn = pool.get_connection()
try:
cursor = conn.cursor()
cursor.execute('SELECT item_name, json_data FROM config_table WHERE id = ?', (item_id,))
row = cursor.fetchone()
finally:
pool.release_connection(conn)
if row is None:
return -2, "not found"
else:
return 0, {"item_name": row[0], "json_data": row[1]}
# Function to get a config item by ID
def get_config_item_by_name(item_name):
#print(item_name)
conn = pool.get_connection()
try:
cursor = conn.cursor()
query = f"SELECT item_name, json_data FROM config_table WHERE item_name = '{item_name}'"
#print(query)
cursor.execute(query)
row = cursor.fetchone()
#print(row)
finally:
pool.release_connection(conn)
if row is None:
return -2, "not found"
else:
return 0, {"item_name": row[0], "json_data": row[1]}
# Function to create a new config item
def create_config_item(config_item: ConfigItem):
conn = pool.get_connection()
try:
try:
cursor = conn.cursor()
cursor.execute('INSERT INTO config_table (item_name, json_data) VALUES (?, ?)', (config_item.item_name, config_item.json_data))
item_id = cursor.lastrowid
conn.commit()
print(item_id)
finally:
pool.release_connection(conn)
return 0, {"id": item_id, "item_name":config_item.item_name, "json_data":config_item.json_data}
except Exception as e:
return -2, str(e)
# Function to update a config item by ID
def update_config_item(item_id, config_item: ConfigItem):
conn = pool.get_connection()
try:
try:
cursor = conn.cursor()
cursor.execute('UPDATE config_table SET item_name = ?, json_data = ? WHERE id = ?', (config_item.item_name, config_item.json_data, item_id))
conn.commit()
finally:
pool.release_connection(conn)
return 0, {"id": item_id, **config_item.dict()}
except Exception as e:
return -2, str(e)
# Function to delete a config item by ID
def delete_config_item(item_id):
conn = pool.get_connection()
try:
cursor = conn.cursor()
cursor.execute('DELETE FROM config_table WHERE id = ?', (item_id,))
conn.commit()
finally:
pool.release_connection(conn)
return 0, {"id": item_id}
# endregion
#returns b #returns b
def get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, datetime_object_to: datetime, timeframe: TimeFrame): def get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, datetime_object_to: datetime, timeframe: TimeFrame):
"""Returns Bar object """Returns Bar object

View File

@ -14,7 +14,7 @@ import uvicorn
from uuid import UUID from uuid import UUID
import v2realbot.controller.services as cs import v2realbot.controller.services as cs
from v2realbot.utils.ilog import get_log_window from v2realbot.utils.ilog import get_log_window
from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveDetail, Bar, RunArchiveChange, TestList from v2realbot.common.model import StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem
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
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@ -354,18 +354,11 @@ def create_record(testlist: TestList):
# API endpoint to retrieve all records # API endpoint to retrieve all records
@app.get('/testlists/', dependencies=[Depends(api_key_auth)]) @app.get('/testlists/', dependencies=[Depends(api_key_auth)])
def get_testlists(): def get_testlists():
conn = pool.get_connection() res, sada = cs.get_testlists()
cursor = conn.cursor() if res == 0:
cursor.execute("SELECT id, name, dates FROM test_list") return sada
rows = cursor.fetchall() else:
pool.release_connection(conn) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
testlists = []
for row in rows:
testlist = TestList(id=row[0], name=row[1], dates=json.loads(row[2]))
testlists.append(testlist)
return testlists
# API endpoint to retrieve a single record by ID # API endpoint to retrieve a single record by ID
@app.get('/testlists/{record_id}') @app.get('/testlists/{record_id}')
@ -416,6 +409,73 @@ def delete_testlist(record_id: str):
return {'message': 'Record deleted'} return {'message': 'Record deleted'}
# region CONFIG APIS
# Get all config items
@app.get("/config-items/", dependencies=[Depends(api_key_auth)])
def get_all_items() -> list[ConfigItem]:
res, sada = cs.get_all_config_items()
if res == 0:
return sada
else:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
# Get a config item by ID
@app.get("/config-items/{item_id}", dependencies=[Depends(api_key_auth)])
def get_item(item_id: int)-> ConfigItem:
res, sada = cs.get_config_item_by_id(item_id)
if res == 0:
return sada
else:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
# Get a config item by Name
@app.get("/config-items-by-name/", dependencies=[Depends(api_key_auth)])
def get_item(item_name: str)-> ConfigItem:
res, sada = cs.get_config_item_by_name(item_name)
if res == 0:
return sada
else:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
# Create a new config item
@app.post("/config-items/", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK)
def create_item(config_item: ConfigItem) -> ConfigItem:
res, sada = cs.create_config_item(config_item)
if res == 0: return sada
else:
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error not created: {res}:{id} {sada}")
# Update a config item by ID
@app.put("/config-items/{item_id}", dependencies=[Depends(api_key_auth)])
def update_item(item_id: int, config_item: ConfigItem) -> ConfigItem:
res, sada = cs.get_config_item_by_id(item_id)
if res != 0:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
res, sada = cs.update_config_item(item_id, config_item)
if res == 0: return sada
else:
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error not created: {res}:{id}")
# Delete a config item by ID
@app.delete("/config-items/{item_id}", dependencies=[Depends(api_key_auth)])
def delete_item(item_id: int) -> dict:
res, sada = cs.get_config_item_by_id(item_id)
if res != 0:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
res, sada = cs.delete_config_item(item_id)
if res == 0: return sada
else:
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error not created: {res}:{id}")
# endregion
# Thread function to insert data from the queue into the database # Thread function to insert data from the queue into the database
def insert_queue2db(): def insert_queue2db():
print("starting insert_queue2db thread") print("starting insert_queue2db thread")

View File

@ -559,13 +559,28 @@
<h4>Config</h4> <h4>Config</h4>
</label> </label>
<div id="configInner" class="collapse show"> <div id="configInner" class="collapse show">
config options <form id="configForm">
<label for="configList">Select an Item:</label>
<select id="configList"></select><br><br>
<label for="itemName">Item Name:</label>
<input type="text" id="itemName"><br><br>
<label for="jsonTextarea">JSON Data:</label><br>
<textarea id="jsonTextarea" rows="10" cols="50"></textarea><br><br>
<button type="button" id="saveButton">Save</button>
<button type="button" id="addButton">Add</button>
<button type="button" id="deleteButton">Delete</button>
<button type="button" id="cancelButton">Cancel</button>
</form>
</div> </div>
</div> </div>
<div id="bottomContainer" class="flex-items" style="height: 800px"> <div id="bottomContainer" class="flex-items" style="height: 800px">
<BR> <BR>
</div> </div>
</div> </div>
<script src="/static/js/config.js"></script>
<script type="text/javascript" src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script> <script type="text/javascript" src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
<script src="/static/js/jquery.serializejson.js"></script> <script src="/static/js/jquery.serializejson.js"></script>
<script src="/static/js/utils.js"></script> <script src="/static/js/utils.js"></script>
@ -575,5 +590,6 @@
<script src="/static/js/realtimechart.js"></script> <script src="/static/js/realtimechart.js"></script>
<script src="/static/js/mytables.js"></script> <script src="/static/js/mytables.js"></script>
<script src="/static/js/testlist.js"></script> <script src="/static/js/testlist.js"></script>
<script src="/static/js/configform.js"></script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,9 @@
var tradeDetails = new Map(); var tradeDetails = new Map();
var toolTip = null var toolTip = null
var CHART_SHOW_TEXT = false var CHART_SHOW_TEXT = get_from_config("CHART_SHOW_TEXT", false)
console.log("CHART_SHOW_TEXT archchart", CHART_SHOW_TEXT)
// var vwapSeries = null // var vwapSeries = null
// var volumeSeries = null // var volumeSeries = null
var markersLine = null var markersLine = null
@ -11,6 +14,7 @@ var slLine = []
//input array object bars = { high: [1,2,3], time: [1,2,3], close: [2,2,2]...} //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},..] //output array [{ time: 111, open: 11, high: 33, low: 333, close: 333},..]
function transform_data(data) { function transform_data(data) {
var SHOW_SL_DIGITS = get_from_config("SHOW_SL_DIGITS", true)
transformed = [] transformed = []
//get basic bars, volume and vvwap //get basic bars, volume and vvwap
var bars = [] var bars = []
@ -64,7 +68,8 @@ function transform_data(data) {
sline_markers["position"] = "inBar" sline_markers["position"] = "inBar"
sline_markers["color"] = "#f5aa42" sline_markers["color"] = "#f5aa42"
//sline_markers["shape"] = "circle" //sline_markers["shape"] = "circle"
sline_markers["text"] = histRecord.sl_val.toFixed(3) console.log("SHOW_SL_DIGITS",SHOW_SL_DIGITS)
sline_markers["text"] = SHOW_SL_DIGITS ? histRecord.sl_val.toFixed(3) : ""
sl_line_markers_sada.push(sline_markers) sl_line_markers_sada.push(sline_markers)
if (index === array.length - 1) { if (index === array.length - 1) {

View File

@ -0,0 +1,64 @@
//JS code for using config value on the frontend
configData = {}
function get_from_config(name, def_value) {
console.log("required", name, configData)
if ((configData["JS"]) && (configData["JS"][name] !== undefined)) {
console.log("returned from config", configData["JS"][name])
return configData["JS"][name]
}
else {
console.log("returned def_value", def_value)
return def_value
}
}
$(document).ready(function () {
const apiBaseUrl = '';
// Function to populate the config list and load JSON data initially
function loadConfig(configName) {
const rec = new Object()
rec.item_name = configName
$.ajax({
url: `${apiBaseUrl}/config-items-by-name/`,
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
METHOD: 'GET',
contentType: "application/json",
dataType: "json",
data: rec,
success: function (data) {
console.log(data)
try {
configData[configName] = JSON.parse(data.json_data)
console.log(configData)
console.log("jsme tu")
indConfig = configData["JS"].indConfig
console.log("after")
console.log(JSON.stringify(indConfig, null,null, 2))
console.log("before CHART_SHOW_TEXT",CHART_SHOW_TEXT)
var CHART_SHOW_TEXT = configData["JS"].CHART_SHOW_TEXT
console.log("after CHART_SHOW_TEXT",CHART_SHOW_TEXT)
}
catch (error) {
window.alert(`Nešlo rozparsovat JSON_data string ${configName}`, error.message)
}
},
error: function(xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
window.alert(`Nešlo dotáhnout config nastaveni z db ${configName}`, JSON.stringify(xhr));
console.log(JSON.stringify(xhr));
}
});
}
const jsConfigName = "JS"
//naloadovan config
loadConfig(jsConfigName)
});

View File

@ -0,0 +1,166 @@
//JS code for FRONTEND CONFIG FORM
$(document).ready(function () {
// API Base URL
const apiBaseUrl = '';
let editingItemId = null;
var localArray = []
// Function to populate the config list and load JSON data initially
function populateConfigList(to_select = null) {
$.ajax({
url: `${apiBaseUrl}/config-items/`,
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
type: 'GET',
success: function (data) {
const configList = $('#configList');
configList.empty();
localArray = data
selected = ""
data.forEach((item, index, array) => {
selected = "";
//pokud prijde id ktere mame vybrat vybereme to, jinak vybereme prvni
if (((to_select !== null) && (to_select == item.id)) || ((to_select == null) && (index==0))) {
selected = "SELECTED"
$('#itemName').val(item.item_name);
$('#jsonTextarea').val(item.json_data);
editingItemId = item.id;
}
configList.append(`<option value="${item.id}" ${selected}>${item.item_name}</option>`);
});
}
});
}
//
function showJSONdata(itemId) {
localArray.forEach((item, index, array) => {
if (item.id == itemId) {
$('#itemName').val(item.item_name);
$('#jsonTextarea').val(item.json_data);
editingItemId = itemId;
}
});
}
$('#cancelButton').attr('disabled', true);
// Populate the config list and load JSON data and item name initially
populateConfigList();
// Event listener for config list change
$('#configList').change(function () {
const selectedItem = $(this).val();
console.log(selectedItem)
if (selectedItem) {
showJSONdata(selectedItem);
}
});
// Save or add a config item
$('#saveButton').click(function () {
const itemName = $('#itemName').val();
const jsonData = $('#jsonTextarea').val();
var validformat = false
$('#addButton').attr('disabled', false);
$('#deleteButton').attr('disabled', false);
try {
var parsedJSON = JSON.parse(jsonData)
validformat = true
}
catch (error) {
alert("Not valid JSON", error.message)
}
if (validformat) {
var confirmed = window.confirm("Sure?");
if (editingItemId && confirmed) {
// Update the selected item with the modified data using API
$.ajax({
url: `${apiBaseUrl}/config-items/${editingItemId}`,
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify({ item_name: itemName, json_data: jsonData }),
success: function () {
alert('Data saved successfully.');
populateConfigList(editingItemId); // Refresh the config list
}
});
} else if (confirmed) {
// Add a new config item using API
$.ajax({
url: `${apiBaseUrl}/config-items/`,
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
type: "POST",
contentType: "application/json",
dataType: "json",
data: JSON.stringify({ item_name: itemName, json_data: jsonData }),
success: function (data) {
console.log(data)
alert('New item added successfully.');
populateConfigList(data.id); // Refresh the config list
}
});
}
}
});
// Add a new config item (populates a new record in the form)
$('#addButton').click(function () {
$('#configList').val('');
$('#itemName').val('');
$('#jsonTextarea').val('');
editingItemId = null;
$('#addButton').attr('disabled', true);
$('#deleteButton').attr('disabled', true);
$('#cancelButton').attr('disabled', false);
});
// Add a new config item (populates a new record in the form)
$('#cancelButton').click(function () {
$('#addButton').attr('disabled', false);
$('#deleteButton').attr('disabled', false);
$('#cancelButton').attr('disabled', true);
populateConfigList();
});
// Delete a config item
$('#deleteButton').click(function () {
if (editingItemId == null) {
$('#configList').val('');
$('#itemName').val('');
$('#jsonTextarea').val('');
}
else {
var confirmed = window.confirm("Confirm?");
if (confirmed) {
$.ajax({
url: `${apiBaseUrl}/config-items/${editingItemId}`,
beforeSend: function (xhr) {
xhr.setRequestHeader('X-API-Key',
API_KEY); },
type: 'DELETE',
success: function () {
alert('Item deleted successfully.');
populateConfigList(); // Refresh the config list
}
});
}
}
});
});

View File

@ -525,7 +525,7 @@ var stratinRecords =
{data: 'history', visible: false}, {data: 'history', visible: false},
{data: 'id', visible: true}, {data: 'id', visible: true},
], ],
paging: false, paging: true,
processing: false, processing: false,
columnDefs: [{ columnDefs: [{
targets: 12, targets: 12,

View File

@ -50,6 +50,7 @@ indConfig = [ {name: "ema", titlevisible: false, embed: true, display: true, pri
{name: "ppo", titlevisible: true, embed: true, display: true, priceScaleId: "middle", lastValueVisible: false}, {name: "ppo", titlevisible: true, embed: true, display: true, priceScaleId: "middle", lastValueVisible: false},
{name: "stoch2", titlevisible: true, embed: true, display: true, priceScaleId: "middle", lastValueVisible: false}, {name: "stoch2", titlevisible: true, embed: true, display: true, priceScaleId: "middle", lastValueVisible: false},
{name: "sec_price", titlevisible: true, embed: true, display: true, priceScaleId: "right", lastValueVisible: false},] {name: "sec_price", titlevisible: true, embed: true, display: true, priceScaleId: "right", lastValueVisible: false},]
console.log(JSON.stringify(indConfig, null,null, 2))
function initialize_statusheader() { function initialize_statusheader() {