Strategies is now fixed and I am ready to implement backtesting.

This commit is contained in:
Rob 2024-09-25 14:27:56 -03:00
parent adedaaa540
commit 86843e8cb4
6 changed files with 180 additions and 57 deletions

View File

@ -346,6 +346,59 @@ class BrighterTrades:
# Save the new strategy (in both cache and database) and return the result.
return self.strategies.new_strategy(strategy_data)
def received_edit_strategy(self, data: dict) -> dict:
"""
Handles editing an existing strategy based on the provided data.
:param data: A dictionary containing the attributes of the strategy to edit.
:return: A dictionary containing success or failure information.
"""
# Extract user_name and strategy name from the data
user_name = data.get('user_name')
strategy_name = data.get('name')
if not user_name:
return {"success": False, "message": "User not specified"}
if not strategy_name:
return {"success": False, "message": "Strategy name not specified"}
# Fetch the user_id using the user_name
user_id = self.get_user_info(user_name=user_name, info='User_id')
if not user_id:
return {"success": False, "message": "User ID not found"}
# Retrieve the tbl_key using user_id and strategy name
filter_conditions = [('creator', user_id), ('name', strategy_name)]
strategy_row = self.data.get_rows_from_datacache(
cache_name='strategies',
filter_vals=filter_conditions,
include_tbl_key=True # Include tbl_key in the result
)
if strategy_row.empty:
return {"success": False, "message": "Strategy not found"}
# Ensure only one strategy is found
if len(strategy_row) > 1:
return {"success": False, "message": "Multiple strategies found. Please provide more specific information."}
# Extract the tbl_key
tbl_key = strategy_row.iloc[0]['tbl_key']
# Prepare the updated strategy data
strategy_data = {
"creator": user_id,
"name": strategy_name,
"workspace": data.get('workspace'),
"code": data.get('code'),
"stats": data.get('stats', {}),
"public": data.get('public', 0),
"fee": data.get('fee', None),
"tbl_key": tbl_key # Include the tbl_key to identify the strategy
}
# Call the edit_strategy method to update the strategy
return self.strategies.edit_strategy(strategy_data)
def delete_strategy(self, data: dict) -> str | dict:
"""
Deletes the specified strategy from the strategies instance and the configuration file.
@ -640,6 +693,10 @@ class BrighterTrades:
if r_data := self.received_new_strategy(msg_data):
return standard_reply("strategy_created", r_data)
if msg_type == 'edit_strategy':
if r_data := self.received_edit_strategy(msg_data):
return standard_reply("strategy_created", r_data)
if msg_type == 'new_trade':
if r_data := self.received_new_trade(msg_data):
return standard_reply("trade_created", r_data)

View File

@ -727,11 +727,12 @@ class DatabaseInteractions(SnapshotDataCache):
self.db = Database()
def get_rows_from_datacache(self, cache_name: str, filter_vals: list[tuple[str, Any]] = None,
key: str = None) -> pd.DataFrame | None:
key: str = None, include_tbl_key: bool = False) -> pd.DataFrame | None:
"""
Retrieves rows from the cache if available; otherwise, queries the database and caches the result.
:param key: Optional
:param include_tbl_key: If True, includes 'tbl_key' in the returned DataFrame.
:param key: Optional key to filter by 'tbl_key'.
:param cache_name: The key used to identify the cache (also the name of the database table).
:param filter_vals: A list of tuples, each containing a column name and the value(s) to filter by.
:return: A DataFrame containing the requested rows, or None if no matching rows are found.
@ -753,8 +754,11 @@ class DatabaseInteractions(SnapshotDataCache):
# Fallback: Fetch from the database and cache the result if necessary
result = self._fetch_from_database(cache_name, filter_vals)
# Take the key out on return.
return result.drop(columns=['tbl_key'], errors='ignore')
# Remove 'tbl_key' unless include_tbl_key is True
if not include_tbl_key:
result = result.drop(columns=['tbl_key'], errors='ignore')
return result
def _fetch_from_database(self, cache_name: str, filter_vals: List[tuple[str, Any]]) -> pd.DataFrame:
"""
@ -822,7 +826,12 @@ class DatabaseInteractions(SnapshotDataCache):
columns, values = columns + ('tbl_key',), values + (key,)
# Insert the row into the database
self.db.insert_row(table=cache_name, columns=columns, values=values)
last_inserted_id = self.db.insert_row(table=cache_name, columns=columns, values=values)
# Now include the 'id' in the columns and values when inserting into the cache
columns = ('id',) + columns
values = (last_inserted_id,) + values
# Insert the row into the cache
if skip_cache:
return
@ -869,18 +878,18 @@ class DatabaseInteractions(SnapshotDataCache):
# Execute the SQL query to remove the row from the database
self.db.execute_sql(sql, params)
def modify_datacache_item(self, cache_name: str, filter_vals: List[Tuple[str, any]], field_name: str,
new_data: any, key: str = None, overwrite: str = None) -> None:
def modify_datacache_item(self, cache_name: str, filter_vals: List[Tuple[str, any]], field_names: Tuple[str, ...],
new_values: Tuple[Any, ...], key: str = None, overwrite: str = None) -> None:
"""
Modifies a specific field in a row within the cache and updates the database accordingly.
Modifies specific fields in a row within the cache and updates the database accordingly.
:param overwrite:
:param key: optional key
:param cache_name: The name used to identify the cache (also the name of the database table).
:param cache_name: The name used to identify the cache.
:param filter_vals: A list of tuples containing column names and values to filter by.
:param field_name: The field to be updated.
:param new_data: The new data to be set.
:raises ValueError: If the row is not found in the cache or the database, or if multiple rows are returned.
:param field_names: A tuple of field names to be updated.
:param new_values: A tuple of new values corresponding to field_names.
:param key: Optional key to identify the entry.
:param overwrite: Column name(s) to use for overwriting in the cache.
:raises ValueError: If the row is not found or multiple rows are returned.
"""
if key:
filter_vals.insert(0, ('tbl_key', key))
@ -893,44 +902,30 @@ class DatabaseInteractions(SnapshotDataCache):
# Check if multiple rows are returned
if len(rows) > 1:
raise ValueError(f"Multiple rows found for {filter_vals}. Please provide a more specific filter.")
raise ValueError(f"Multiple rows found for {filter_vals}. Please provide more specific filter.")
# Ensure consistency when storing boolean data like 'visible'
if isinstance(new_data, bool):
# If storing in a system that supports booleans, you can store directly as boolean
updated_value = new_data # For cache systems that support booleans
# If your cache or database only supports strings, convert boolean to string
# updated_value = 'true' if new_data else 'false'
elif isinstance(new_data, str):
updated_value = new_data
else:
updated_value = json.dumps(new_data) # Convert non-string data to JSON string if necessary
# Update the DataFrame with the new value
rows[field_name] = updated_value
# Update the DataFrame with the new values
for field_name, new_value in zip(field_names, new_values):
rows[field_name] = new_value
# Get the cache instance
cache = self.get_cache(cache_name)
# Set the updated row in the cache
if isinstance(cache, RowBasedCache):
# For row-based cache, the 'tbl_key' must be in filter_vals
key_value = next((val for key, val in filter_vals if key == 'tbl_key'), None)
if key_value is None:
raise ValueError("'tbl_key' must be present in filter_vals for row-based caches.")
# Update the cache entry with the modified row
cache.add_entry(key=key_value, data=rows)
cache.add_entry(key=key, data=rows)
elif isinstance(cache, TableBasedCache):
# For table-based cache, use the existing query method to update the correct rows
# Use 'overwrite' to ensure correct row is updated
cache.add_table(rows, overwrite=overwrite)
else:
raise ValueError(f"Unsupported cache type for {cache_name}")
# Update the value in the database as well
sql_update = f"UPDATE {cache_name} SET {field_name} = ? " \
f"WHERE {' AND '.join([f'{col} = ?' for col, _ in filter_vals])}"
params = [updated_value] + [val for _, val in filter_vals]
# Update the values in the database
set_clause = ", ".join([f"{field} = ?" for field in field_names])
where_clause = " AND ".join([f"{col} = ?" for col, _ in filter_vals])
sql_update = f"UPDATE {cache_name} SET {set_clause} WHERE {where_clause}"
params = list(new_values) + [val for _, val in filter_vals]
# Execute the SQL update to modify the database
self.db.execute_sql(sql_update, params)

View File

@ -1,4 +1,5 @@
import json
import uuid
import pandas as pd
@ -199,18 +200,25 @@ class Strategies:
:return: A dictionary containing success or failure information.
"""
try:
# # Create a new Strategy object and add it to the list
# self.strat_list.append(Strategy(**data))
# Check if a strategy with the same name already exists for this user
filter_conditions = [('creator', data.get('creator')), ('name', data['name'])]
existing_strategy = self.data.get_rows_from_datacache(cache_name='strategies',
filter_vals=filter_conditions)
if not existing_strategy.empty:
return {"success": False, "message": "A strategy with this name already exists"}
# Serialize complex data fields like workspace and stats
workspace_serialized = json.dumps(data['workspace']) if isinstance(data['workspace'], dict) else data[
'workspace']
stats_serialized = json.dumps(data.get('stats', {})) # Convert stats to a JSON string
# generate a unique identifier
tbl_key = str(uuid.uuid4())
# Insert the strategy into the database and cache
self.data.insert_row_into_datacache(
cache_name='strategies',
columns=("creator", "name", "workspace", "code", "stats", "public", "fee"),
columns=("creator", "name", "workspace", "code", "stats", "public", "fee", 'tbl_key'),
values=(
data.get('creator'),
data['name'],
@ -218,7 +226,8 @@ class Strategies:
data['code'],
stats_serialized, # Serialized stats
data.get('public', False),
data.get('fee', 0)
data.get('fee', 0),
tbl_key
)
)
@ -243,6 +252,51 @@ class Strategies:
filter_vals=[('creator', user_id), ('name', name)]
)
def edit_strategy(self, data: dict) -> dict:
"""
Updates an existing strategy in the cache and database.
:param data: A dictionary containing the updated strategy data.
:return: A dictionary containing success or failure information.
"""
try:
tbl_key = data['tbl_key'] # The unique identifier for the strategy
# Serialize complex data fields like workspace and stats
workspace_serialized = json.dumps(data['workspace']) if isinstance(data['workspace'], dict) else data[
'workspace']
stats_serialized = json.dumps(data.get('stats', {})) # Convert stats to a JSON string
# Prepare the columns and values for the update
columns = ("creator", "name", "workspace", "code", "stats", "public", "fee", "tbl_key")
values = (
data.get('creator'),
data['name'],
workspace_serialized, # Serialized workspace
data['code'],
stats_serialized, # Serialized stats
data.get('public', False),
data.get('fee', 0),
tbl_key
)
# Update the strategy in the database and cache
self.data.modify_datacache_item(
cache_name='strategies',
filter_vals=[('tbl_key', tbl_key)],
field_names=columns,
new_values=values,
key=tbl_key,
overwrite='tbl_key' # Use 'tbl_key' to identify the entry to overwrite
)
# Return success message
return {"success": True, "message": "Strategy updated successfully"}
except Exception as e:
# Handle exceptions and return failure message
return {"success": False, "message": f"Failed to update strategy: {str(e)}"}
def get_all_strategy_names(self, user_id) -> list | None:
"""
Return a list of all strategy names stored in the cache or database.

View File

@ -108,9 +108,10 @@ class BaseUser:
self.data.modify_datacache_item(
cache_name='users',
filter_vals=[('user_name', username)],
field_name=field_name,
new_data=new_data,
overwrite='user_name')
field_names=(field_name,),
new_values=(new_data,),
overwrite='user_name'
)
class UserAccountManagement(BaseUser):

View File

@ -361,12 +361,22 @@ class Indicators:
return
# Set visibility for all indicators off
self.cache_manager.modify_datacache_item('indicators', [('creator', str(user_id))],
field_name='visible', new_data=False, overwrite='name')
self.cache_manager.modify_datacache_item(
cache_name='indicators',
filter_vals=[('creator', str(user_id))],
field_names=('visible',),
new_values=(False,),
overwrite='name'
)
# Set visibility for the specified indicators on
self.cache_manager.modify_datacache_item('indicators', [('creator', str(user_id)), ('name', indicator_names)],
field_name='visible', new_data=True, overwrite='name')
self.cache_manager.modify_datacache_item(
cache_name='indicators',
filter_vals=[('creator', str(user_id)), ('name', indicator_names)],
field_names=('visible',),
new_values=(True,),
overwrite='name'
)
def edit_indicator(self, user_name: str, params: dict):
"""
@ -397,8 +407,8 @@ class Indicators:
self.cache_manager.modify_datacache_item(
'indicators',
[('creator', str(user_id)), ('name', indicator_name)],
field_name='properties',
new_data=new_properties,
field_name=('properties',),
new_data=(new_properties,),
overwrite='name'
)
@ -414,8 +424,8 @@ class Indicators:
self.cache_manager.modify_datacache_item(
'indicators',
[('creator', str(user_id)), ('name', indicator_name)],
field_name='source',
new_data=new_source,
field_name=('source',),
new_data=(new_source,),
overwrite='name'
)
@ -427,8 +437,8 @@ class Indicators:
self.cache_manager.modify_datacache_item(
'indicators',
[('creator', str(user_id)), ('name', indicator_name)],
field_name='visible',
new_data=new_visible,
field_name=('visible',),
new_data=(new_visible,),
overwrite='name'
)

View File

@ -228,10 +228,16 @@ class Strategies {
strategy_name: name // Strategy name to be deleted
};
// Corrected call to send message type and data separately
window.UI.data.comms.sendToApp('delete_strategy', deleteData);
// Remove the strategy from the local array
this.strategies = this.strategies.filter(strat => strat.name !== name);
// Update the UI
this.update_html();
}
// Initialize strategies
initialize() {
this.target = document.getElementById(this.target_id);