Skip to content

Search for public symphonies meeting specific criteria.

"""
Search for public symphonies meeting specific criteria.
This example shows available symphonies with their metadata and runs a quick backtest.
"""

from composer import ComposerClient
import os
from dotenv import load_dotenv

load_dotenv()

# Initialize client
client = ComposerClient(
    api_key=os.getenv("COMPOSER_API_KEY"),
    api_secret=os.getenv("COMPOSER_API_SECRET"),
)

print("Searching for public symphonies...\n")

# Search for symphonies (without filter since oos fields may not be populated)
results = client.search.search_symphonies_v2(
    offset=0,
)

print(f"Found {len(results)} public symphonies\n")

print("="*120)
print("SEARCH RESULTS")
print("="*120)

# Display results in a table with available fields
print(f"\n{'#':<3} {'Name':<40} {'Assets':>8} {'Filters':>8} {'Rebalance':<10} {'ID':<12}")
print("-"*120)

for i, symphony in enumerate(results[:15], 1):
    name = (symphony.name[:37] + "...") if symphony.name and len(symphony.name) > 40 else (symphony.name or "N/A")

    assets = symphony.num_node_asset or 0
    filters = symphony.num_node_filter or 0
    rebal = symphony.rebalance_frequency or "N/A"
    sid = symphony.symphony_sid[:12]

    print(f"{i:<3} {name:<40} {assets:>8} {filters:>8} {rebal:<10} {sid:<12}")

print("\n" + "="*120)

# Show details for top result including AI description
if results:
    top_result = results[0]
    print(f"\n--- TOP RESULT DETAILS ---")
    print(f"\nName: {top_result.name or 'N/A'}")
    print(f"ID: {top_result.symphony_sid}")
    print(f"Description: {top_result.description or 'N/A'}")
    print(f"Asset Classes: {', '.join(top_result.asset_classes) if top_result.asset_classes else 'N/A'}")
    print(f"Rebalance Frequency: {top_result.rebalance_frequency or 'N/A'}")
    print(f"\nStructure:")
    print(f"  - Assets: {top_result.num_node_asset}")
    print(f"  - Filters: {top_result.num_node_filter}")
    print(f"  - Groups: {top_result.num_node_group}")
    print(f"  - IF Nodes: {top_result.num_node_if}")

    if top_result.ai_description:
        ai = top_result.ai_description
        print(f"\nAI Summary: {ai.summary or 'N/A'}")
        print(f"\nCategories: {ai.categories or 'N/A'}")

    # Run a quick backtest to get performance metrics
    print(f"\n--- RUNNING 30-DAY BACKTEST ---")
    print("-"*50)

    from datetime import datetime, timedelta

    end_date = datetime.now().date().isoformat()
    start_date = (datetime.now() - timedelta(days=30)).date().isoformat()

    backtest = client.public_symphony.backtest_symphony_v2(
        top_result.symphony_sid,
        start_date=start_date,
        end_date=end_date,
    )

    if backtest.stats:
        stats = backtest.stats
        print(f"Sharpe Ratio: {stats.sharpe_ratio:.2f}" if stats.sharpe_ratio else "Sharpe: N/A")
        print(f"Cumulative Return: {stats.cumulative_return*100:+.2f}%" if stats.cumulative_return else "Cumulative Return: N/A")
        print(f"Max Drawdown: {stats.max_drawdown*100:.2f}%" if stats.max_drawdown else "Max Drawdown: N/A")
        print(f"Annualized Return: {stats.annualized_rate_of_return*100:+.2f}%" if stats.annualized_rate_of_return else "Annualized Return: N/A")

    # Show daily values
    if backtest.dvm_capital:
        print(f"\nDaily values (last 5 days):")
        dates = sorted(backtest.dvm_capital.keys(), reverse=True)[:5]
        for date in dates:
            if top_result.symphony_sid in backtest.dvm_capital[date]:
                value = backtest.dvm_capital[date][top_result.symphony_sid]
                print(f"  {date}: ${value:,.2f}")

print("\n" + "="*120)
print("FILTER OPTIONS")
print("="*120)
print("""
You can filter by various fields. Some available filters:
  - asset_classes (e.g., "CRYPTO" in asset_classes)
  - rebalance_frequency (e.g., rebalance_frequency == 'daily')
  - num_node_asset, num_node_filter, num_node_group
  - oos_* fields (may not always be populated)

Examples:
  - filter="rebalance_frequency == 'daily'"
  - filter="'CRYPTO' in asset_classes"
  - filter="num_node_asset > 100"

You can also sort by any field:
  - order_by=[["num_node_asset", "desc"]]
  - order_by=[["rebalance_frequency", "asc"]]
""")

Output:

Searching for public symphonies...

Found 5 public symphonies

========================================================================================================================
SEARCH RESULTS
========================================================================================================================

#   Name                                       Assets  Filters Rebalance  ID          
------------------------------------------------------------------------------------------------------------------------
1   Revised Gold Digger                            10        0 daily      nODZigmFcKBm
2   Core - Allana Tactical Momentum               442       34 daily      fLQfosCfkFqb
3   Macro on top Pals Minor Spell of Summ...      157        9 daily      ZKBZN6MTCjsw
4   Copy of MSTR FTLT + MSTR Nested + MST...      278        0 daily      1A4cveqbh3YU
5   Jerry Five Inv Vol 200d                         6        0 daily      wtIOhEGiLO98

========================================================================================================================

--- TOP RESULT DETAILS ---

Name: Revised Gold Digger
ID: nODZigmFcKBmvBmbuzae
Description: (Created with Composer AI)
Asset Classes: EQUITIES
Rebalance Frequency: daily

Structure:
  - Assets: 10
  - Filters: 0
  - Groups: 0
  - IF Nodes: 5

AI Summary: N/A

Categories: N/A

--- RUNNING 30-DAY BACKTEST ---
--------------------------------------------------
Sharpe Ratio: 0.14
Cumulative Return: -17.31%
Max Drawdown: 46.15%
Annualized Return: -91.96%

Daily values (last 5 days):
  2026-02-13: $8,268.85
  2026-02-12: $7,096.09
  2026-02-11: $9,130.38
  2026-02-10: $8,429.58
  2026-02-09: $8,418.63

========================================================================================================================
FILTER OPTIONS
========================================================================================================================

You can filter by various fields. Some available filters:
  - asset_classes (e.g., "CRYPTO" in asset_classes)
  - rebalance_frequency (e.g., rebalance_frequency == 'daily')
  - num_node_asset, num_node_filter, num_node_group
  - oos_* fields (may not always be populated)

Examples:
  - filter="rebalance_frequency == 'daily'"
  - filter="'CRYPTO' in asset_classes"
  - filter="num_node_asset > 100"

You can also sort by any field:
  - order_by=[["num_node_asset", "desc"]]
  - order_by=[["rebalance_frequency", "asc"]]