545 lines
21 KiB
Python
545 lines
21 KiB
Python
#!/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
|
||
from src.database.mysql_database_manager import MySQLDatabaseManager
|
||
|
||
|
||
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()
|
||
|
||
# 初始化MySQL数据库管理器
|
||
db_manager = MySQLDatabaseManager()
|
||
logger.info("MySQL数据库管理器初始化完成")
|
||
|
||
# 初始化通知管理器
|
||
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, db_manager)
|
||
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() |