Add Centillion Investment Partners trading agents setup
- centillion_config.py: Custom config for long-only SMID cap fund (US + EU), portfolio/watchlist tickers, benchmark definitions, Anthropic Claude as LLM - centillion_run.py: Batch analysis runner with CLI flags for ticker/region/date filtering, outputs JSON results with buy/sell/hold signals - centillion_portfolio_tracker.py: Virtual portfolio tracker for paper trading with position sizing, NAV tracking, and performance reporting https://claude.ai/code/session_01Wq18jXYiJNdb9po7EzmnUZ
This commit is contained in:
parent
f362a160c3
commit
cd2236079f
|
|
@ -0,0 +1,93 @@
|
|||
"""
|
||||
Centillion Investment Partners - TradingAgents Configuration
|
||||
|
||||
Tailored for a long-only SMID cap fund focused on US and European stocks.
|
||||
Two modes:
|
||||
1. Virtual portfolio tracking (paper trading to test alpha generation)
|
||||
2. Buy/sell recommendation engine for active portfolio + watchlist
|
||||
"""
|
||||
|
||||
import os
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Base config — inherit defaults and override for Centillion's needs
|
||||
# ---------------------------------------------------------------------------
|
||||
CENTILLION_CONFIG = DEFAULT_CONFIG.copy()
|
||||
|
||||
# LLM provider — change to "anthropic" or "google" if you prefer
|
||||
CENTILLION_CONFIG["llm_provider"] = "anthropic"
|
||||
CENTILLION_CONFIG["deep_think_llm"] = "claude-sonnet-4-20250514"
|
||||
CENTILLION_CONFIG["quick_think_llm"] = "claude-sonnet-4-20250514"
|
||||
|
||||
# More thorough analysis: 2 rounds of debate for better signal quality
|
||||
CENTILLION_CONFIG["max_debate_rounds"] = 2
|
||||
CENTILLION_CONFIG["max_risk_discuss_rounds"] = 2
|
||||
|
||||
# Data vendors — yfinance is free and covers US + European equities
|
||||
CENTILLION_CONFIG["data_vendors"] = {
|
||||
"core_stock_apis": "yfinance",
|
||||
"technical_indicators": "yfinance",
|
||||
"fundamental_data": "yfinance",
|
||||
"news_data": "yfinance",
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Portfolio & Watchlist
|
||||
# ---------------------------------------------------------------------------
|
||||
# US SMID cap holdings/watchlist — add your actual tickers here
|
||||
US_PORTFOLIO = [
|
||||
# Example SMID cap US names — replace with your actual holdings
|
||||
"CVLT", # Commvault Systems
|
||||
"CSWI", # CSW Industrials
|
||||
"EXPO", # Exponent
|
||||
"LOPE", # Grand Canyon Education
|
||||
"NOVT", # Novanta
|
||||
"PCVX", # Vaxcyte
|
||||
"STEP", # StepStone Group
|
||||
"TNET", # TriNet Group
|
||||
"ACIW", # ACI Worldwide
|
||||
"CADE", # Cadence Bank
|
||||
]
|
||||
|
||||
US_WATCHLIST = [
|
||||
# Stocks you're considering but don't own yet
|
||||
"CORT", # Corcept Therapeutics
|
||||
"ELF", # e.l.f. Beauty
|
||||
"DUOL", # Duolingo
|
||||
"GWRE", # Guidewire Software
|
||||
"KNSL", # Kingsdale Advisors
|
||||
]
|
||||
|
||||
# European SMID cap — use Yahoo Finance tickers (exchange suffix)
|
||||
# .L = London, .AS = Amsterdam, .DE = Frankfurt, .PA = Paris, .ST = Stockholm
|
||||
EU_PORTFOLIO = [
|
||||
# Example European SMID names — replace with your actual holdings
|
||||
"DARK.L", # Darktrace (London)
|
||||
"IMCD.AS", # IMCD (Amsterdam)
|
||||
"RHM.DE", # Rheinmetall (Frankfurt)
|
||||
"DSY.PA", # Dassault Systèmes (Paris)
|
||||
"ALFA.ST", # Alfa Laval (Stockholm)
|
||||
]
|
||||
|
||||
EU_WATCHLIST = [
|
||||
"ASM.AS", # ASM International
|
||||
"BESI.AS", # BE Semiconductor
|
||||
"MONY.L", # Moneysupermarket
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Combined universe
|
||||
# ---------------------------------------------------------------------------
|
||||
ALL_PORTFOLIO = US_PORTFOLIO + EU_PORTFOLIO
|
||||
ALL_WATCHLIST = US_WATCHLIST + EU_WATCHLIST
|
||||
ALL_TICKERS = ALL_PORTFOLIO + ALL_WATCHLIST
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Benchmarks for performance tracking
|
||||
# ---------------------------------------------------------------------------
|
||||
BENCHMARKS = {
|
||||
"us": "^RUT", # Russell 2000
|
||||
"eu": "^STOXX", # STOXX Europe 600
|
||||
"combined": "ACWI", # MSCI ACWI (broad global)
|
||||
}
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
"""
|
||||
Centillion Investment Partners - Virtual Portfolio Tracker
|
||||
|
||||
Maintains a paper-trading portfolio based on TradingAgents signals.
|
||||
Tracks performance over time against benchmarks to evaluate whether
|
||||
the agent-driven strategy generates alpha.
|
||||
|
||||
Usage:
|
||||
# Initialize a new virtual portfolio with $1M
|
||||
python centillion_portfolio_tracker.py init --cash 1000000
|
||||
|
||||
# Record today's signals and update positions
|
||||
python centillion_portfolio_tracker.py update
|
||||
|
||||
# Show current portfolio and performance
|
||||
python centillion_portfolio_tracker.py status
|
||||
|
||||
# Show performance vs benchmarks
|
||||
python centillion_portfolio_tracker.py performance
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import yfinance as yf
|
||||
|
||||
PORTFOLIO_FILE = "results/virtual_portfolio.json"
|
||||
|
||||
|
||||
def load_portfolio():
|
||||
if not os.path.exists(PORTFOLIO_FILE):
|
||||
print("No portfolio found. Run 'init' first.")
|
||||
return None
|
||||
with open(PORTFOLIO_FILE) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_portfolio(portfolio):
|
||||
os.makedirs(os.path.dirname(PORTFOLIO_FILE), exist_ok=True)
|
||||
with open(PORTFOLIO_FILE, "w") as f:
|
||||
json.dump(portfolio, f, indent=2, default=str)
|
||||
|
||||
|
||||
def cmd_init(args):
|
||||
"""Initialize a fresh virtual portfolio."""
|
||||
portfolio = {
|
||||
"inception_date": datetime.now().strftime("%Y-%m-%d"),
|
||||
"initial_cash": args.cash,
|
||||
"cash": args.cash,
|
||||
"positions": {}, # ticker -> {"shares": n, "avg_cost": x, "entry_date": d}
|
||||
"history": [], # list of {date, action, ticker, shares, price, total_value}
|
||||
"daily_nav": [], # list of {date, nav, benchmark_values: {}}
|
||||
}
|
||||
save_portfolio(portfolio)
|
||||
print(f"Virtual portfolio initialized with ${args.cash:,.0f} cash.")
|
||||
print(f"Saved to {PORTFOLIO_FILE}")
|
||||
|
||||
|
||||
def cmd_update(args):
|
||||
"""Apply signals from the latest analysis run to the portfolio."""
|
||||
portfolio = load_portfolio()
|
||||
if not portfolio:
|
||||
return
|
||||
|
||||
# Load latest analysis results
|
||||
results_dir = "results"
|
||||
result_files = sorted(
|
||||
[f for f in os.listdir(results_dir) if f.startswith("centillion_") and f.endswith(".json") and f != "virtual_portfolio.json"],
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
if not result_files:
|
||||
print("No analysis results found. Run centillion_run.py first.")
|
||||
return
|
||||
|
||||
latest_file = os.path.join(results_dir, result_files[0])
|
||||
print(f"Using signals from: {latest_file}")
|
||||
|
||||
with open(latest_file) as f:
|
||||
signals = json.load(f)
|
||||
|
||||
date = datetime.now().strftime("%Y-%m-%d")
|
||||
position_size = portfolio["cash"] + _total_position_value(portfolio)
|
||||
target_per_position = position_size * 0.05 # 5% position sizing (long-only)
|
||||
|
||||
actions_taken = []
|
||||
|
||||
for ticker, data in signals.items():
|
||||
if data["status"] == "error":
|
||||
continue
|
||||
|
||||
signal = _extract_signal(data.get("decision", ""))
|
||||
|
||||
if signal == "BUY" and ticker not in portfolio["positions"]:
|
||||
# Buy new position — allocate ~5% of portfolio
|
||||
try:
|
||||
price = _get_current_price(ticker)
|
||||
if price and portfolio["cash"] >= target_per_position:
|
||||
shares = int(target_per_position / price)
|
||||
if shares > 0:
|
||||
cost = shares * price
|
||||
portfolio["cash"] -= cost
|
||||
portfolio["positions"][ticker] = {
|
||||
"shares": shares,
|
||||
"avg_cost": price,
|
||||
"entry_date": date,
|
||||
}
|
||||
actions_taken.append(f" BUY {shares} {ticker} @ ${price:.2f} = ${cost:,.0f}")
|
||||
portfolio["history"].append({
|
||||
"date": date, "action": "BUY", "ticker": ticker,
|
||||
"shares": shares, "price": price,
|
||||
})
|
||||
except Exception as e:
|
||||
print(f" Could not buy {ticker}: {e}")
|
||||
|
||||
elif signal == "SELL" and ticker in portfolio["positions"]:
|
||||
# Sell entire position (long-only fund, no shorting)
|
||||
pos = portfolio["positions"][ticker]
|
||||
try:
|
||||
price = _get_current_price(ticker)
|
||||
if price:
|
||||
proceeds = pos["shares"] * price
|
||||
pnl = (price - pos["avg_cost"]) * pos["shares"]
|
||||
portfolio["cash"] += proceeds
|
||||
actions_taken.append(
|
||||
f" SELL {pos['shares']} {ticker} @ ${price:.2f} = ${proceeds:,.0f} "
|
||||
f"(PnL: ${pnl:+,.0f})"
|
||||
)
|
||||
portfolio["history"].append({
|
||||
"date": date, "action": "SELL", "ticker": ticker,
|
||||
"shares": pos["shares"], "price": price, "pnl": pnl,
|
||||
})
|
||||
del portfolio["positions"][ticker]
|
||||
except Exception as e:
|
||||
print(f" Could not sell {ticker}: {e}")
|
||||
|
||||
# Record daily NAV
|
||||
nav = portfolio["cash"] + _total_position_value(portfolio)
|
||||
portfolio["daily_nav"].append({"date": date, "nav": nav})
|
||||
|
||||
save_portfolio(portfolio)
|
||||
|
||||
if actions_taken:
|
||||
print(f"\nActions taken on {date}:")
|
||||
print("\n".join(actions_taken))
|
||||
else:
|
||||
print("No actions taken (all signals were HOLD or positions unchanged).")
|
||||
|
||||
print(f"\nPortfolio NAV: ${nav:,.0f}")
|
||||
print(f"Cash: ${portfolio['cash']:,.0f}")
|
||||
print(f"Positions: {len(portfolio['positions'])}")
|
||||
|
||||
|
||||
def cmd_status(args):
|
||||
"""Show current portfolio status."""
|
||||
portfolio = load_portfolio()
|
||||
if not portfolio:
|
||||
return
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(" CENTILLION VIRTUAL PORTFOLIO")
|
||||
print(f"{'='*60}")
|
||||
print(f" Inception: {portfolio['inception_date']}")
|
||||
print(f" Cash: ${portfolio['cash']:,.0f}")
|
||||
|
||||
total_mkt = 0
|
||||
total_cost = 0
|
||||
|
||||
if portfolio["positions"]:
|
||||
print(f"\n {'Ticker':<10} {'Shares':<8} {'AvgCost':<10} {'Current':<10} {'MktVal':<12} {'PnL':<12}")
|
||||
print(f" {'-'*62}")
|
||||
|
||||
for ticker, pos in sorted(portfolio["positions"].items()):
|
||||
price = _get_current_price(ticker) or pos["avg_cost"]
|
||||
mkt_val = pos["shares"] * price
|
||||
cost_basis = pos["shares"] * pos["avg_cost"]
|
||||
pnl = mkt_val - cost_basis
|
||||
total_mkt += mkt_val
|
||||
total_cost += cost_basis
|
||||
print(
|
||||
f" {ticker:<10} {pos['shares']:<8} ${pos['avg_cost']:<9.2f} ${price:<9.2f} "
|
||||
f"${mkt_val:<11,.0f} ${pnl:<+11,.0f}"
|
||||
)
|
||||
|
||||
nav = portfolio["cash"] + total_mkt
|
||||
total_return = ((nav / portfolio["initial_cash"]) - 1) * 100
|
||||
|
||||
print(f"\n Total Market Value: ${total_mkt:,.0f}")
|
||||
print(f" Total NAV: ${nav:,.0f}")
|
||||
print(f" Total Return: {total_return:+.2f}%")
|
||||
print(f" # Positions: {len(portfolio['positions'])}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
|
||||
def cmd_performance(args):
|
||||
"""Show performance history and comparison to benchmarks."""
|
||||
portfolio = load_portfolio()
|
||||
if not portfolio:
|
||||
return
|
||||
|
||||
if not portfolio["daily_nav"]:
|
||||
print("No NAV history yet. Run 'update' after each analysis.")
|
||||
return
|
||||
|
||||
initial = portfolio["initial_cash"]
|
||||
print(f"\n NAV History:")
|
||||
print(f" {'Date':<12} {'NAV':<15} {'Return':<10}")
|
||||
print(f" {'-'*37}")
|
||||
|
||||
for entry in portfolio["daily_nav"]:
|
||||
ret = ((entry["nav"] / initial) - 1) * 100
|
||||
print(f" {entry['date']:<12} ${entry['nav']:<14,.0f} {ret:+.2f}%")
|
||||
|
||||
# Trade history summary
|
||||
trades = portfolio["history"]
|
||||
if trades:
|
||||
total_pnl = sum(t.get("pnl", 0) for t in trades if t["action"] == "SELL")
|
||||
wins = sum(1 for t in trades if t["action"] == "SELL" and t.get("pnl", 0) > 0)
|
||||
losses = sum(1 for t in trades if t["action"] == "SELL" and t.get("pnl", 0) <= 0)
|
||||
print(f"\n Closed Trades: {wins + losses} (Wins: {wins}, Losses: {losses})")
|
||||
print(f" Realized PnL: ${total_pnl:+,.0f}")
|
||||
|
||||
|
||||
def _get_current_price(ticker):
|
||||
"""Fetch current/latest price for a ticker via yfinance."""
|
||||
try:
|
||||
tk = yf.Ticker(ticker)
|
||||
hist = tk.history(period="5d")
|
||||
if not hist.empty:
|
||||
return float(hist["Close"].iloc[-1])
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _total_position_value(portfolio):
|
||||
"""Calculate total market value of all positions."""
|
||||
total = 0
|
||||
for ticker, pos in portfolio["positions"].items():
|
||||
price = _get_current_price(ticker) or pos["avg_cost"]
|
||||
total += pos["shares"] * price
|
||||
return total
|
||||
|
||||
|
||||
def _extract_signal(decision_text):
|
||||
text_upper = decision_text.upper() if isinstance(decision_text, str) else ""
|
||||
for signal in ["BUY", "SELL", "HOLD"]:
|
||||
if signal in text_upper:
|
||||
return signal
|
||||
return "HOLD"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Centillion Virtual Portfolio Tracker")
|
||||
sub = parser.add_subparsers(dest="command")
|
||||
|
||||
init_p = sub.add_parser("init", help="Initialize new virtual portfolio")
|
||||
init_p.add_argument("--cash", type=float, default=1_000_000, help="Starting cash (default $1M)")
|
||||
|
||||
sub.add_parser("update", help="Apply latest signals to portfolio")
|
||||
sub.add_parser("status", help="Show current portfolio")
|
||||
sub.add_parser("performance", help="Show performance history")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "init":
|
||||
cmd_init(args)
|
||||
elif args.command == "update":
|
||||
cmd_update(args)
|
||||
elif args.command == "status":
|
||||
cmd_status(args)
|
||||
elif args.command == "performance":
|
||||
cmd_performance(args)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
"""
|
||||
Centillion Investment Partners - Portfolio Analysis Runner
|
||||
|
||||
Runs the TradingAgents framework across all portfolio + watchlist tickers
|
||||
and outputs buy/sell/hold recommendations.
|
||||
|
||||
Usage:
|
||||
# Analyze full portfolio + watchlist
|
||||
python centillion_run.py
|
||||
|
||||
# Analyze a single ticker
|
||||
python centillion_run.py --ticker CVLT
|
||||
|
||||
# Analyze only the watchlist
|
||||
python centillion_run.py --watchlist-only
|
||||
|
||||
# Analyze only US names
|
||||
python centillion_run.py --region us
|
||||
|
||||
# Custom analysis date
|
||||
python centillion_run.py --date 2025-03-21
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from centillion_config import (
|
||||
ALL_PORTFOLIO,
|
||||
ALL_TICKERS,
|
||||
ALL_WATCHLIST,
|
||||
CENTILLION_CONFIG,
|
||||
EU_PORTFOLIO,
|
||||
EU_WATCHLIST,
|
||||
US_PORTFOLIO,
|
||||
US_WATCHLIST,
|
||||
)
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Centillion Investment Partners - Trading Agent Analysis"
|
||||
)
|
||||
parser.add_argument("--ticker", type=str, help="Analyze a single ticker")
|
||||
parser.add_argument(
|
||||
"--watchlist-only", action="store_true", help="Only analyze watchlist"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--portfolio-only", action="store_true", help="Only analyze current holdings"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--region",
|
||||
choices=["us", "eu", "all"],
|
||||
default="all",
|
||||
help="Region filter (default: all)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--date",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Analysis date in YYYY-MM-DD format (default: yesterday)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Output file path for JSON results",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug", action="store_true", help="Enable debug output from agents"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_tickers(args):
|
||||
"""Determine which tickers to analyze based on flags."""
|
||||
if args.ticker:
|
||||
return [args.ticker.upper()]
|
||||
|
||||
region = args.region
|
||||
|
||||
if args.portfolio_only:
|
||||
if region == "us":
|
||||
return US_PORTFOLIO
|
||||
elif region == "eu":
|
||||
return EU_PORTFOLIO
|
||||
return ALL_PORTFOLIO
|
||||
|
||||
if args.watchlist_only:
|
||||
if region == "us":
|
||||
return US_WATCHLIST
|
||||
elif region == "eu":
|
||||
return EU_WATCHLIST
|
||||
return ALL_WATCHLIST
|
||||
|
||||
# Default: everything
|
||||
if region == "us":
|
||||
return US_PORTFOLIO + US_WATCHLIST
|
||||
elif region == "eu":
|
||||
return EU_PORTFOLIO + EU_WATCHLIST
|
||||
return ALL_TICKERS
|
||||
|
||||
|
||||
def get_analysis_date(args):
|
||||
"""Get the analysis date — defaults to yesterday (last trading day approximation)."""
|
||||
if args.date:
|
||||
return args.date
|
||||
yesterday = datetime.now() - timedelta(days=1)
|
||||
# Skip weekends
|
||||
if yesterday.weekday() == 6: # Sunday
|
||||
yesterday -= timedelta(days=2)
|
||||
elif yesterday.weekday() == 5: # Saturday
|
||||
yesterday -= timedelta(days=1)
|
||||
return yesterday.strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def run_analysis(tickers, date, debug=False):
|
||||
"""Run the trading agents analysis on each ticker."""
|
||||
ta = TradingAgentsGraph(debug=debug, config=CENTILLION_CONFIG)
|
||||
|
||||
results = {}
|
||||
total = len(tickers)
|
||||
|
||||
for i, ticker in enumerate(tickers, 1):
|
||||
print(f"\n{'='*60}")
|
||||
print(f" [{i}/{total}] Analyzing {ticker} as of {date}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
try:
|
||||
_, decision = ta.propagate(ticker, date)
|
||||
results[ticker] = {
|
||||
"decision": decision,
|
||||
"date": date,
|
||||
"status": "success",
|
||||
"in_portfolio": ticker in ALL_PORTFOLIO,
|
||||
}
|
||||
|
||||
# Print summary
|
||||
print(f"\n >> {ticker}: {_extract_signal(decision)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n >> {ticker}: ERROR - {e}")
|
||||
results[ticker] = {
|
||||
"decision": str(e),
|
||||
"date": date,
|
||||
"status": "error",
|
||||
"in_portfolio": ticker in ALL_PORTFOLIO,
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _extract_signal(decision_text):
|
||||
"""Extract the BUY/SELL/HOLD signal from the decision text."""
|
||||
text_upper = decision_text.upper() if isinstance(decision_text, str) else ""
|
||||
for signal in ["BUY", "SELL", "HOLD"]:
|
||||
if signal in text_upper:
|
||||
return signal
|
||||
return "UNCLEAR"
|
||||
|
||||
|
||||
def print_summary(results):
|
||||
"""Print a clean summary table of all results."""
|
||||
print(f"\n\n{'='*70}")
|
||||
print(" CENTILLION INVESTMENT PARTNERS — ANALYSIS SUMMARY")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
buys, sells, holds, errors = [], [], [], []
|
||||
|
||||
for ticker, res in results.items():
|
||||
if res["status"] == "error":
|
||||
errors.append(ticker)
|
||||
continue
|
||||
|
||||
signal = _extract_signal(res["decision"])
|
||||
tag = " [HELD]" if res["in_portfolio"] else " [WATCH]"
|
||||
|
||||
if signal == "BUY":
|
||||
buys.append(f" {ticker}{tag}")
|
||||
elif signal == "SELL":
|
||||
sells.append(f" {ticker}{tag}")
|
||||
else:
|
||||
holds.append(f" {ticker}{tag}")
|
||||
|
||||
if buys:
|
||||
print("BUY signals:")
|
||||
print("\n".join(buys))
|
||||
if sells:
|
||||
print("\nSELL signals:")
|
||||
print("\n".join(sells))
|
||||
if holds:
|
||||
print("\nHOLD signals:")
|
||||
print("\n".join(holds))
|
||||
if errors:
|
||||
print(f"\nErrors: {', '.join(errors)}")
|
||||
|
||||
print(f"\n{'='*70}\n")
|
||||
|
||||
|
||||
def save_results(results, output_path):
|
||||
"""Save results to JSON file."""
|
||||
os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(results, f, indent=2, default=str)
|
||||
print(f"Results saved to {output_path}")
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
tickers = get_tickers(args)
|
||||
date = get_analysis_date(args)
|
||||
|
||||
print(f"\n Centillion Investment Partners — TradingAgents Analysis")
|
||||
print(f" Date: {date}")
|
||||
print(f" Tickers ({len(tickers)}): {', '.join(tickers)}")
|
||||
print(f" LLM: {CENTILLION_CONFIG['llm_provider']} / {CENTILLION_CONFIG['deep_think_llm']}")
|
||||
print()
|
||||
|
||||
results = run_analysis(tickers, date, debug=args.debug)
|
||||
print_summary(results)
|
||||
|
||||
# Save results
|
||||
output_path = args.output or f"results/centillion_{date}.json"
|
||||
save_results(results, output_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue