diff --git a/v2realbot/common/__pycache__/model.cpython-310.pyc b/v2realbot/common/__pycache__/model.cpython-310.pyc index 6329a60..6286240 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 dec2925..4ce9b1c 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -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 diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 4ad56c4..5d0cd8c 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -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 diff --git a/v2realbot/main.py b/v2realbot/main.py index d728bca..9167505 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -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") diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index 7555b30..ee74251 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -559,13 +559,28 @@