custom indicators fixed

This commit is contained in:
2026-04-09 17:00:43 -04:00
parent a70dcd954f
commit fd431516cc
17 changed files with 778 additions and 440 deletions

View File

@@ -15,6 +15,7 @@ import logging
import os
import signal
import sys
import time
from pathlib import Path
from typing import Optional
@@ -35,7 +36,7 @@ from dexorder.conda_manager import sync_packages, install_packages
from dexorder.events import EventType, UserEvent, DeliverySpec
from dexorder.impl.charting_api_impl import ChartingAPIImpl
from dexorder.impl.data_api_impl import DataAPIImpl
from dexorder.tools.python_tools import get_category_manager
from dexorder.tools.python_tools import get_category_manager, sanitize_name
from dexorder.tools.workspace_tools import get_workspace_store
from dexorder.tools.evaluate_indicator import evaluate_indicator
from dexorder.tools.backtest_strategy import backtest_strategy
@@ -57,6 +58,75 @@ def get_data_dir() -> Path:
return DATA_DIR
# =============================================================================
# Indicator Types Helpers
# =============================================================================
def _build_indicator_type_entry(meta: dict) -> dict:
"""Build an indicator_types workspace entry from indicator metadata dict."""
name = meta.get('name', '')
pandas_ta_name = f"custom_{sanitize_name(name).lower()}"
now = int(time.time())
return {
'pandas_ta_name': pandas_ta_name,
'display_name': name,
'description': meta.get('description', ''),
'metadata': {
'display_name': name,
'parameters': meta.get('parameters') or {},
'input_series': meta.get('input_series') or ['close'],
'output_columns': meta.get('output_columns') or [{'name': 'value'}],
'pane': meta.get('pane', 'separate'),
'filled_areas': meta.get('filled_areas') or [],
'bands': meta.get('bands') or [],
},
'created_at': now,
'modified_at': now,
}
def _upsert_indicator_type(workspace_store, category_manager, name: str) -> None:
"""Read indicator metadata from disk and upsert into indicator_types workspace store."""
read_result = category_manager.read('indicator', name)
if not read_result.get('exists') or not read_result.get('metadata'):
return
meta = read_result['metadata']
entry = _build_indicator_type_entry(meta)
pandas_ta_name = entry['pandas_ta_name']
# Preserve original created_at if already present
existing = workspace_store.read('indicator_types')
existing_types = (existing.get('data') or {}).get('types') or {}
if pandas_ta_name in existing_types:
entry['created_at'] = existing_types[pandas_ta_name].get('created_at', entry['created_at'])
workspace_store.patch('indicator_types', [
{'op': 'add', 'path': f'/types/{pandas_ta_name}', 'value': entry}
])
logging.info(f"Upserted indicator_types/{pandas_ta_name} for '{name}'")
def _populate_indicator_types_from_disk(workspace_store, category_manager) -> None:
"""Scan existing indicators and add any missing entries to indicator_types store."""
existing = workspace_store.read('indicator_types')
existing_types = (existing.get('data') or {}).get('types') or {}
list_result = category_manager.list_items('indicator')
items = list_result.get('items', [])
added = 0
for item in items:
item_name = item.get('name', '')
if not item_name:
continue
pandas_ta_name = f"custom_{sanitize_name(item_name).lower()}"
if pandas_ta_name not in existing_types:
_upsert_indicator_type(workspace_store, category_manager, item_name)
added += 1
if added > 0:
logging.info(f"Populated {added} indicator type(s) from disk into indicator_types store")
# =============================================================================
# Configuration
# =============================================================================
@@ -156,6 +226,9 @@ def create_mcp_server(config: Config, event_publisher: EventPublisher) -> Server
category_manager = get_category_manager(config.data_dir)
logging.info(f"Category manager initialized at {config.data_dir}")
# Populate indicator_types store from existing indicators on disk (migration/startup sync)
_populate_indicator_types_from_disk(workspace_store, category_manager)
@server.list_resources()
async def list_resources():
"""List available resources"""
@@ -674,6 +747,8 @@ def create_mcp_server(config: Config, event_publisher: EventPublisher) -> Server
logging.info(f"python_write '{arguments.get('name')}': returning {len(content)} items, {image_count} images")
else:
logging.info(f"python_write '{arguments.get('name')}': no execution result (category={arguments.get('category')})")
if result.get("success") and arguments.get("category") == "indicator":
_upsert_indicator_type(workspace_store, category_manager, arguments.get("name", ""))
return content
elif name == "python_edit":
result = category_manager.edit(
@@ -698,6 +773,8 @@ def create_mcp_server(config: Config, event_publisher: EventPublisher) -> Server
logging.info(f"python_edit '{arguments.get('name')}': returning {len(content)} items, {image_count} images")
else:
logging.info(f"python_edit '{arguments.get('name')}': no execution result")
if result.get("success") and arguments.get("category") == "indicator":
_upsert_indicator_type(workspace_store, category_manager, arguments.get("name", ""))
return content
elif name == "python_read":
return category_manager.read(