236 lines
6.3 KiB
Python
236 lines
6.3 KiB
Python
"""
|
|
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()
|