trading.ai/main.py
aaron 283901df18 Initial commit: A股量化交易系统
主要功能:
- K线形态策略: 两阳+阴+阳突破形态识别
- 信号时间修复: 使用K线时间而非发送时间
- 换手率约束: 最后阳线换手率不超过40%
- 汇总通知: 钉钉webhook单次发送所有信号
- 数据获取: 支持AKShare数据源
- 舆情分析: 北向资金、热门股票等

技术特性:
- 支持日线/周线/月线多时间周期
- EMA20趋势确认
- 实体比例验证
- 突破价格确认
- 流动性约束检查

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 15:59:48 +08:00

540 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
A股量化交易主程序
"""
import sys
from pathlib import Path
# 将src目录添加到Python路径
current_dir = Path(__file__).parent
src_dir = current_dir / "src"
sys.path.insert(0, str(src_dir))
from loguru import logger
from src.utils.config_loader import config_loader
from src.data.data_fetcher import ADataFetcher
from src.data.sentiment_fetcher import SentimentFetcher
from src.utils.notification import NotificationManager
from src.strategy.kline_pattern_strategy import KLinePatternStrategy
def setup_logging():
"""设置日志配置"""
log_config = config_loader.get_logging_config()
# 移除默认的控制台日志
logger.remove()
# 添加控制台输出
logger.add(
sys.stdout,
level=log_config.get('level', 'INFO'),
format=log_config.get('format', '{time} | {level} | {message}')
)
# 添加文件输出
log_file = Path(log_config.get('file_path', 'logs/trading.log'))
log_file.parent.mkdir(parents=True, exist_ok=True)
logger.add(
log_file,
level=log_config.get('level', 'INFO'),
format=log_config.get('format', '{time} | {level} | {message}'),
rotation=log_config.get('rotation', '1 day'),
retention=log_config.get('retention', '30 days')
)
logger.info("日志系统初始化完成")
def main():
"""主函数"""
print("="*60)
print(" A股量化交易系统")
print("="*60)
try:
# 初始化日志
setup_logging()
# 加载配置
config = config_loader.load_config()
logger.info("配置文件加载成功")
# 初始化数据获取器
data_fetcher = ADataFetcher()
sentiment_fetcher = SentimentFetcher()
# 初始化通知管理器
notification_config = config.get('notification', {})
notification_manager = NotificationManager(notification_config)
# 初始化K线形态策略
strategy_config = config.get('strategy', {}).get('kline_pattern', {})
if strategy_config.get('enabled', False):
kline_strategy = KLinePatternStrategy(data_fetcher, notification_manager, strategy_config)
logger.info("K线形态策略已启用")
else:
kline_strategy = None
logger.info("K线形态策略未启用")
# 显示系统信息
logger.info("系统启动成功")
print("\n系统功能:")
print("1. 数据获取 - 实时行情、历史数据、财务数据")
print("2. 舆情分析 - 北向资金、融资融券、热点股票、龙虎榜")
print("3. K线形态策略 - 两阳线+阴线+阳线突破形态识别")
print("4. 股票筛选 - 基于技术指标和基本面的选股")
print("5. 实时监控 - 价格变动、成交量异常监控")
print("6. 策略回测 - 历史数据验证交易策略")
# 获取市场概况
print("\n正在获取市场概况...")
market_overview = data_fetcher.get_market_overview()
if market_overview:
print(f"\n市场概况 (更新时间: {market_overview.get('update_time', 'N/A')}):")
for market, data in market_overview.items():
if market != 'update_time' and isinstance(data, dict):
price = data.get('close', data.get('current', 'N/A'))
change = data.get('change', 'N/A')
change_pct = data.get('change_pct', 'N/A')
print(f" {market.upper()}: 价格={price}, 涨跌={change}, 涨跌幅={change_pct}%")
print("\n系统就绪,等待指令...")
print("输入 'help' 查看帮助,输入 'quit' 退出程序")
# 简单的交互式命令行
while True:
try:
command = input("\n> ").strip().lower()
if command == 'quit' or command == 'exit':
print("感谢使用A股量化交易系统")
break
elif command == 'help':
print_help()
elif command == 'status':
print_system_status()
elif command.startswith('search '):
keyword = command[7:] # 移除'search '
search_stocks(data_fetcher, keyword)
elif command == 'market':
show_market_overview(data_fetcher)
elif command == 'sentiment':
show_market_sentiment(sentiment_fetcher)
elif command == 'hotstock':
show_hot_stocks(sentiment_fetcher)
elif command == 'northflow':
show_north_flow(sentiment_fetcher)
elif command == 'dragon':
show_dragon_tiger_list(sentiment_fetcher)
elif command.startswith('analyze '):
stock_code = command[8:] # 移除'analyze '
analyze_stock_sentiment(sentiment_fetcher, stock_code)
elif command == 'strategy':
show_strategy_info(kline_strategy)
elif command.startswith('scan '):
stock_code = command[5:] # 移除'scan '
scan_single_stock(kline_strategy, stock_code)
elif command == 'scanmarket':
scan_market_patterns(kline_strategy)
elif command == 'testnotify':
test_notification(notification_manager)
else:
print("未知命令,输入 'help' 查看帮助")
except KeyboardInterrupt:
print("\n\n程序被用户中断")
break
except Exception as e:
logger.error(f"命令执行错误: {e}")
print(f"执行错误: {e}")
except Exception as e:
logger.error(f"程序启动失败: {e}")
print(f"启动失败: {e}")
sys.exit(1)
def print_help():
"""打印帮助信息"""
print("\n可用命令:")
print(" help - 显示此帮助信息")
print(" status - 显示系统状态")
print(" market - 显示市场概况")
print(" search <关键词> - 搜索股票")
print(" sentiment - 显示市场舆情综合概览")
print(" hotstock - 显示热门股票排行")
print(" northflow - 显示北向资金流向")
print(" dragon - 显示龙虎榜数据")
print(" analyze <股票代码> - 分析单只股票舆情")
print(" strategy - 显示K线形态策略信息")
print(" scan <股票代码> - 扫描单只股票K线形态")
print(" scanmarket - 扫描市场K线形态")
print(" testnotify - 测试通知功能")
print(" quit/exit - 退出程序")
def print_system_status():
"""显示系统状态"""
config = config_loader.config
print("\n系统状态:")
print(f" 配置文件: 已加载")
print(f" 数据源: {config.get('data', {}).get('sources', {}).get('primary', 'N/A')}")
print(f" 日志级别: {config.get('logging', {}).get('level', 'N/A')}")
print(f" 实时监控: {'启用' if config.get('monitor', {}).get('realtime', {}).get('enabled', False) else '禁用'}")
def search_stocks(data_fetcher: ADataFetcher, keyword: str):
"""搜索股票"""
if not keyword:
print("请提供搜索关键词")
return
print(f"\n搜索股票: {keyword}")
results = data_fetcher.search_stocks(keyword)
if not results.empty:
print(f"找到 {len(results)} 个结果:")
for idx, row in results.head(10).iterrows(): # 只显示前10个结果
code = row.get('stock_code', 'N/A')
name = row.get('short_name', 'N/A')
print(f" {code} - {name}")
if len(results) > 10:
print(f" ... 还有 {len(results) - 10} 个结果")
else:
print("未找到匹配的股票")
def show_market_overview(data_fetcher: ADataFetcher):
"""显示市场概况"""
print("\n正在获取最新市场数据...")
overview = data_fetcher.get_market_overview()
if overview:
print(f"\n市场概况 (更新时间: {overview.get('update_time', 'N/A')}):")
for market, data in overview.items():
if market != 'update_time' and isinstance(data, dict):
price = data.get('close', data.get('current', 'N/A'))
change = data.get('change', 'N/A')
change_pct = data.get('change_pct', 'N/A')
volume = data.get('volume', 'N/A')
print(f" {market.upper()}: 价格={price}, 涨跌={change}, 涨跌幅={change_pct}%, 成交量={volume}")
else:
print("无法获取市场数据")
def show_market_sentiment(sentiment_fetcher: SentimentFetcher):
"""显示市场舆情综合概览"""
print("\n正在获取市场舆情数据...")
overview = sentiment_fetcher.get_market_sentiment_overview()
if overview:
print(f"\n市场舆情综合概览 (更新时间: {overview.get('update_time', 'N/A')}):")
# 北向资金
if 'north_flow' in overview:
north_data = overview['north_flow']
print(f"\n📊 北向资金:")
print(f" 总净流入: {north_data.get('net_total', 'N/A')} 万元")
print(f" 沪股通: {north_data.get('net_hgt', 'N/A')} 万元")
print(f" 深股通: {north_data.get('net_sgt', 'N/A')} 万元")
print(f" 更新时间: {north_data.get('update_time', 'N/A')}")
# 融资融券
if 'latest_margin' in overview:
margin_data = overview['latest_margin']
print(f"\n📈 融资融券:")
print(f" 融资余额: {margin_data.get('rzye', 'N/A')} 亿元")
print(f" 融券余额: {margin_data.get('rqye', 'N/A')} 亿元")
print(f" 两融余额: {margin_data.get('rzrqye', 'N/A')} 亿元")
# 热门股票前5名
if 'hot_stocks_east' in overview and not overview['hot_stocks_east'].empty:
print(f"\n🔥 东财热门股票TOP5:")
for idx, row in overview['hot_stocks_east'].head(5).iterrows():
code = row.get('stock_code', 'N/A')
name = row.get('short_name', 'N/A')
rank = row.get('rank', idx + 1)
print(f" {rank}. {code} - {name}")
# 热门概念前5名
if 'hot_concepts' in overview and not overview['hot_concepts'].empty:
print(f"\n💡 热门概念TOP5:")
for idx, row in overview['hot_concepts'].head(5).iterrows():
name = row.get('concept_name', 'N/A')
change_pct = row.get('change_pct', 'N/A')
rank = row.get('rank', idx + 1)
print(f" {rank}. {name} (涨跌幅: {change_pct}%)")
# 龙虎榜前3名
if 'dragon_tiger' in overview and not overview['dragon_tiger'].empty:
print(f"\n🐉 今日龙虎榜TOP3:")
for idx, row in overview['dragon_tiger'].head(3).iterrows():
code = row.get('stock_code', 'N/A')
name = row.get('short_name', 'N/A')
reason = row.get('reason', 'N/A')
print(f" {idx + 1}. {code} - {name} ({reason})")
else:
print("无法获取市场舆情数据")
def show_hot_stocks(sentiment_fetcher: SentimentFetcher):
"""显示热门股票排行"""
print("\n正在获取热门股票数据...")
# 东财人气股票
east_stocks = sentiment_fetcher.get_popular_stocks_east_100()
if not east_stocks.empty:
print(f"\n🔥 东财人气股票TOP10:")
for idx, row in east_stocks.head(10).iterrows():
code = row.get('stock_code', 'N/A')
name = row.get('short_name', 'N/A')
rank = row.get('rank', idx + 1)
change_pct = row.get('change_pct', 'N/A')
print(f" {rank}. {code} - {name} (涨跌幅: {change_pct}%)")
# 同花顺热门股票
ths_stocks = sentiment_fetcher.get_hot_stocks_ths_100()
if not ths_stocks.empty:
print(f"\n🌟 同花顺热门股票TOP10:")
for idx, row in ths_stocks.head(10).iterrows():
code = row.get('stock_code', 'N/A')
name = row.get('short_name', 'N/A')
rank = row.get('rank', idx + 1)
change_pct = row.get('change_pct', 'N/A')
print(f" {rank}. {code} - {name} (涨跌幅: {change_pct}%)")
def show_north_flow(sentiment_fetcher: SentimentFetcher):
"""显示北向资金流向"""
print("\n正在获取北向资金数据...")
# 当前流向
current_flow = sentiment_fetcher.get_north_flow_current()
if not current_flow.empty:
print(f"\n💰 当前北向资金流向:")
for idx, row in current_flow.iterrows():
net_total = row.get('net_tgt', 'N/A')
net_hgt = row.get('net_hgt', 'N/A')
net_sgt = row.get('net_sgt', 'N/A')
trade_time = row.get('trade_time', 'N/A')
print(f" 总净流入: {net_total} 万元")
print(f" 沪股通: {net_hgt} 万元")
print(f" 深股通: {net_sgt} 万元")
print(f" 更新时间: {trade_time}")
break # 只显示第一行数据
# 历史流向最近5天
hist_flow = sentiment_fetcher.get_north_flow_history()
if not hist_flow.empty:
print(f"\n📊 最近5天北向资金流向:")
for idx, row in hist_flow.tail(5).iterrows():
date = row.get('trade_date', 'N/A')
net_total = row.get('net_tgt', 'N/A')
print(f" {date}: {net_total} 万元")
def show_dragon_tiger_list(sentiment_fetcher: SentimentFetcher):
"""显示龙虎榜数据"""
print("\n正在获取龙虎榜数据...")
dragon_tiger = sentiment_fetcher.get_dragon_tiger_list_daily()
if not dragon_tiger.empty:
print(f"\n🐉 今日龙虎榜 (共{len(dragon_tiger)}只股票):")
for idx, row in dragon_tiger.head(15).iterrows(): # 显示前15个
code = row.get('stock_code', 'N/A')
name = row.get('short_name', 'N/A')
reason = row.get('reason', 'N/A')
change_pct = row.get('change_pct', 'N/A')
amount = row.get('amount', 'N/A')
print(f" {idx + 1}. {code} - {name}")
print(f" 上榜原因: {reason}")
print(f" 涨跌幅: {change_pct}%, 成交金额: {amount} 万元")
if len(dragon_tiger) > 15:
print(f" ... 还有 {len(dragon_tiger) - 15} 只股票")
else:
print("今日暂无龙虎榜数据")
def analyze_stock_sentiment(sentiment_fetcher: SentimentFetcher, stock_code: str):
"""分析单只股票舆情"""
if not stock_code:
print("请提供股票代码")
return
print(f"\n正在分析股票 {stock_code} 的舆情情况...")
analysis = sentiment_fetcher.analyze_stock_sentiment(stock_code)
if 'error' in analysis:
print(f"分析失败: {analysis['error']}")
return
print(f"\n📊 {stock_code} 舆情分析报告:")
print(f"更新时间: {analysis.get('update_time', 'N/A')}")
# 热度情况
print(f"\n🔥 热度情况:")
print(f" 东财人气榜: {'在榜' if analysis.get('in_popular_east', False) else '不在榜'}")
print(f" 同花顺热门榜: {'在榜' if analysis.get('in_hot_ths', False) else '不在榜'}")
# 龙虎榜情况
if 'dragon_tiger' in analysis and not analysis['dragon_tiger'].empty:
print(f"\n🐉 龙虎榜情况:")
dragon_data = analysis['dragon_tiger'].iloc[0]
reason = dragon_data.get('reason', 'N/A')
amount = dragon_data.get('amount', 'N/A')
print(f" 上榜原因: {reason}")
print(f" 成交金额: {amount} 万元")
else:
print(f"\n🐉 龙虎榜情况: 今日未上榜")
# 风险扫描
if 'risk_scan' in analysis and not analysis['risk_scan'].empty:
print(f"\n⚠️ 风险扫描:")
risk_data = analysis['risk_scan'].iloc[0]
risk_level = risk_data.get('risk_level', 'N/A')
risk_desc = risk_data.get('risk_desc', 'N/A')
print(f" 风险等级: {risk_level}")
print(f" 风险描述: {risk_desc}")
else:
print(f"\n⚠️ 风险扫描: 暂无数据")
def show_strategy_info(kline_strategy: KLinePatternStrategy):
"""显示K线形态策略信息"""
if kline_strategy is None:
print("K线形态策略未启用")
return
print("\n" + "="*60)
print(" K线形态策略信息")
print("="*60)
print(kline_strategy.get_strategy_summary())
def scan_single_stock(kline_strategy: KLinePatternStrategy, stock_code: str):
"""扫描单只股票K线形态"""
if kline_strategy is None:
print("K线形态策略未启用")
return
if not stock_code:
print("请提供股票代码")
return
print(f"\n正在扫描股票 {stock_code} 的K线形态...")
try:
results = kline_strategy.analyze_stock(stock_code)
print(f"\n📊 {stock_code} K线形态分析结果:")
total_signals = 0
for timeframe, signals in results.items():
print(f"\n{timeframe.upper()} 时间周期:")
if signals:
for i, signal in enumerate(signals, 1):
print(f" 信号 {i}:")
print(f" 日期: {signal['date']}")
print(f" 形态: {signal['pattern_type']}")
print(f" 突破价格: {signal['breakout_price']:.2f}")
print(f" 突破幅度: {signal['breakout_pct']:.2f}%")
print(f" 阳线1实体比例: {signal['yang1_entity_ratio']:.1%}")
print(f" 阳线2实体比例: {signal['yang2_entity_ratio']:.1%}")
print(f" EMA20价格: {signal['ema20_price']:.2f}")
print(f" EMA20状态: {'✅ 上方' if signal['above_ema20'] else '❌ 下方'}")
print(f" 换手率: {signal.get('turnover_ratio', 0):.2f}%")
total_signals += len(signals)
print(f" 共发现 {len(signals)} 个信号")
else:
print(" 未发现形态信号")
print(f"\n总计发现 {total_signals} 个信号")
except Exception as e:
logger.error(f"扫描股票失败: {e}")
print(f"扫描失败: {e}")
def scan_market_patterns(kline_strategy: KLinePatternStrategy):
"""扫描市场K线形态"""
if kline_strategy is None:
print("K线形态策略未启用")
return
print("\n开始扫描市场K线形态...")
print("⚠️ 注意: 这可能需要较长时间,请耐心等待")
try:
# 获取扫描股票数量配置
scan_count = kline_strategy.config.get('scan_stocks_count', 20)
print(f"扫描股票数量: {scan_count}")
results = kline_strategy.scan_market(max_stocks=scan_count)
if results:
print(f"\n📈 市场扫描结果 (发现 {len(results)} 只股票有信号):")
for stock_code, stock_results in results.items():
total_signals = sum(len(signals) for signals in stock_results.values())
print(f"\n股票: {stock_code} (共{total_signals}个信号)")
for timeframe, signals in stock_results.items():
if signals:
print(f" {timeframe}: {len(signals)}个信号")
# 只显示最新的信号
latest_signal = signals[-1]
print(f" 最新: {latest_signal['date']} 突破价格 {latest_signal['breakout_price']:.2f}")
else:
print("未发现任何K线形态信号")
except Exception as e:
logger.error(f"市场扫描失败: {e}")
print(f"扫描失败: {e}")
def test_notification(notification_manager: NotificationManager):
"""测试通知功能"""
print("\n正在测试通知功能...")
try:
# 发送测试消息
success = notification_manager.send_test_message()
if success:
print("✅ 通知测试成功")
else:
print("❌ 通知测试失败,请检查配置")
# 发送策略信号测试
test_success = notification_manager.send_strategy_signal(
stock_code="000001.SZ",
stock_name="平安银行",
timeframe="daily",
signal_type="测试信号",
price=10.50,
signal_date="2024-01-15",
additional_info={
"测试项目": "通知功能",
"发送时间": "现在"
}
)
if test_success:
print("✅ 策略信号通知测试成功")
else:
print("❌ 策略信号通知测试失败")
except Exception as e:
logger.error(f"通知测试失败: {e}")
print(f"测试失败: {e}")
if __name__ == "__main__":
main()