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
# 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):
start: 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.timeframe import TimeFrame
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.ilog import delete_logs
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType
@ -761,6 +761,7 @@ def get_all_archived_runners_detail():
c = conn.cursor()
res = c.execute(f"SELECT data FROM runner_detail")
finally:
conn.row_factory = None
pool.release_connection(conn)
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)}'")
res= result.fetchone()
finally:
conn.row_factory = None
pool.release_connection(conn)
if res==None:
return -2, "not found"
@ -797,6 +799,114 @@ def insert_archive_detail(archdetail: RunArchiveDetail):
pool.release_connection(conn)
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
def get_alpaca_history_bars(symbol: str, datetime_object_from: datetime, datetime_object_to: datetime, timeframe: TimeFrame):
"""Returns Bar object

View File

@ -14,7 +14,7 @@ import uvicorn
from uuid import UUID
import v2realbot.controller.services as cs
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.responses import HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles
@ -354,18 +354,11 @@ def create_record(testlist: TestList):
# API endpoint to retrieve all records
@app.get('/testlists/', dependencies=[Depends(api_key_auth)])
def get_testlists():
conn = pool.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT id, name, dates FROM test_list")
rows = cursor.fetchall()
pool.release_connection(conn)
testlists = []
for row in rows:
testlist = TestList(id=row[0], name=row[1], dates=json.loads(row[2]))
testlists.append(testlist)
return testlists
res, sada = cs.get_testlists()
if res == 0:
return sada
else:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No data found")
# API endpoint to retrieve a single record by ID
@app.get('/testlists/{record_id}')
@ -416,6 +409,73 @@ def delete_testlist(record_id: str):
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
def insert_queue2db():
print("starting insert_queue2db thread")

View File

@ -559,13 +559,28 @@
<h4>Config</h4>
</label>
<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 id="bottomContainer" class="flex-items" style="height: 800px">
<BR>
</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 src="/static/js/jquery.serializejson.js"></script>
<script src="/static/js/utils.js"></script>
@ -575,5 +590,6 @@
<script src="/static/js/realtimechart.js"></script>
<script src="/static/js/mytables.js"></script>
<script src="/static/js/testlist.js"></script>
<script src="/static/js/configform.js"></script>
</body>
</html>

View File

@ -1,6 +1,9 @@
var tradeDetails = new Map();
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 volumeSeries = 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]...}
//output array [{ time: 111, open: 11, high: 33, low: 333, close: 333},..]
function transform_data(data) {
var SHOW_SL_DIGITS = get_from_config("SHOW_SL_DIGITS", true)
transformed = []
//get basic bars, volume and vvwap
var bars = []
@ -64,7 +68,8 @@ function transform_data(data) {
sline_markers["position"] = "inBar"
sline_markers["color"] = "#f5aa42"
//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)
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: 'id', visible: true},
],
paging: false,
paging: true,
processing: false,
columnDefs: [{
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: "stoch2", titlevisible: true, embed: true, display: true, priceScaleId: "middle", 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() {