214 lines
7.1 KiB
Python
Executable File
214 lines
7.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
美股手动分析脚本
|
||
|
||
用法:
|
||
python3 scripts/analyze_stock.py AAPL # 分析单只股票
|
||
python3 scripts/analyze_stock.py AAPL TSLA # 分析多只股票
|
||
python3 scripts/analyze_stock.py # 分析配置的所有股票
|
||
|
||
环境:
|
||
需要在 backend 目录下运行,或设置 PYTHONPATH
|
||
"""
|
||
import sys
|
||
import os
|
||
import asyncio
|
||
from datetime import datetime
|
||
|
||
# 添加项目路径
|
||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'backend'))
|
||
|
||
from app.services.yfinance_service import get_yfinance_service
|
||
from app.crypto_agent.llm_signal_analyzer import LLMSignalAnalyzer
|
||
from app.config import get_settings
|
||
from app.utils.logger import logger
|
||
|
||
|
||
async def analyze_stock(symbol: str):
|
||
"""
|
||
分析单只股票
|
||
|
||
Args:
|
||
symbol: 股票代码
|
||
"""
|
||
print("\n" + "=" * 80)
|
||
print(f"📊 分析 {symbol}")
|
||
print("=" * 80)
|
||
|
||
try:
|
||
# 1. 获取服务
|
||
yf_service = get_yfinance_service()
|
||
llm_analyzer = LLMSignalAnalyzer()
|
||
settings = get_settings()
|
||
|
||
# 2. 获取当前行情
|
||
print(f"\n📈 获取当前行情...")
|
||
ticker = yf_service.get_ticker(symbol)
|
||
if not ticker:
|
||
print(f"❌ 无法获取 {symbol} 的行情数据")
|
||
return
|
||
|
||
current_price = ticker['lastPrice']
|
||
price_change = ticker['priceChange']
|
||
price_change_percent = ticker['priceChangePercent']
|
||
|
||
print(f" 最新价格: ${current_price:,.2f}")
|
||
print(f" 涨跌额: ${price_change:+,.2f}")
|
||
print(f" 涨跌幅: {price_change_percent:+.2f}%")
|
||
print(f" 成交量: {ticker['volume']:,}")
|
||
|
||
# 3. 获取 K 线数据
|
||
print(f"\n📊 获取 K 线数据...")
|
||
data = yf_service.get_multi_timeframe_data(symbol)
|
||
|
||
if not data:
|
||
print(f"❌ 无法获取 {symbol} 的 K 线数据")
|
||
return
|
||
|
||
for tf, df in data.items():
|
||
print(f" {tf}: {len(df)} 条数据")
|
||
|
||
# 4. LLM 分析
|
||
print(f"\n🤖 LLM 分析中...")
|
||
print("-" * 80)
|
||
|
||
result = await llm_analyzer.analyze(
|
||
symbol,
|
||
data,
|
||
symbols=[symbol],
|
||
position_info=None
|
||
)
|
||
|
||
# 5. 输出分析结果
|
||
print("\n📋 分析结果:")
|
||
print("-" * 80)
|
||
|
||
# 市场状态
|
||
summary = result.get('analysis_summary', '无')
|
||
print(f"市场状态: {summary}")
|
||
|
||
# 新闻情绪
|
||
news_sentiment = result.get('news_sentiment', '')
|
||
if news_sentiment:
|
||
sentiment_map = {'positive': '📈 积极', 'negative': '📉 消极', 'neutral': '➖ 中性'}
|
||
print(f"新闻情绪: {sentiment_map.get(news_sentiment, news_sentiment)}")
|
||
|
||
news_impact = result.get('news_impact', '')
|
||
if news_impact:
|
||
print(f"消息影响: {news_impact}")
|
||
|
||
# 关键价位
|
||
levels = result.get('key_levels', {})
|
||
if levels.get('support') or levels.get('resistance'):
|
||
print(f"\n关键价位:")
|
||
if levels.get('support'):
|
||
support_str = ', '.join([f"${s:,.2f}" for s in levels.get('support', [])[:3]])
|
||
print(f" 支撑位: {support_str}")
|
||
if levels.get('resistance'):
|
||
resistance_str = ', '.join([f"${r:,.2f}" for r in levels.get('resistance', [])[:3]])
|
||
print(f" 阻力位: {resistance_str}")
|
||
|
||
# 信号
|
||
signals = result.get('signals', [])
|
||
if not signals:
|
||
print(f"\n⏸️ 结论: 无交易信号,继续观望")
|
||
else:
|
||
print(f"\n🎯 发现 {len(signals)} 个信号:")
|
||
print("-" * 80)
|
||
|
||
for sig in signals:
|
||
signal_type = sig.get('type', 'unknown')
|
||
type_map = {'short_term': '短线', 'medium_term': '中线', 'long_term': '长线'}
|
||
type_text = type_map.get(signal_type, signal_type)
|
||
|
||
action = sig.get('action', 'wait')
|
||
action_map = {'buy': '🟢 做多', 'sell': '🔴 做空', 'wait': '⏸️ 观望'}
|
||
action_text = action_map.get(action, action)
|
||
|
||
grade = sig.get('grade', 'D')
|
||
confidence = sig.get('confidence', 0)
|
||
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(grade, '')
|
||
|
||
print(f"\n{type_text} {action_text} [{grade}级{grade_icon}] {confidence}%")
|
||
|
||
entry = sig.get('entry_price', 0)
|
||
sl = sig.get('stop_loss', 0)
|
||
tp = sig.get('take_profit', 0)
|
||
|
||
if entry and sl and tp:
|
||
print(f" 入场: ${entry:,.2f}")
|
||
print(f" 止损: ${sl:,.2f} ({((sl - entry) / entry * 100):+.1f}%)")
|
||
print(f" 止盈: ${tp:,.2f} ({((tp - entry) / entry * 100):+.1f}%)")
|
||
|
||
reason = sig.get('reason', '')
|
||
if reason:
|
||
print(f" 理由: {reason}")
|
||
|
||
risk_warning = sig.get('risk_warning', '')
|
||
if risk_warning:
|
||
print(f" 风险提示: {risk_warning}")
|
||
|
||
# 6. 格式化通知
|
||
if signals:
|
||
print("\n" + "=" * 80)
|
||
print("📱 通知预览:")
|
||
print("-" * 80)
|
||
|
||
for sig in signals:
|
||
if sig.get('grade', 'D') != 'D':
|
||
formatted = llm_analyzer.format_signal_for_notification(symbol, sig)
|
||
print(f"\n{formatted['title']}")
|
||
print(formatted['content'])
|
||
|
||
except Exception as e:
|
||
print(f"\n❌ 分析 {symbol} 失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
|
||
async def main():
|
||
"""主函数"""
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description='美股手动分析脚本')
|
||
parser.add_argument('symbols', nargs='*', help='股票代码(留空则分析配置的所有股票)')
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 确定要分析的股票
|
||
if args.symbols:
|
||
symbols = [s.upper() for s in args.symbols]
|
||
else:
|
||
settings = get_settings()
|
||
symbols = settings.stock_symbols.split(',') if settings.stock_symbols else []
|
||
if not symbols or not symbols[0]:
|
||
print("❌ 未指定股票代码,且配置文件中未配置 STOCK_SYMBOLS")
|
||
print("\n用法:")
|
||
print(" python3 scripts/analyze_stock.py AAPL")
|
||
print(" python3 scripts/analyze_stock.py AAPL TSLA NVDA")
|
||
print(" python3 scripts/analyze_stock.py # 分析配置的所有股票")
|
||
sys.exit(1)
|
||
|
||
print("\n" + "=" * 80)
|
||
print("🤖 美股手动分析脚本")
|
||
print("=" * 80)
|
||
print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||
print(f"股票: {', '.join(symbols)}")
|
||
print("=" * 80)
|
||
|
||
# 逐个分析
|
||
for symbol in symbols:
|
||
await analyze_stock(symbol)
|
||
|
||
print("\n" + "=" * 80)
|
||
print("✅ 分析完成!")
|
||
print("=" * 80)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
asyncio.run(main())
|
||
except KeyboardInterrupt:
|
||
print("\n\n⚠️ 用户中断")
|
||
sys.exit(0)
|