commit;
This commit is contained in:
parent
ab27f46c87
commit
a0de689b3b
69
src/app.py
69
src/app.py
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
@ -44,6 +45,7 @@ from src.scripts.stock_daily_data_collector import collect_stock_daily_data
|
|||
|
||||
from valuation_analysis.financial_analysis import FinancialAnalyzer
|
||||
from src.valuation_analysis.stock_price_collector import StockPriceCollector
|
||||
from src.quantitative_analysis.batch_stock_price_collector import fetch_and_store_stock_data, get_stock_realtime_info_from_redis
|
||||
|
||||
# 设置日志
|
||||
logging.basicConfig(
|
||||
|
@ -227,7 +229,7 @@ def run_rzrq_initial_collection1():
|
|||
}), 200
|
||||
|
||||
|
||||
@app.route('/scheduler/industry/crowding', methods=['GET'])
|
||||
@app.route('/scheduler/industry/crowding1/bak', methods=['GET'])
|
||||
def precalculate_industry_crowding1():
|
||||
"""预计算部分行业和概念板块的拥挤度指标 晚上10点开始"""
|
||||
try:
|
||||
|
@ -337,6 +339,27 @@ def scheduler_financial_analysis():
|
|||
"status": "success"
|
||||
}), 200
|
||||
|
||||
@app.route('/scheduler/industry/crowding', methods=['GET'])
|
||||
def precalculate_industry_crowding_batch():
|
||||
"""批量预计算行业和概念板块的拥挤度指标"""
|
||||
try:
|
||||
from src.valuation_analysis.industry_analysis import IndustryAnalyzer
|
||||
|
||||
analyzer = IndustryAnalyzer()
|
||||
# 固定行业和概念板块
|
||||
industries = ["煤炭开采", "焦炭加工", "油气开采", "石油化工", "油服工程", "日用化工", "化纤", "化学原料", "化学制品", "塑料", "橡胶", "农用化工", "非金属材料", "冶钢原料", "普钢", "特钢", "工业金属", "贵金属", "能源金属", "稀有金属", "金属新材料", "水泥", "玻璃玻纤", "装饰建材", "种植业", "养殖业", "林业", "渔业", "饲料", "农产品加工", "动物保健", "酿酒", "饮料乳品", "调味品", "休闲食品", "食品加工", "纺织制造", "服装家纺", "饰品", "造纸", "包装印刷", "家居用品", "文娱用品", "白色家电", "黑色家电", "小家电", "厨卫电器", "家电零部件", "一般零售", "商业物业经营", "专业连锁", "贸易", "电子商务", "乘用车", "商用车", "汽车零部件", "汽车服务", "摩托车及其他", "化学制药", "生物制品", "中药", "医药商业", "医疗器械", "医疗服务", "医疗美容", "电机制造", "电池", "电网设备", "光伏设备", "风电设备", "其他发电设备", "地面兵装", "航空装备", "航天装备", "航海装备", "军工电子", "轨交设备", "通用设备", "专用设备", "工程机械", "自动化设备", "半导体", "消费电子", "光学光电", "元器件", "其他电子", "通信设备", "通信工程", "电信服务", "IT设备", "软件服务", "云服务", "产业互联网", "游戏", "广告营销", "影视院线", "数字媒体", "出版业", "广播电视", "全国性银行", "地方性银行", "证券", "保险", "多元金融", "房屋建设", "基础建设", "专业工程", "工程咨询服务", "装修装饰", "房地产开发", "房产服务", "体育", "教育培训", "酒店餐饮", "旅游", "专业服务", "公路铁路", "航空机场", "航运港口", "物流", "电力", "燃气", "水务", "环保设备", "环境治理", "环境监测", "综合类"]
|
||||
concepts = ["通达信88", "海峡西岸", "海南自贸", "一带一路", "上海自贸", "雄安新区", "粤港澳", "ST板块", "次新股", "含H股", "含B股", "含GDR", "含可转债", "国防军工", "军民融合", "大飞机", "稀缺资源", "5G概念", "碳中和", "黄金概念", "物联网", "创投概念", "航运概念", "铁路基建", "高端装备", "核电核能", "光伏", "风电", "锂电池概念", "燃料电池", "HJT电池", "固态电池", "钠电池", "钒电池", "TOPCon电池", "钙钛矿电池", "BC电池", "氢能源", "稀土永磁", "盐湖提锂", "锂矿", "水利建设", "卫星导航", "可燃冰", "页岩气", "生物疫苗", "基因概念", "维生素", "仿制药", "创新药", "免疫治疗", "CXO概念", "节能环保", "食品安全", "白酒概念", "代糖概念", "猪肉", "鸡肉", "水产品", "碳纤维", "石墨烯", "3D打印", "苹果概念", "阿里概念", "腾讯概念", "小米概念", "百度概念", "华为鸿蒙", "华为海思", "华为汽车", "华为算力", "特斯拉概念", "消费电子概念", "汽车电子", "无线耳机", "生物质能", "地热能", "充电桩", "新能源车", "换电概念", "高压快充", "草甘膦", "安防服务", "垃圾分类", "核污染防治", "风沙治理", "乡村振兴", "土地流转", "体育概念", "博彩概念", "赛马概念", "分散染料", "聚氨酯", "云计算", "边缘计算", "网络游戏", "信息安全", "国产软件", "大数据", "数据中心", "芯片", "MCU芯片", "汽车芯片", "存储芯片", "互联金融", "婴童概念", "养老概念", "网红经济", "民营医院", "特高压", "智能电网", "智能穿戴", "智能交通", "智能家居", "智能医疗", "智慧城市", "智慧政务", "机器人概念", "机器视觉", "超导概念", "职业教育", "物业管理概念", "虚拟现实", "数字孪生", "钛金属", "钴金属", "镍金属", "氟概念", "磷概念", "无人机", "PPP概念", "新零售", "跨境电商", "量子科技", "无人驾驶", "ETC概念", "胎压监测", "OLED概念", "MiniLED", "MicroLED", "超清视频", "区块链", "数字货币", "人工智能", "租购同权", "工业互联", "知识产权", "工业大麻", "工业气体", "人造肉", "预制菜", "种业", "化肥概念", "操作系统", "光刻机", "第三代半导体", "远程办公", "口罩防护", "医废处理", "虫害防治", "超级电容", "C2M概念", "地摊经济", "冷链物流", "抖音概念", "降解塑料", "医美概念", "人脑工程", "烟草概念", "新型烟草", "有机硅概念", "新冠检测", "BIPV概念", "地下管网", "储能", "新材料", "工业母机", "一体压铸", "汽车热管理", "汽车拆解", "NMN概念", "国资云", "元宇宙概念", "NFT概念", "云游戏", "天然气", "绿色电力", "培育钻石", "信创", "幽门螺杆菌", "电子纸", "新冠药概念", "免税概念", "PVDF概念", "装配式建筑", "绿色建筑", "东数西算", "跨境支付CIPS", "中俄贸易", "电子身份证", "家庭医生", "辅助生殖", "肝炎概念", "新型城镇", "粮食概念", "超临界发电", "虚拟电厂", "动力电池回收", "PCB概念", "先进封装", "热泵概念", "EDA概念", "光热发电", "供销社", "Web3概念", "DRG-DIP", "AIGC概念", "复合铜箔", "数据确权", "数据要素", "POE胶膜", "血氧仪", "旅游概念", "中特估", "ChatGPT概念", "CPO概念", "数字水印", "毫米波雷达", "工业软件", "6G概念", "时空大数据", "可控核聚变", "知识付费", "算力租赁", "光通信", "混合现实", "英伟达概念", "减速器", "减肥药", "合成生物", "星闪概念", "液冷服务器", "新型工业化", "短剧游戏", "多模态AI", "PEEK材料", "小米汽车概念", "飞行汽车", "Sora概念", "人形机器人", "AI手机PC", "低空经济", "铜缆高速连接", "军工信息化", "玻璃基板", "商业航天", "车联网", "财税数字化", "折叠屏", "AI眼镜", "智谱AI", "IP经济", "宠物经济", "小红书概念", "AI智能体", "DeepSeek概念", "AI医疗概念", "海洋经济", "外骨骼机器人", "军贸概念"]
|
||||
|
||||
# 批量计算行业和概念板块拥挤度
|
||||
analyzer.batch_calculate_industry_crowding(industries, concepts)
|
||||
|
||||
logger.info("批量计算行业和概念板块的拥挤度指标完成")
|
||||
except Exception as e:
|
||||
logger.error(f"批量计算行业拥挤度指标失败: {str(e)}")
|
||||
return jsonify({
|
||||
"status": "success"
|
||||
}), 200
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""渲染主页"""
|
||||
|
@ -2863,17 +2886,53 @@ def get_pep_stock_info_by_shortname():
|
|||
|
||||
@app.route('/api/pep_stock_info_by_code', methods=['GET'])
|
||||
def get_pep_stock_info_by_code():
|
||||
"""根据股票简称查询pep_stock_info集合中的全部字段"""
|
||||
"""根据股票代码查询Redis中的实时行情并返回指定结构"""
|
||||
short_code = request.args.get('code')
|
||||
if not short_code:
|
||||
return jsonify({'success': False, 'message': '缺少必要参数: short_code'}), 400
|
||||
try:
|
||||
analyzer = FinancialAnalyzer()
|
||||
result = analyzer.get_pep_stock_info_by_code(short_code)
|
||||
return jsonify(result)
|
||||
# 兼容600001.SH/SH600001等格式
|
||||
from src.quantitative_analysis.batch_stock_price_collector import get_stock_realtime_info_from_redis
|
||||
result = get_stock_realtime_info_from_redis(short_code)
|
||||
if result:
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify({'success': False, 'message': f'未找到股票 {short_code} 的实时行情'}), 404
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': f'服务器错误: {str(e)}'}), 500
|
||||
|
||||
@app.route('/api/industry/crowding/filter', methods=['GET'])
|
||||
def filter_industry_crowding():
|
||||
"""根据拥挤度百分位区间筛选行业和概念板块"""
|
||||
try:
|
||||
min_val = float(request.args.get('min', 0))
|
||||
max_val = float(request.args.get('max', 100))
|
||||
from src.valuation_analysis.industry_analysis import IndustryAnalyzer
|
||||
analyzer = IndustryAnalyzer()
|
||||
result = analyzer.filter_crowding_by_percentile(min_val, max_val)
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'min': min_val,
|
||||
'max': max_val,
|
||||
'result': result
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"筛选行业/概念拥挤度接口异常: {str(e)}")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
@app.route('/scheduler/batch_stock_price/collection', methods=['GET'])
|
||||
def run_batch_stock_price_collection():
|
||||
"""批量采集A股行情并保存到数据库"""
|
||||
try:
|
||||
fetch_and_store_stock_data()
|
||||
return jsonify({"status": "success", "message": "批量采集A股行情并保存到数据库成功"})
|
||||
except Exception as e:
|
||||
logger.error(f"批量采集A股行情失败: {str(e)}")
|
||||
return jsonify({"status": "error", "message": str(e)})
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# 启动Web服务器
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,724 @@
|
|||
代码,日期,开盘价(元),最高价(元),最低价(元),收盘价(元),涨跌幅
|
||||
0020.HK,2022-07-05,2.4,2.78,2.38,2.75,0.0827
|
||||
0020.HK,2022-07-06,2.77,2.79,2.53,2.6,-0.0545
|
||||
0020.HK,2022-07-07,2.56,2.68,2.56,2.6,0
|
||||
0020.HK,2022-07-08,2.64,2.69,2.54,2.58,-0.0077
|
||||
0020.HK,2022-07-11,2.6,2.6,2.52,2.54,-0.0155
|
||||
0020.HK,2022-07-12,2.5,2.52,2.4,2.43,-0.0433
|
||||
0020.HK,2022-07-13,2.45,2.48,2.38,2.39,-0.0165
|
||||
0020.HK,2022-07-14,2.36,2.38,2.25,2.29,-0.0418
|
||||
0020.HK,2022-07-15,2.28,2.36,2.15,2.16,-0.0568
|
||||
0020.HK,2022-07-18,2.13,2.23,2.11,2.14,-0.0093
|
||||
0020.HK,2022-07-19,2.14,2.17,2.04,2.1,-0.0187
|
||||
0020.HK,2022-07-20,2.18,2.42,2.17,2.33,0.1095
|
||||
0020.HK,2022-07-21,2.33,2.46,2.29,2.39,0.0258
|
||||
0020.HK,2022-07-22,2.41,2.49,2.39,2.43,0.0167
|
||||
0020.HK,2022-07-25,2.41,2.43,2.34,2.4,-0.0123
|
||||
0020.HK,2022-07-26,2.31,2.34,2.19,2.27,-0.0542
|
||||
0020.HK,2022-07-27,2.23,2.37,2.22,2.34,0.0308
|
||||
0020.HK,2022-07-28,2.38,2.5,2.37,2.47,0.0556
|
||||
0020.HK,2022-07-29,2.5,2.52,2.3,2.33,-0.0567
|
||||
0020.HK,2022-08-01,2.32,2.35,2.26,2.28,-0.0215
|
||||
0020.HK,2022-08-02,2.22,2.22,2.12,2.17,-0.0482
|
||||
0020.HK,2022-08-03,2.22,2.33,2.19,2.2,0.0138
|
||||
0020.HK,2022-08-04,2.24,2.25,2.18,2.2,0
|
||||
0020.HK,2022-08-05,2.23,2.24,2.18,2.23,0.0136
|
||||
0020.HK,2022-08-08,2.25,2.26,2.2,2.2,-0.0135
|
||||
0020.HK,2022-08-09,2.2,2.23,2.18,2.19,-0.0045
|
||||
0020.HK,2022-08-10,2.2,2.21,2.14,2.16,-0.0137
|
||||
0020.HK,2022-08-11,2.19,2.21,2.16,2.19,0.0139
|
||||
0020.HK,2022-08-12,2.19,2.29,2.19,2.23,0.0183
|
||||
0020.HK,2022-08-15,2.26,2.29,2.23,2.25,0.009
|
||||
0020.HK,2022-08-16,2.25,2.28,2.18,2.2,-0.0222
|
||||
0020.HK,2022-08-17,2.2,2.22,2.16,2.16,-0.0182
|
||||
0020.HK,2022-08-18,2.18,2.18,2.06,2.08,-0.037
|
||||
0020.HK,2022-08-19,2.09,2.13,2.06,2.1,0.0096
|
||||
0020.HK,2022-08-22,2.15,2.22,2.12,2.19,0.0429
|
||||
0020.HK,2022-08-23,2.18,2.23,2.16,2.2,0.0046
|
||||
0020.HK,2022-08-24,2.21,2.3,2.21,2.24,0.0182
|
||||
0020.HK,2022-08-25,2.25,2.35,2.24,2.35,0.0491
|
||||
0020.HK,2022-08-26,2.34,2.34,2.26,2.29,-0.0255
|
||||
0020.HK,2022-08-29,2.23,2.32,2.21,2.29,0
|
||||
0020.HK,2022-08-30,2.29,2.34,2.27,2.31,0.0087
|
||||
0020.HK,2022-08-31,2.28,2.49,2.26,2.47,0.0693
|
||||
0020.HK,2022-09-01,2.47,2.49,2.36,2.39,-0.0324
|
||||
0020.HK,2022-09-02,2.38,2.39,2.25,2.26,-0.0544
|
||||
0020.HK,2022-09-05,2.25,2.35,2.25,2.29,0.0133
|
||||
0020.HK,2022-09-06,2.29,2.35,2.29,2.32,0.0131
|
||||
0020.HK,2022-09-07,2.29,2.31,2.26,2.29,-0.0129
|
||||
0020.HK,2022-09-08,2.3,2.35,2.26,2.29,0
|
||||
0020.HK,2022-09-09,2.28,2.35,2.28,2.28,-0.0044
|
||||
0020.HK,2022-09-13,2.3,2.3,2.15,2.17,-0.0482
|
||||
0020.HK,2022-09-14,2.11,2.14,2.09,2.13,-0.0184
|
||||
0020.HK,2022-09-15,2.12,2.15,2.07,2.08,-0.0235
|
||||
0020.HK,2022-09-16,2.05,2.06,1.99,2,-0.0385
|
||||
0020.HK,2022-09-19,1.98,1.98,1.85,1.91,-0.045
|
||||
0020.HK,2022-09-20,1.94,1.98,1.92,1.94,0.0157
|
||||
0020.HK,2022-09-21,1.93,1.99,1.89,1.9,-0.0206
|
||||
0020.HK,2022-09-22,1.89,1.95,1.84,1.94,0.0211
|
||||
0020.HK,2022-09-23,1.94,1.99,1.91,1.94,0
|
||||
0020.HK,2022-09-26,1.91,1.96,1.84,1.87,-0.0361
|
||||
0020.HK,2022-09-27,1.86,1.87,1.81,1.84,-0.016
|
||||
0020.HK,2022-09-28,1.82,1.83,1.72,1.73,-0.0598
|
||||
0020.HK,2022-09-29,1.78,1.79,1.58,1.63,-0.0578
|
||||
0020.HK,2022-09-30,1.63,1.67,1.57,1.6,-0.0184
|
||||
0020.HK,2022-10-03,1.57,1.64,1.56,1.61,0.0063
|
||||
0020.HK,2022-10-05,1.67,1.77,1.67,1.72,0.0683
|
||||
0020.HK,2022-10-06,1.71,1.75,1.65,1.66,-0.0349
|
||||
0020.HK,2022-10-07,1.66,1.66,1.58,1.59,-0.0422
|
||||
0020.HK,2022-10-10,1.54,1.56,1.49,1.5,-0.0566
|
||||
0020.HK,2022-10-11,1.5,1.53,1.32,1.33,-0.1133
|
||||
0020.HK,2022-10-12,1.33,1.34,1.2,1.28,-0.0376
|
||||
0020.HK,2022-10-13,1.3,1.32,1.2,1.21,-0.0547
|
||||
0020.HK,2022-10-14,1.25,1.28,1.21,1.24,0.0248
|
||||
0020.HK,2022-10-17,1.21,1.29,1.2,1.27,0.0242
|
||||
0020.HK,2022-10-18,1.3,1.37,1.27,1.35,0.063
|
||||
0020.HK,2022-10-19,1.34,1.34,1.27,1.29,-0.0444
|
||||
0020.HK,2022-10-20,1.25,1.31,1.22,1.28,-0.0078
|
||||
0020.HK,2022-10-21,1.29,1.31,1.25,1.26,-0.0156
|
||||
0020.HK,2022-10-24,1.26,1.29,1.13,1.17,-0.0714
|
||||
0020.HK,2022-10-25,1.2,1.26,1.11,1.21,0.0342
|
||||
0020.HK,2022-10-26,1.21,1.3,1.2,1.29,0.0661
|
||||
0020.HK,2022-10-27,1.34,1.35,1.24,1.26,-0.0233
|
||||
0020.HK,2022-10-28,1.26,1.28,1.18,1.19,-0.0556
|
||||
0020.HK,2022-10-31,1.2,1.26,1.18,1.19,0
|
||||
0020.HK,2022-11-01,1.21,1.29,1.18,1.28,0.0756
|
||||
0020.HK,2022-11-02,1.28,1.33,1.25,1.28,0
|
||||
0020.HK,2022-11-03,1.25,1.3,1.23,1.24,-0.0313
|
||||
0020.HK,2022-11-04,1.26,1.38,1.25,1.33,0.0726
|
||||
0020.HK,2022-11-07,1.34,1.8,1.33,1.8,0.3534
|
||||
0020.HK,2022-11-08,1.84,2.04,1.72,1.75,-0.0278
|
||||
0020.HK,2022-11-09,1.78,1.89,1.65,1.69,-0.0343
|
||||
0020.HK,2022-11-10,1.64,1.65,1.53,1.57,-0.071
|
||||
0020.HK,2022-11-11,1.78,1.82,1.65,1.7,0.0828
|
||||
0020.HK,2022-11-14,1.76,1.87,1.71,1.75,0.0294
|
||||
0020.HK,2022-11-15,1.69,1.85,1.63,1.79,0.0229
|
||||
0020.HK,2022-11-16,1.78,2.1,1.76,2.02,0.1285
|
||||
0020.HK,2022-11-17,2,2.2,1.88,2.13,0.0545
|
||||
0020.HK,2022-11-18,2.18,2.24,2.01,2.01,-0.0563
|
||||
0020.HK,2022-11-21,1.98,1.98,1.89,1.94,-0.0348
|
||||
0020.HK,2022-11-22,1.94,2.02,1.87,1.87,-0.0361
|
||||
0020.HK,2022-11-23,1.9,1.96,1.81,1.84,-0.016
|
||||
0020.HK,2022-11-24,1.87,1.89,1.81,1.85,0.0054
|
||||
0020.HK,2022-11-25,1.86,1.86,1.74,1.77,-0.0432
|
||||
0020.HK,2022-11-28,1.69,1.75,1.65,1.73,-0.0226
|
||||
0020.HK,2022-11-29,1.78,1.85,1.75,1.83,0.0578
|
||||
0020.HK,2022-11-30,1.86,1.97,1.84,1.91,0.0437
|
||||
0020.HK,2022-12-01,2,2.02,1.93,1.94,0.0157
|
||||
0020.HK,2022-12-02,1.95,2.02,1.91,2.01,0.0361
|
||||
0020.HK,2022-12-05,2.08,2.17,2.05,2.13,0.0597
|
||||
0020.HK,2022-12-06,2.08,2.13,2.04,2.06,-0.0329
|
||||
0020.HK,2022-12-07,2.06,2.46,2.05,2.2,0.068
|
||||
0020.HK,2022-12-08,2.26,2.39,2.19,2.37,0.0773
|
||||
0020.HK,2022-12-09,2.38,2.47,2.28,2.4,0.0127
|
||||
0020.HK,2022-12-12,2.34,2.54,2.25,2.28,-0.05
|
||||
0020.HK,2022-12-13,2.31,2.37,2.26,2.3,0.0088
|
||||
0020.HK,2022-12-14,2.31,2.35,2.19,2.27,-0.013
|
||||
0020.HK,2022-12-15,2.27,2.27,2.11,2.14,-0.0573
|
||||
0020.HK,2022-12-16,2.12,2.16,2.05,2.12,-0.0093
|
||||
0020.HK,2022-12-19,2.13,2.2,2.1,2.13,0.0047
|
||||
0020.HK,2022-12-20,2.1,2.12,2.05,2.09,-0.0188
|
||||
0020.HK,2022-12-21,2.11,2.16,2.11,2.16,0.0335
|
||||
0020.HK,2022-12-22,2.23,2.24,2.17,2.21,0.0231
|
||||
0020.HK,2022-12-23,2.16,2.21,2.14,2.16,-0.0226
|
||||
0020.HK,2022-12-28,2.18,2.31,2.18,2.29,0.0602
|
||||
0020.HK,2022-12-29,2.23,2.29,2.17,2.18,-0.048
|
||||
0020.HK,2022-12-30,2.25,2.25,2.18,2.22,0.0183
|
||||
0020.HK,2023-01-03,2.19,2.22,2.08,2.19,-0.0135
|
||||
0020.HK,2023-01-04,2.21,2.3,2.2,2.3,0.0502
|
||||
0020.HK,2023-01-05,2.35,2.37,2.26,2.26,-0.0174
|
||||
0020.HK,2023-01-06,2.27,2.28,2.11,2.15,-0.0487
|
||||
0020.HK,2023-01-09,2.18,2.23,2.15,2.19,0.0186
|
||||
0020.HK,2023-01-10,2.19,2.21,2.14,2.21,0.0091
|
||||
0020.HK,2023-01-11,2.23,2.34,2.18,2.22,0.0045
|
||||
0020.HK,2023-01-12,2.25,2.25,2.14,2.16,-0.027
|
||||
0020.HK,2023-01-13,2.17,2.22,2.15,2.22,0.0278
|
||||
0020.HK,2023-01-16,2.23,2.25,2.17,2.18,-0.018
|
||||
0020.HK,2023-01-17,2.18,2.19,2.11,2.13,-0.0229
|
||||
0020.HK,2023-01-18,2.11,2.16,2.1,2.13,0
|
||||
0020.HK,2023-01-19,2.11,2.13,2.08,2.11,-0.0094
|
||||
0020.HK,2023-01-20,2.1,2.17,2.1,2.17,0.0284
|
||||
0020.HK,2023-01-26,2.2,2.27,2.19,2.26,0.0415
|
||||
0020.HK,2023-01-27,2.34,2.74,2.31,2.71,0.1991
|
||||
0020.HK,2023-01-30,2.72,2.85,2.59,2.75,0.0148
|
||||
0020.HK,2023-01-31,2.78,2.84,2.62,2.84,0.0327
|
||||
0020.HK,2023-02-01,2.89,3.1,2.86,2.94,0.0352
|
||||
0020.HK,2023-02-02,3,3.07,2.71,2.73,-0.0714
|
||||
0020.HK,2023-02-03,2.74,2.88,2.74,2.82,0.033
|
||||
0020.HK,2023-02-06,2.79,3.03,2.75,3,0.0638
|
||||
0020.HK,2023-02-07,2.99,3.06,2.86,2.88,-0.04
|
||||
0020.HK,2023-02-08,2.87,2.88,2.68,2.69,-0.066
|
||||
0020.HK,2023-02-09,2.65,2.8,2.61,2.78,0.0335
|
||||
0020.HK,2023-02-10,2.75,2.81,2.68,2.69,-0.0324
|
||||
0020.HK,2023-02-13,2.66,2.71,2.6,2.67,-0.0074
|
||||
0020.HK,2023-02-14,2.73,2.79,2.7,2.74,0.0262
|
||||
0020.HK,2023-02-15,2.78,2.86,2.69,2.73,-0.0036
|
||||
0020.HK,2023-02-16,2.76,2.82,2.63,2.65,-0.0293
|
||||
0020.HK,2023-02-17,2.66,2.7,2.48,2.5,-0.0566
|
||||
0020.HK,2023-02-20,2.5,2.56,2.46,2.48,-0.008
|
||||
0020.HK,2023-02-21,2.5,2.57,2.48,2.49,0.004
|
||||
0020.HK,2023-02-22,2.46,2.49,2.37,2.42,-0.0281
|
||||
0020.HK,2023-02-23,2.44,2.46,2.38,2.4,-0.0083
|
||||
0020.HK,2023-02-24,2.4,2.56,2.39,2.51,0.0458
|
||||
0020.HK,2023-02-27,2.48,2.58,2.46,2.54,0.012
|
||||
0020.HK,2023-02-28,2.58,2.61,2.46,2.52,-0.0079
|
||||
0020.HK,2023-03-01,2.54,2.73,2.54,2.71,0.0754
|
||||
0020.HK,2023-03-02,2.69,2.74,2.66,2.71,0
|
||||
0020.HK,2023-03-03,2.74,2.76,2.63,2.69,-0.0074
|
||||
0020.HK,2023-03-06,2.67,2.71,2.58,2.66,-0.0112
|
||||
0020.HK,2023-03-07,2.65,2.7,2.5,2.53,-0.0489
|
||||
0020.HK,2023-03-08,2.5,2.51,2.4,2.5,-0.0119
|
||||
0020.HK,2023-03-09,2.5,2.54,2.45,2.51,0.004
|
||||
0020.HK,2023-03-10,2.45,2.54,2.44,2.51,0
|
||||
0020.HK,2023-03-13,2.52,2.59,2.47,2.52,0.004
|
||||
0020.HK,2023-03-14,2.53,2.59,2.45,2.48,-0.0159
|
||||
0020.HK,2023-03-15,2.56,2.6,2.5,2.55,0.0282
|
||||
0020.HK,2023-03-16,2.52,2.58,2.44,2.47,-0.0314
|
||||
0020.HK,2023-03-17,2.54,2.73,2.52,2.73,0.1053
|
||||
0020.HK,2023-03-20,2.72,2.74,2.61,2.66,-0.0256
|
||||
0020.HK,2023-03-21,2.7,2.7,2.63,2.68,0.0075
|
||||
0020.HK,2023-03-22,2.7,2.74,2.67,2.68,0
|
||||
0020.HK,2023-03-23,2.7,2.89,2.68,2.88,0.0746
|
||||
0020.HK,2023-03-24,2.87,2.92,2.77,2.9,0.0069
|
||||
0020.HK,2023-03-27,2.95,2.98,2.8,2.81,-0.031
|
||||
0020.HK,2023-03-28,2.81,2.83,2.64,2.68,-0.0463
|
||||
0020.HK,2023-03-29,2.72,2.76,2.56,2.62,-0.0224
|
||||
0020.HK,2023-03-30,2.64,2.77,2.6,2.64,0.0076
|
||||
0020.HK,2023-03-31,2.68,2.68,2.58,2.66,0.0076
|
||||
0020.HK,2023-04-03,2.65,2.9,2.65,2.89,0.0865
|
||||
0020.HK,2023-04-04,3.12,3.4,3.06,3.26,0.128
|
||||
0020.HK,2023-04-06,3.13,3.5,3.11,3.33,0.0215
|
||||
0020.HK,2023-04-11,3.62,3.7,3.13,3.3,-0.009
|
||||
0020.HK,2023-04-12,3.34,3.52,3.1,3.16,-0.0424
|
||||
0020.HK,2023-04-13,3.11,3.17,2.9,2.92,-0.0759
|
||||
0020.HK,2023-04-14,2.97,2.99,2.8,2.93,0.0034
|
||||
0020.HK,2023-04-17,2.92,2.93,2.76,2.81,-0.041
|
||||
0020.HK,2023-04-18,2.79,2.82,2.7,2.77,-0.0142
|
||||
0020.HK,2023-04-19,2.76,2.83,2.71,2.73,-0.0144
|
||||
0020.HK,2023-04-20,2.73,2.8,2.71,2.73,0
|
||||
0020.HK,2023-04-21,2.72,2.74,2.38,2.42,-0.1136
|
||||
0020.HK,2023-04-24,2.42,2.54,2.4,2.48,0.0248
|
||||
0020.HK,2023-04-25,2.48,2.5,2.38,2.42,-0.0242
|
||||
0020.HK,2023-04-26,2.4,2.47,2.4,2.44,0.0083
|
||||
0020.HK,2023-04-27,2.45,2.5,2.4,2.48,0.0164
|
||||
0020.HK,2023-04-28,2.51,2.63,2.5,2.6,0.0484
|
||||
0020.HK,2023-05-02,2.61,2.67,2.54,2.57,-0.0115
|
||||
0020.HK,2023-05-03,2.52,2.59,2.49,2.57,0
|
||||
0020.HK,2023-05-04,2.58,2.64,2.56,2.6,0.0117
|
||||
0020.HK,2023-05-05,2.6,2.65,2.56,2.59,-0.0038
|
||||
0020.HK,2023-05-08,2.5,2.51,2.4,2.43,-0.0618
|
||||
0020.HK,2023-05-09,2.43,2.43,2.22,2.27,-0.0658
|
||||
0020.HK,2023-05-10,2.28,2.35,2.27,2.31,0.0176
|
||||
0020.HK,2023-05-11,2.31,2.32,2.26,2.28,-0.013
|
||||
0020.HK,2023-05-12,2.31,2.34,2.28,2.28,0
|
||||
0020.HK,2023-05-15,2.25,2.29,2.19,2.25,-0.0132
|
||||
0020.HK,2023-05-16,2.28,2.29,2.2,2.22,-0.0133
|
||||
0020.HK,2023-05-17,2.23,2.25,2.16,2.17,-0.0225
|
||||
0020.HK,2023-05-18,2.2,2.3,2.18,2.24,0.0323
|
||||
0020.HK,2023-05-19,2.24,2.25,2.19,2.21,-0.0134
|
||||
0020.HK,2023-05-22,2.21,2.26,2.21,2.23,0.009
|
||||
0020.HK,2023-05-23,2.25,2.26,2.19,2.19,-0.0179
|
||||
0020.HK,2023-05-24,2.18,2.18,2.07,2.08,-0.0502
|
||||
0020.HK,2023-05-25,2.07,2.13,2.04,2.1,0.0096
|
||||
0020.HK,2023-05-29,2.15,2.22,2.12,2.15,0.0238
|
||||
0020.HK,2023-05-30,2.15,2.2,2.1,2.19,0.0186
|
||||
0020.HK,2023-05-31,2.16,2.18,2.06,2.1,-0.0411
|
||||
0020.HK,2023-06-01,2.1,2.21,2.07,2.12,0.0095
|
||||
0020.HK,2023-06-02,2.2,2.23,2.16,2.2,0.0377
|
||||
0020.HK,2023-06-05,2.24,2.26,2.16,2.18,-0.0091
|
||||
0020.HK,2023-06-06,2.16,2.2,2.1,2.12,-0.0275
|
||||
0020.HK,2023-06-07,2.18,2.18,2.12,2.14,0.0094
|
||||
0020.HK,2023-06-08,2.12,2.14,2.08,2.11,-0.014
|
||||
0020.HK,2023-06-09,2.12,2.19,2.07,2.18,0.0332
|
||||
0020.HK,2023-06-12,2.18,2.2,2.15,2.17,-0.0046
|
||||
0020.HK,2023-06-13,2.16,2.34,2.15,2.33,0.0737
|
||||
0020.HK,2023-06-14,2.36,2.36,2.23,2.26,-0.03
|
||||
0020.HK,2023-06-15,2.29,2.32,2.24,2.28,0.0088
|
||||
0020.HK,2023-06-16,2.29,2.33,2.26,2.29,0.0044
|
||||
0020.HK,2023-06-19,2.27,2.36,2.22,2.28,-0.0044
|
||||
0020.HK,2023-06-20,2.28,2.3,2.21,2.25,-0.0132
|
||||
0020.HK,2023-06-21,2.22,2.24,2.1,2.11,-0.0622
|
||||
0020.HK,2023-06-23,2.14,2.14,2.08,2.1,-0.0047
|
||||
0020.HK,2023-06-26,2.1,2.14,2.05,2.13,0.0143
|
||||
0020.HK,2023-06-27,2.13,2.16,2.12,2.13,0
|
||||
0020.HK,2023-06-28,2.13,2.16,2.07,2.15,0.0094
|
||||
0020.HK,2023-06-29,2.15,2.16,2.09,2.11,-0.0186
|
||||
0020.HK,2023-06-30,2.11,2.12,2.07,2.07,-0.019
|
||||
0020.HK,2023-07-03,2.1,2.23,2.09,2.18,0.0531
|
||||
0020.HK,2023-07-04,2.16,2.16,1.97,1.99,-0.0872
|
||||
0020.HK,2023-07-05,1.99,2,1.87,1.9,-0.0452
|
||||
0020.HK,2023-07-06,1.9,1.94,1.86,1.89,-0.0053
|
||||
0020.HK,2023-07-07,1.89,1.92,1.86,1.87,-0.0106
|
||||
0020.HK,2023-07-10,1.91,1.93,1.87,1.87,0
|
||||
0020.HK,2023-07-11,1.89,1.89,1.78,1.79,-0.0428
|
||||
0020.HK,2023-07-12,1.8,1.85,1.78,1.79,0
|
||||
0020.HK,2023-07-13,1.83,1.94,1.81,1.93,0.0782
|
||||
0020.HK,2023-07-14,1.94,1.96,1.89,1.92,-0.0052
|
||||
0020.HK,2023-07-18,1.9,1.9,1.8,1.8,-0.0625
|
||||
0020.HK,2023-07-19,1.8,1.83,1.78,1.79,-0.0056
|
||||
0020.HK,2023-07-20,1.8,1.82,1.68,1.69,-0.0559
|
||||
0020.HK,2023-07-21,1.64,1.84,1.61,1.72,0.0178
|
||||
0020.HK,2023-07-24,1.72,1.75,1.67,1.68,-0.0233
|
||||
0020.HK,2023-07-25,1.73,1.79,1.71,1.77,0.0536
|
||||
0020.HK,2023-07-26,1.77,1.79,1.73,1.76,-0.0056
|
||||
0020.HK,2023-07-27,1.77,1.81,1.76,1.8,0.0227
|
||||
0020.HK,2023-07-28,1.76,1.84,1.75,1.82,0.0111
|
||||
0020.HK,2023-07-31,1.85,1.91,1.84,1.85,0.0165
|
||||
0020.HK,2023-08-01,1.87,1.88,1.8,1.82,-0.0162
|
||||
0020.HK,2023-08-02,1.8,1.83,1.75,1.76,-0.033
|
||||
0020.HK,2023-08-03,1.75,1.79,1.74,1.76,0
|
||||
0020.HK,2023-08-04,1.79,1.81,1.75,1.76,0
|
||||
0020.HK,2023-08-07,1.74,1.74,1.69,1.7,-0.0341
|
||||
0020.HK,2023-08-08,1.68,1.72,1.67,1.68,-0.0118
|
||||
0020.HK,2023-08-09,1.67,1.7,1.66,1.69,0.006
|
||||
0020.HK,2023-08-10,1.68,1.68,1.62,1.65,-0.0237
|
||||
0020.HK,2023-08-11,1.65,1.65,1.56,1.59,-0.0364
|
||||
0020.HK,2023-08-14,1.54,1.62,1.5,1.61,0.0126
|
||||
0020.HK,2023-08-15,1.61,1.62,1.55,1.57,-0.0248
|
||||
0020.HK,2023-08-16,1.55,1.57,1.51,1.52,-0.0318
|
||||
0020.HK,2023-08-17,1.5,1.56,1.5,1.55,0.0197
|
||||
0020.HK,2023-08-18,1.55,1.56,1.5,1.51,-0.0258
|
||||
0020.HK,2023-08-21,1.51,1.52,1.48,1.49,-0.0132
|
||||
0020.HK,2023-08-22,1.49,1.53,1.47,1.52,0.0201
|
||||
0020.HK,2023-08-23,1.52,1.53,1.48,1.5,-0.0132
|
||||
0020.HK,2023-08-24,1.51,1.58,1.5,1.57,0.0467
|
||||
0020.HK,2023-08-25,1.54,1.55,1.51,1.53,-0.0255
|
||||
0020.HK,2023-08-28,1.58,1.6,1.5,1.5,-0.0196
|
||||
0020.HK,2023-08-29,1.51,1.55,1.37,1.54,0.0267
|
||||
0020.HK,2023-08-30,1.57,1.58,1.48,1.51,-0.0195
|
||||
0020.HK,2023-08-31,1.56,1.62,1.53,1.56,0.0331
|
||||
0020.HK,2023-09-04,1.58,1.71,1.57,1.68,0.0769
|
||||
0020.HK,2023-09-05,1.68,1.68,1.55,1.6,-0.0476
|
||||
0020.HK,2023-09-06,1.59,1.61,1.54,1.59,-0.0063
|
||||
0020.HK,2023-09-07,1.58,1.58,1.5,1.51,-0.0503
|
||||
0020.HK,2023-09-11,1.48,1.55,1.45,1.55,0.0265
|
||||
0020.HK,2023-09-12,1.55,1.55,1.49,1.5,-0.0323
|
||||
0020.HK,2023-09-13,1.51,1.52,1.47,1.48,-0.0133
|
||||
0020.HK,2023-09-14,1.49,1.5,1.47,1.48,0
|
||||
0020.HK,2023-09-15,1.49,1.51,1.43,1.49,0.0068
|
||||
0020.HK,2023-09-18,1.47,1.49,1.45,1.46,-0.0201
|
||||
0020.HK,2023-09-19,1.45,1.47,1.43,1.44,-0.0137
|
||||
0020.HK,2023-09-20,1.43,1.43,1.4,1.41,-0.0208
|
||||
0020.HK,2023-09-21,1.4,1.42,1.37,1.38,-0.0213
|
||||
0020.HK,2023-09-22,1.37,1.45,1.36,1.45,0.0507
|
||||
0020.HK,2023-09-25,1.46,1.47,1.42,1.42,-0.0207
|
||||
0020.HK,2023-09-26,1.43,1.46,1.4,1.42,0
|
||||
0020.HK,2023-09-27,1.43,1.44,1.38,1.39,-0.0211
|
||||
0020.HK,2023-09-28,1.39,1.42,1.37,1.37,-0.0144
|
||||
0020.HK,2023-09-29,1.39,1.44,1.38,1.43,0.0438
|
||||
0020.HK,2023-10-03,1.41,1.42,1.35,1.37,-0.042
|
||||
0020.HK,2023-10-04,1.37,1.37,1.31,1.32,-0.0365
|
||||
0020.HK,2023-10-05,1.33,1.37,1.32,1.34,0.0152
|
||||
0020.HK,2023-10-06,1.36,1.43,1.35,1.43,0.0672
|
||||
0020.HK,2023-10-09,1.46,1.46,1.4,1.41,-0.014
|
||||
0020.HK,2023-10-10,1.41,1.45,1.41,1.42,0.0071
|
||||
0020.HK,2023-10-11,1.45,1.49,1.43,1.47,0.0352
|
||||
0020.HK,2023-10-12,1.5,1.5,1.46,1.48,0.0068
|
||||
0020.HK,2023-10-13,1.46,1.47,1.43,1.44,-0.027
|
||||
0020.HK,2023-10-16,1.44,1.45,1.4,1.43,-0.0069
|
||||
0020.HK,2023-10-17,1.45,1.45,1.4,1.43,0
|
||||
0020.HK,2023-10-18,1.42,1.43,1.39,1.41,-0.014
|
||||
0020.HK,2023-10-19,1.4,1.43,1.38,1.4,-0.0071
|
||||
0020.HK,2023-10-20,1.4,1.4,1.37,1.38,-0.0143
|
||||
0020.HK,2023-10-24,1.38,1.39,1.34,1.35,-0.0217
|
||||
0020.HK,2023-10-25,1.4,1.43,1.37,1.39,0.0296
|
||||
0020.HK,2023-10-26,1.38,1.41,1.36,1.38,-0.0072
|
||||
0020.HK,2023-10-27,1.39,1.42,1.37,1.4,0.0145
|
||||
0020.HK,2023-10-30,1.4,1.46,1.4,1.42,0.0143
|
||||
0020.HK,2023-10-31,1.42,1.43,1.38,1.4,-0.0141
|
||||
0020.HK,2023-11-01,1.4,1.41,1.38,1.4,0
|
||||
0020.HK,2023-11-02,1.41,1.43,1.38,1.39,-0.0071
|
||||
0020.HK,2023-11-03,1.41,1.46,1.39,1.42,0.0216
|
||||
0020.HK,2023-11-06,1.45,1.54,1.43,1.53,0.0775
|
||||
0020.HK,2023-11-07,1.51,1.56,1.5,1.54,0.0065
|
||||
0020.HK,2023-11-08,1.55,1.57,1.52,1.53,-0.0065
|
||||
0020.HK,2023-11-09,1.53,1.56,1.48,1.49,-0.0261
|
||||
0020.HK,2023-11-10,1.47,1.48,1.43,1.44,-0.0336
|
||||
0020.HK,2023-11-13,1.46,1.54,1.46,1.53,0.0625
|
||||
0020.HK,2023-11-14,1.53,1.55,1.5,1.54,0.0065
|
||||
0020.HK,2023-11-15,1.58,1.6,1.53,1.56,0.013
|
||||
0020.HK,2023-11-16,1.57,1.57,1.51,1.52,-0.0256
|
||||
0020.HK,2023-11-17,1.5,1.52,1.47,1.48,-0.0263
|
||||
0020.HK,2023-11-20,1.5,1.55,1.48,1.55,0.0473
|
||||
0020.HK,2023-11-21,1.57,1.57,1.48,1.5,-0.0323
|
||||
0020.HK,2023-11-22,1.49,1.5,1.47,1.47,-0.02
|
||||
0020.HK,2023-11-23,1.48,1.53,1.46,1.52,0.034
|
||||
0020.HK,2023-11-24,1.51,1.52,1.46,1.46,-0.0395
|
||||
0020.HK,2023-11-27,1.47,1.47,1.43,1.44,-0.0137
|
||||
0020.HK,2023-11-28,1.44,1.45,1.3,1.37,-0.0486
|
||||
0020.HK,2023-11-29,1.37,1.38,1.33,1.36,-0.0073
|
||||
0020.HK,2023-11-30,1.36,1.37,1.33,1.36,0
|
||||
0020.HK,2023-12-01,1.36,1.39,1.34,1.38,0.0147
|
||||
0020.HK,2023-12-04,1.39,1.4,1.34,1.36,-0.0145
|
||||
0020.HK,2023-12-05,1.35,1.36,1.31,1.32,-0.0294
|
||||
0020.HK,2023-12-06,1.32,1.34,1.25,1.29,-0.0227
|
||||
0020.HK,2023-12-07,1.28,1.3,1.25,1.28,-0.0078
|
||||
0020.HK,2023-12-08,1.3,1.33,1.27,1.29,0.0078
|
||||
0020.HK,2023-12-11,1.29,1.29,1.23,1.25,-0.031
|
||||
0020.HK,2023-12-12,1.26,1.27,1.23,1.24,-0.008
|
||||
0020.HK,2023-12-13,1.24,1.24,1.2,1.22,-0.0161
|
||||
0020.HK,2023-12-14,1.24,1.25,1.21,1.22,0
|
||||
0020.HK,2023-12-15,1.24,1.29,1.24,1.26,0.0328
|
||||
0020.HK,2023-12-18,1.08,1.15,1.03,1.12,-0.1111
|
||||
0020.HK,2023-12-19,1.12,1.17,1.11,1.12,0
|
||||
0020.HK,2023-12-20,1.13,1.18,1.13,1.15,0.0268
|
||||
0020.HK,2023-12-21,1.14,1.17,1.13,1.15,0
|
||||
0020.HK,2023-12-22,1.17,1.17,1.08,1.08,-0.0609
|
||||
0020.HK,2023-12-27,1.09,1.12,1.06,1.09,0.0093
|
||||
0020.HK,2023-12-28,1.1,1.17,1.09,1.15,0.055
|
||||
0020.HK,2023-12-29,1.15,1.17,1.14,1.16,0.0087
|
||||
0020.HK,2024-01-02,1.18,1.19,1.14,1.16,0
|
||||
0020.HK,2024-01-03,1.14,1.16,1.11,1.12,-0.0345
|
||||
0020.HK,2024-01-04,1.12,1.13,1.09,1.1,-0.0179
|
||||
0020.HK,2024-01-05,1.1,1.12,1.08,1.09,-0.0091
|
||||
0020.HK,2024-01-08,1.09,1.09,1.02,1.03,-0.055
|
||||
0020.HK,2024-01-09,1.04,1.07,1.03,1.05,0.0194
|
||||
0020.HK,2024-01-10,1.05,1.06,1.02,1.02,-0.0286
|
||||
0020.HK,2024-01-11,1.03,1.07,1.02,1.05,0.0294
|
||||
0020.HK,2024-01-12,1.05,1.07,1.04,1.05,0
|
||||
0020.HK,2024-01-15,1.05,1.06,1.03,1.06,0.0095
|
||||
0020.HK,2024-01-16,1.05,1.07,1.03,1.03,-0.0283
|
||||
0020.HK,2024-01-17,1.03,1.03,0.91,0.91,-0.1165
|
||||
0020.HK,2024-01-18,0.91,0.94,0.89,0.91,0
|
||||
0020.HK,2024-01-19,0.91,0.93,0.87,0.89,-0.022
|
||||
0020.HK,2024-01-22,0.89,0.9,0.82,0.83,-0.0674
|
||||
0020.HK,2024-01-23,0.84,0.93,0.83,0.91,0.0964
|
||||
0020.HK,2024-01-24,0.92,0.93,0.88,0.92,0.011
|
||||
0020.HK,2024-01-25,0.93,0.93,0.89,0.92,0
|
||||
0020.HK,2024-01-26,0.91,0.92,0.87,0.87,-0.0543
|
||||
0020.HK,2024-01-29,0.87,0.9,0.86,0.87,0
|
||||
0020.HK,2024-01-30,0.87,0.87,0.83,0.84,-0.0345
|
||||
0020.HK,2024-01-31,0.83,0.85,0.78,0.79,-0.0595
|
||||
0020.HK,2024-02-01,0.8,0.83,0.79,0.79,0
|
||||
0020.HK,2024-02-02,0.81,0.84,0.76,0.78,-0.0127
|
||||
0020.HK,2024-02-05,0.77,0.82,0.76,0.78,0
|
||||
0020.HK,2024-02-06,0.78,0.88,0.78,0.86,0.1026
|
||||
0020.HK,2024-02-07,0.88,0.88,0.82,0.83,-0.0349
|
||||
0020.HK,2024-02-08,0.83,0.88,0.83,0.86,0.0361
|
||||
0020.HK,2024-02-09,0.85,0.85,0.81,0.82,-0.0465
|
||||
0020.HK,2024-02-14,0.81,0.85,0.78,0.8,-0.0244
|
||||
0020.HK,2024-02-15,0.81,0.82,0.78,0.8,0
|
||||
0020.HK,2024-02-16,0.8,0.86,0.8,0.85,0.0625
|
||||
0020.HK,2024-02-19,0.88,0.91,0.85,0.86,0.0118
|
||||
0020.HK,2024-02-20,0.86,0.87,0.83,0.85,-0.0116
|
||||
0020.HK,2024-02-21,0.84,0.94,0.83,0.91,0.0706
|
||||
0020.HK,2024-02-22,0.92,0.93,0.9,0.93,0.022
|
||||
0020.HK,2024-02-23,0.93,0.96,0.91,0.93,0
|
||||
0020.HK,2024-02-26,0.93,0.96,0.91,0.92,-0.0108
|
||||
0020.HK,2024-02-27,0.92,0.97,0.88,0.96,0.0435
|
||||
0020.HK,2024-02-28,0.97,0.99,0.89,0.9,-0.0625
|
||||
0020.HK,2024-02-29,0.89,0.94,0.89,0.9,0
|
||||
0020.HK,2024-03-01,0.9,0.91,0.88,0.89,-0.0111
|
||||
0020.HK,2024-03-04,0.9,0.93,0.89,0.89,0
|
||||
0020.HK,2024-03-05,0.88,0.89,0.82,0.83,-0.0674
|
||||
0020.HK,2024-03-06,0.84,0.86,0.82,0.84,0.012
|
||||
0020.HK,2024-03-07,0.84,0.89,0.83,0.83,-0.0119
|
||||
0020.HK,2024-03-08,0.84,0.87,0.83,0.86,0.0361
|
||||
0020.HK,2024-03-11,0.87,0.91,0.86,0.91,0.0581
|
||||
0020.HK,2024-03-12,0.92,0.93,0.9,0.92,0.011
|
||||
0020.HK,2024-03-13,0.92,0.94,0.88,0.89,-0.0326
|
||||
0020.HK,2024-03-14,0.89,0.9,0.84,0.85,-0.0449
|
||||
0020.HK,2024-03-15,0.84,0.85,0.82,0.84,-0.0118
|
||||
0020.HK,2024-03-18,0.85,0.85,0.83,0.84,0
|
||||
0020.HK,2024-03-19,0.84,0.85,0.82,0.82,-0.0238
|
||||
0020.HK,2024-03-20,0.82,0.84,0.82,0.82,0
|
||||
0020.HK,2024-03-21,0.84,0.87,0.83,0.84,0.0244
|
||||
0020.HK,2024-03-22,0.84,0.85,0.8,0.8,-0.0476
|
||||
0020.HK,2024-03-25,0.81,0.81,0.78,0.78,-0.025
|
||||
0020.HK,2024-03-26,0.78,0.79,0.76,0.78,0
|
||||
0020.HK,2024-03-27,0.79,0.79,0.7,0.7,-0.1026
|
||||
0020.HK,2024-03-28,0.7,0.74,0.7,0.71,0.0143
|
||||
0020.HK,2024-04-02,0.71,0.74,0.71,0.73,0.0282
|
||||
0020.HK,2024-04-03,0.73,0.74,0.68,0.68,-0.0685
|
||||
0020.HK,2024-04-05,0.68,0.69,0.62,0.65,-0.0441
|
||||
0020.HK,2024-04-08,0.66,0.68,0.64,0.65,0
|
||||
0020.HK,2024-04-09,0.65,0.68,0.65,0.67,0.0308
|
||||
0020.HK,2024-04-10,0.68,0.7,0.67,0.68,0.0149
|
||||
0020.HK,2024-04-11,0.67,0.7,0.66,0.68,0
|
||||
0020.HK,2024-04-12,0.68,0.71,0.66,0.66,-0.0294
|
||||
0020.HK,2024-04-15,0.65,0.66,0.61,0.62,-0.0606
|
||||
0020.HK,2024-04-16,0.61,0.62,0.58,0.59,-0.0484
|
||||
0020.HK,2024-04-17,0.59,0.62,0.58,0.62,0.0508
|
||||
0020.HK,2024-04-18,0.62,0.63,0.6,0.61,-0.0161
|
||||
0020.HK,2024-04-19,0.61,0.61,0.58,0.58,-0.0492
|
||||
0020.HK,2024-04-22,0.58,0.61,0.58,0.6,0.0345
|
||||
0020.HK,2024-04-23,0.61,0.63,0.6,0.61,0.0167
|
||||
0020.HK,2024-04-24,0.63,0.83,0.62,0.8,0.3115
|
||||
0020.HK,2024-04-25,0.94,0.96,0.82,0.83,0.0375
|
||||
0020.HK,2024-04-26,0.84,1.23,0.84,1.19,0.4337
|
||||
0020.HK,2024-04-29,1.22,1.32,1.16,1.21,0.0168
|
||||
0020.HK,2024-04-30,1.25,1.28,1.11,1.22,0.0083
|
||||
0020.HK,2024-05-02,1.21,1.68,1.19,1.66,0.3607
|
||||
0020.HK,2024-05-03,1.76,1.76,1.54,1.6,-0.0361
|
||||
0020.HK,2024-05-06,1.58,1.73,1.51,1.68,0.05
|
||||
0020.HK,2024-05-07,1.7,1.77,1.63,1.65,-0.0179
|
||||
0020.HK,2024-05-08,1.68,1.74,1.38,1.41,-0.1455
|
||||
0020.HK,2024-05-09,1.42,1.49,1.4,1.45,0.0284
|
||||
0020.HK,2024-05-10,1.47,1.5,1.34,1.47,0.0138
|
||||
0020.HK,2024-05-13,1.48,1.55,1.42,1.46,-0.0068
|
||||
0020.HK,2024-05-14,1.5,1.54,1.42,1.45,-0.0068
|
||||
0020.HK,2024-05-16,1.47,1.48,1.37,1.38,-0.0483
|
||||
0020.HK,2024-05-17,1.39,1.44,1.36,1.4,0.0145
|
||||
0020.HK,2024-05-20,1.4,1.6,1.38,1.57,0.1214
|
||||
0020.HK,2024-05-21,1.56,1.59,1.5,1.5,-0.0446
|
||||
0020.HK,2024-05-22,1.52,1.53,1.46,1.48,-0.0133
|
||||
0020.HK,2024-05-23,1.5,1.55,1.47,1.48,0
|
||||
0020.HK,2024-05-24,1.49,1.52,1.37,1.4,-0.0541
|
||||
0020.HK,2024-05-27,1.4,1.42,1.29,1.37,-0.0214
|
||||
0020.HK,2024-05-28,1.39,1.4,1.3,1.31,-0.0438
|
||||
0020.HK,2024-05-29,1.31,1.46,1.27,1.37,0.0458
|
||||
0020.HK,2024-05-30,1.37,1.4,1.35,1.36,-0.0073
|
||||
0020.HK,2024-05-31,1.38,1.42,1.31,1.32,-0.0294
|
||||
0020.HK,2024-06-03,1.33,1.38,1.32,1.36,0.0303
|
||||
0020.HK,2024-06-04,1.36,1.42,1.35,1.39,0.0221
|
||||
0020.HK,2024-06-05,1.39,1.45,1.37,1.38,-0.0072
|
||||
0020.HK,2024-06-06,1.4,1.5,1.38,1.48,0.0725
|
||||
0020.HK,2024-06-07,1.49,1.51,1.44,1.45,-0.0203
|
||||
0020.HK,2024-06-11,1.44,1.45,1.38,1.4,-0.0345
|
||||
0020.HK,2024-06-12,1.39,1.43,1.37,1.4,0
|
||||
0020.HK,2024-06-13,1.42,1.43,1.37,1.38,-0.0143
|
||||
0020.HK,2024-06-14,1.37,1.39,1.33,1.35,-0.0217
|
||||
0020.HK,2024-06-17,1.34,1.36,1.32,1.33,-0.0148
|
||||
0020.HK,2024-06-18,1.34,1.38,1.32,1.34,0.0075
|
||||
0020.HK,2024-06-19,1.35,1.46,1.35,1.43,0.0672
|
||||
0020.HK,2024-06-20,1.44,1.45,1.32,1.32,-0.0769
|
||||
0020.HK,2024-06-21,1.26,1.37,1.23,1.36,0.0303
|
||||
0020.HK,2024-06-24,1.37,1.38,1.32,1.37,0.0074
|
||||
0020.HK,2024-06-25,1.37,1.39,1.36,1.37,0
|
||||
0020.HK,2024-06-26,1.37,1.44,1.33,1.4,0.0219
|
||||
0020.HK,2024-06-27,1.4,1.41,1.33,1.34,-0.0429
|
||||
0020.HK,2024-06-28,1.33,1.37,1.32,1.32,-0.0149
|
||||
0020.HK,2024-07-02,1.32,1.41,1.31,1.38,0.0455
|
||||
0020.HK,2024-07-03,1.41,1.62,1.41,1.62,0.1739
|
||||
0020.HK,2024-07-04,1.66,1.67,1.56,1.61,-0.0062
|
||||
0020.HK,2024-07-05,1.61,1.63,1.35,1.35,-0.1615
|
||||
0020.HK,2024-07-08,1.36,1.39,1.28,1.31,-0.0296
|
||||
0020.HK,2024-07-09,1.32,1.35,1.29,1.33,0.0153
|
||||
0020.HK,2024-07-10,1.34,1.37,1.33,1.33,0
|
||||
0020.HK,2024-07-11,1.34,1.36,1.34,1.35,0.015
|
||||
0020.HK,2024-07-12,1.36,1.39,1.35,1.38,0.0222
|
||||
0020.HK,2024-07-15,1.37,1.37,1.32,1.33,-0.0362
|
||||
0020.HK,2024-07-16,1.32,1.35,1.32,1.34,0.0075
|
||||
0020.HK,2024-07-17,1.34,1.36,1.33,1.34,0
|
||||
0020.HK,2024-07-18,1.34,1.35,1.29,1.31,-0.0224
|
||||
0020.HK,2024-07-19,1.29,1.31,1.28,1.3,-0.0076
|
||||
0020.HK,2024-07-22,1.3,1.31,1.19,1.26,-0.0308
|
||||
0020.HK,2024-07-23,1.26,1.26,1.19,1.2,-0.0476
|
||||
0020.HK,2024-07-24,1.21,1.23,1.15,1.16,-0.0333
|
||||
0020.HK,2024-07-25,1.15,1.18,1.13,1.16,0
|
||||
0020.HK,2024-07-26,1.17,1.22,1.15,1.17,0.0086
|
||||
0020.HK,2024-07-29,1.19,1.21,1.16,1.18,0.0085
|
||||
0020.HK,2024-07-30,1.18,1.18,1.13,1.14,-0.0339
|
||||
0020.HK,2024-07-31,1.14,1.22,1.13,1.21,0.0614
|
||||
0020.HK,2024-08-01,1.21,1.23,1.17,1.18,-0.0248
|
||||
0020.HK,2024-08-02,1.16,1.18,1.14,1.16,-0.0169
|
||||
0020.HK,2024-08-05,1.14,1.17,1.05,1.07,-0.0776
|
||||
0020.HK,2024-08-06,1.1,1.12,1.05,1.09,0.0187
|
||||
0020.HK,2024-08-07,1.1,1.12,1.08,1.09,0
|
||||
0020.HK,2024-08-08,1.08,1.1,1.05,1.08,-0.0092
|
||||
0020.HK,2024-08-09,1.1,1.12,1.09,1.11,0.0278
|
||||
0020.HK,2024-08-12,1.12,1.12,1.07,1.1,-0.009
|
||||
0020.HK,2024-08-13,1.11,1.11,1.08,1.1,0
|
||||
0020.HK,2024-08-14,1.11,1.12,1.07,1.07,-0.0273
|
||||
0020.HK,2024-08-15,1.07,1.15,1.06,1.11,0.0374
|
||||
0020.HK,2024-08-16,1.12,1.14,1.1,1.11,0
|
||||
0020.HK,2024-08-19,1.11,1.15,1.11,1.13,0.018
|
||||
0020.HK,2024-08-20,1.13,1.14,1.09,1.1,-0.0265
|
||||
0020.HK,2024-08-21,1.09,1.12,1.07,1.11,0.0091
|
||||
0020.HK,2024-08-22,1.11,1.13,1.08,1.09,-0.018
|
||||
0020.HK,2024-08-23,1.09,1.11,1.08,1.1,0.0092
|
||||
0020.HK,2024-08-26,1.11,1.17,1.1,1.17,0.0636
|
||||
0020.HK,2024-08-27,1.16,1.19,1.14,1.18,0.0085
|
||||
0020.HK,2024-08-28,1.16,1.17,1.1,1.12,-0.0508
|
||||
0020.HK,2024-08-29,1.1,1.18,1.08,1.16,0.0357
|
||||
0020.HK,2024-08-30,1.17,1.2,1.15,1.18,0.0172
|
||||
0020.HK,2024-09-02,1.17,1.18,1.13,1.14,-0.0339
|
||||
0020.HK,2024-09-03,1.14,1.16,1.13,1.14,0
|
||||
0020.HK,2024-09-04,1.13,1.13,1.09,1.11,-0.0263
|
||||
0020.HK,2024-09-05,1.11,1.14,1.11,1.13,0.018
|
||||
0020.HK,2024-09-09,1.12,1.13,1.08,1.09,-0.0354
|
||||
0020.HK,2024-09-10,1.1,1.11,1.07,1.09,0
|
||||
0020.HK,2024-09-11,1.08,1.09,1,1.03,-0.055
|
||||
0020.HK,2024-09-12,1.04,1.06,1.03,1.04,0.0097
|
||||
0020.HK,2024-09-13,1.04,1.08,1.04,1.04,0
|
||||
0020.HK,2024-09-16,1.05,1.07,1.03,1.07,0.0288
|
||||
0020.HK,2024-09-17,1.08,1.11,1.06,1.1,0.028
|
||||
0020.HK,2024-09-19,1.09,1.13,1.07,1.1,0
|
||||
0020.HK,2024-09-20,1.11,1.2,1.11,1.17,0.0636
|
||||
0020.HK,2024-09-23,1.17,1.21,1.16,1.19,0.0171
|
||||
0020.HK,2024-09-24,1.21,1.24,1.18,1.23,0.0336
|
||||
0020.HK,2024-09-25,1.26,1.33,1.24,1.25,0.0163
|
||||
0020.HK,2024-09-26,1.26,1.4,1.24,1.39,0.112
|
||||
0020.HK,2024-09-27,1.44,1.5,1.4,1.46,0.0504
|
||||
0020.HK,2024-09-30,1.51,1.75,1.51,1.72,0.1781
|
||||
0020.HK,2024-10-02,1.72,1.88,1.68,1.85,0.0756
|
||||
0020.HK,2024-10-03,1.87,1.9,1.63,1.77,-0.0432
|
||||
0020.HK,2024-10-04,1.74,2.15,1.73,2.12,0.1977
|
||||
0020.HK,2024-10-07,2.23,2.34,2.11,2.33,0.0991
|
||||
0020.HK,2024-10-08,2.33,2.35,1.82,1.83,-0.2146
|
||||
0020.HK,2024-10-09,1.89,1.99,1.65,1.74,-0.0492
|
||||
0020.HK,2024-10-10,1.82,1.83,1.68,1.73,-0.0057
|
||||
0020.HK,2024-10-14,1.7,1.71,1.55,1.62,-0.0636
|
||||
0020.HK,2024-10-15,1.62,1.68,1.5,1.53,-0.0556
|
||||
0020.HK,2024-10-16,1.49,1.55,1.47,1.5,-0.0196
|
||||
0020.HK,2024-10-17,1.52,1.61,1.5,1.52,0.0133
|
||||
0020.HK,2024-10-18,1.53,1.68,1.48,1.66,0.0921
|
||||
0020.HK,2024-10-21,1.68,1.69,1.59,1.59,-0.0422
|
||||
0020.HK,2024-10-22,1.6,1.68,1.58,1.64,0.0314
|
||||
0020.HK,2024-10-23,1.66,1.74,1.62,1.66,0.0122
|
||||
0020.HK,2024-10-24,1.63,1.66,1.58,1.59,-0.0422
|
||||
0020.HK,2024-10-25,1.59,1.65,1.58,1.6,0.0063
|
||||
0020.HK,2024-10-28,1.6,1.63,1.57,1.6,0
|
||||
0020.HK,2024-10-29,1.63,1.66,1.54,1.57,-0.0188
|
||||
0020.HK,2024-10-30,1.58,1.6,1.52,1.55,-0.0127
|
||||
0020.HK,2024-10-31,1.55,1.58,1.53,1.56,0.0065
|
||||
0020.HK,2024-11-01,1.56,1.57,1.48,1.52,-0.0256
|
||||
0020.HK,2024-11-04,1.52,1.55,1.51,1.53,0.0066
|
||||
0020.HK,2024-11-05,1.53,1.65,1.5,1.64,0.0719
|
||||
0020.HK,2024-11-06,1.64,1.69,1.59,1.62,-0.0122
|
||||
0020.HK,2024-11-07,1.62,1.74,1.59,1.74,0.0741
|
||||
0020.HK,2024-11-08,1.78,1.8,1.68,1.71,-0.0172
|
||||
0020.HK,2024-11-11,1.65,1.73,1.63,1.72,0.0058
|
||||
0020.HK,2024-11-12,1.74,1.75,1.6,1.61,-0.064
|
||||
0020.HK,2024-11-13,1.59,1.62,1.57,1.61,0
|
||||
0020.HK,2024-11-14,1.6,1.7,1.59,1.61,0
|
||||
0020.HK,2024-11-15,1.63,1.66,1.58,1.58,-0.0186
|
||||
0020.HK,2024-11-18,1.6,1.61,1.52,1.53,-0.0316
|
||||
0020.HK,2024-11-19,1.55,1.57,1.52,1.56,0.0196
|
||||
0020.HK,2024-11-20,1.56,1.6,1.54,1.58,0.0128
|
||||
0020.HK,2024-11-21,1.58,1.6,1.54,1.55,-0.019
|
||||
0020.HK,2024-11-22,1.55,1.59,1.42,1.43,-0.0774
|
||||
0020.HK,2024-11-25,1.44,1.46,1.39,1.44,0.007
|
||||
0020.HK,2024-11-26,1.44,1.48,1.42,1.42,-0.0139
|
||||
0020.HK,2024-11-27,1.43,1.5,1.4,1.48,0.0423
|
||||
0020.HK,2024-11-28,1.49,1.49,1.44,1.44,-0.027
|
||||
0020.HK,2024-11-29,1.45,1.52,1.45,1.49,0.0347
|
||||
0020.HK,2024-12-02,1.49,1.51,1.47,1.5,0.0067
|
||||
0020.HK,2024-12-03,1.5,1.51,1.46,1.5,0
|
||||
0020.HK,2024-12-04,1.51,1.53,1.47,1.49,-0.0067
|
||||
0020.HK,2024-12-05,1.49,1.52,1.48,1.49,0
|
||||
0020.HK,2024-12-06,1.49,1.74,1.47,1.71,0.1477
|
||||
0020.HK,2024-12-09,1.71,1.86,1.68,1.85,0.0819
|
||||
0020.HK,2024-12-10,1.91,1.92,1.6,1.6,-0.1351
|
||||
0020.HK,2024-12-11,1.58,1.64,1.54,1.58,-0.0125
|
||||
0020.HK,2024-12-12,1.59,1.62,1.56,1.56,-0.0127
|
||||
0020.HK,2024-12-13,1.55,1.56,1.49,1.55,-0.0064
|
||||
0020.HK,2024-12-16,1.54,1.54,1.48,1.49,-0.0387
|
||||
0020.HK,2024-12-17,1.48,1.5,1.46,1.48,-0.0067
|
||||
0020.HK,2024-12-18,1.49,1.53,1.49,1.51,0.0203
|
||||
0020.HK,2024-12-19,1.48,1.58,1.48,1.51,0
|
||||
0020.HK,2024-12-20,1.51,1.54,1.5,1.5,-0.0066
|
||||
0020.HK,2024-12-23,1.51,1.52,1.48,1.48,-0.0133
|
||||
0020.HK,2024-12-24,1.48,1.5,1.47,1.48,0
|
||||
0020.HK,2024-12-27,1.49,1.56,1.48,1.53,0.0338
|
||||
0020.HK,2024-12-30,1.54,1.55,1.51,1.51,-0.0131
|
||||
0020.HK,2024-12-31,1.51,1.52,1.49,1.49,-0.0132
|
||||
0020.HK,2025-01-02,1.49,1.49,1.41,1.41,-0.0537
|
||||
0020.HK,2025-01-03,1.42,1.43,1.33,1.33,-0.0567
|
||||
0020.HK,2025-01-06,1.34,1.38,1.32,1.33,0
|
||||
0020.HK,2025-01-07,1.31,1.34,1.26,1.33,0
|
||||
0020.HK,2025-01-08,1.33,1.34,1.28,1.31,-0.015
|
||||
0020.HK,2025-01-09,1.3,1.35,1.3,1.31,0
|
||||
0020.HK,2025-01-10,1.32,1.34,1.28,1.28,-0.0229
|
||||
0020.HK,2025-01-13,1.27,1.3,1.26,1.3,0.0156
|
||||
0020.HK,2025-01-14,1.3,1.37,1.3,1.34,0.0308
|
||||
0020.HK,2025-01-15,1.35,1.39,1.32,1.33,-0.0075
|
||||
0020.HK,2025-01-16,1.35,1.39,1.34,1.36,0.0226
|
||||
0020.HK,2025-01-17,1.36,1.4,1.36,1.37,0.0074
|
||||
0020.HK,2025-01-20,1.4,1.44,1.39,1.41,0.0292
|
||||
0020.HK,2025-01-21,1.42,1.45,1.4,1.44,0.0213
|
||||
0020.HK,2025-01-22,1.42,1.45,1.39,1.41,-0.0208
|
||||
0020.HK,2025-01-23,1.42,1.48,1.42,1.43,0.0142
|
||||
0020.HK,2025-01-24,1.44,1.54,1.43,1.52,0.0629
|
||||
0020.HK,2025-01-27,1.55,1.73,1.55,1.63,0.0724
|
||||
0020.HK,2025-01-28,1.64,1.66,1.59,1.61,-0.0123
|
||||
0020.HK,2025-02-03,1.61,1.7,1.55,1.69,0.0497
|
||||
0020.HK,2025-02-04,1.71,1.74,1.65,1.73,0.0237
|
||||
0020.HK,2025-02-05,1.71,1.71,1.59,1.65,-0.0462
|
||||
0020.HK,2025-02-06,1.64,1.72,1.63,1.72,0.0424
|
||||
0020.HK,2025-02-07,1.73,1.8,1.68,1.73,0.0058
|
||||
0020.HK,2025-02-10,1.76,1.85,1.75,1.77,0.0231
|
||||
0020.HK,2025-02-11,1.78,1.8,1.68,1.71,-0.0339
|
||||
0020.HK,2025-02-12,1.72,1.74,1.67,1.72,0.0058
|
||||
0020.HK,2025-02-13,1.74,1.82,1.67,1.69,-0.0174
|
||||
0020.HK,2025-02-14,1.72,1.83,1.71,1.82,0.0769
|
||||
0020.HK,2025-02-17,1.87,1.9,1.78,1.83,0.0055
|
||||
0020.HK,2025-02-18,1.84,1.92,1.77,1.82,-0.0055
|
||||
0020.HK,2025-02-19,1.84,1.84,1.77,1.83,0.0055
|
||||
0020.HK,2025-02-20,1.81,1.82,1.73,1.73,-0.0546
|
||||
0020.HK,2025-02-21,1.78,1.9,1.75,1.88,0.0867
|
||||
0020.HK,2025-02-24,1.94,1.98,1.89,1.92,0.0213
|
||||
0020.HK,2025-02-25,1.83,1.88,1.79,1.81,-0.0573
|
||||
0020.HK,2025-02-26,1.84,1.86,1.79,1.82,0.0055
|
||||
0020.HK,2025-02-27,1.83,1.87,1.74,1.78,-0.022
|
||||
0020.HK,2025-02-28,1.77,1.77,1.62,1.64,-0.0787
|
||||
0020.HK,2025-03-03,1.68,1.69,1.6,1.64,0
|
||||
0020.HK,2025-03-04,1.56,1.66,1.56,1.64,0
|
||||
0020.HK,2025-03-05,1.66,1.69,1.63,1.68,0.0244
|
||||
0020.HK,2025-03-06,1.72,1.81,1.72,1.78,0.0595
|
||||
0020.HK,2025-03-07,1.76,1.8,1.72,1.75,-0.0169
|
||||
0020.HK,2025-03-10,1.74,1.76,1.69,1.73,-0.0114
|
||||
0020.HK,2025-03-11,1.68,1.76,1.67,1.75,0.0116
|
||||
0020.HK,2025-03-12,1.76,1.78,1.69,1.71,-0.0229
|
||||
0020.HK,2025-03-13,1.72,1.74,1.66,1.69,-0.0117
|
||||
0020.HK,2025-03-14,1.71,1.73,1.67,1.71,0.0118
|
||||
0020.HK,2025-03-17,1.71,1.73,1.67,1.68,-0.0175
|
||||
0020.HK,2025-03-18,1.71,1.73,1.7,1.72,0.0238
|
||||
0020.HK,2025-03-19,1.72,1.8,1.68,1.74,0.0116
|
||||
0020.HK,2025-03-20,1.74,1.75,1.68,1.68,-0.0345
|
||||
0020.HK,2025-03-21,1.68,1.69,1.6,1.61,-0.0417
|
||||
0020.HK,2025-03-24,1.62,1.63,1.58,1.61,0
|
||||
0020.HK,2025-03-25,1.6,1.61,1.56,1.57,-0.0248
|
||||
0020.HK,2025-03-26,1.57,1.6,1.57,1.59,0.0127
|
||||
0020.HK,2025-03-27,1.55,1.55,1.45,1.49,-0.0629
|
||||
0020.HK,2025-03-28,1.51,1.53,1.47,1.5,0.0067
|
||||
0020.HK,2025-03-31,1.5,1.5,1.45,1.49,-0.0067
|
||||
0020.HK,2025-04-01,1.49,1.51,1.46,1.47,-0.0134
|
||||
0020.HK,2025-04-02,1.48,1.54,1.47,1.53,0.0408
|
||||
0020.HK,2025-04-03,1.5,1.6,1.49,1.55,0.0131
|
||||
0020.HK,2025-04-07,1.4,1.44,1.25,1.28,-0.1742
|
||||
0020.HK,2025-04-08,1.31,1.35,1.25,1.31,0.0234
|
||||
0020.HK,2025-04-09,1.27,1.38,1.24,1.36,0.0382
|
||||
0020.HK,2025-04-10,1.41,1.45,1.39,1.41,0.0368
|
||||
0020.HK,2025-04-11,1.41,1.44,1.39,1.42,0.0071
|
||||
0020.HK,2025-04-14,1.45,1.51,1.45,1.47,0.0352
|
||||
0020.HK,2025-04-15,1.48,1.49,1.43,1.45,-0.0136
|
||||
0020.HK,2025-04-16,1.44,1.44,1.39,1.4,-0.0345
|
||||
0020.HK,2025-04-17,1.4,1.42,1.39,1.4,0
|
||||
0020.HK,2025-04-22,1.41,1.43,1.39,1.43,0.0214
|
||||
0020.HK,2025-04-23,1.47,1.47,1.43,1.43,0
|
||||
0020.HK,2025-04-24,1.44,1.45,1.4,1.43,0
|
||||
0020.HK,2025-04-25,1.45,1.47,1.42,1.42,-0.007
|
||||
0020.HK,2025-04-28,1.43,1.45,1.4,1.45,0.0211
|
||||
0020.HK,2025-04-29,1.45,1.48,1.44,1.46,0.0069
|
||||
0020.HK,2025-04-30,1.49,1.53,1.47,1.5,0.0274
|
||||
0020.HK,2025-05-02,1.5,1.56,1.48,1.55,0.0333
|
||||
0020.HK,2025-05-06,1.55,1.56,1.51,1.53,-0.0129
|
||||
0020.HK,2025-05-07,1.56,1.58,1.52,1.52,-0.0065
|
||||
0020.HK,2025-05-08,1.52,1.55,1.51,1.52,0
|
||||
0020.HK,2025-05-09,1.52,1.52,1.48,1.49,-0.0197
|
||||
0020.HK,2025-05-12,1.52,1.58,1.5,1.56,0.047
|
||||
0020.HK,2025-05-13,1.57,1.58,1.5,1.51,-0.0321
|
||||
0020.HK,2025-05-14,1.53,1.53,1.48,1.49,-0.0132
|
||||
0020.HK,2025-05-15,1.49,1.51,1.45,1.46,-0.0201
|
||||
0020.HK,2025-05-16,1.44,1.46,1.44,1.45,-0.0068
|
||||
0020.HK,2025-05-19,1.44,1.45,1.42,1.43,-0.0138
|
||||
0020.HK,2025-05-20,1.44,1.45,1.41,1.42,-0.007
|
||||
0020.HK,2025-05-21,1.43,1.44,1.41,1.41,-0.007
|
||||
0020.HK,2025-05-22,1.41,1.42,1.4,1.4,-0.0071
|
||||
0020.HK,2025-05-23,1.4,1.42,1.39,1.4,0
|
||||
0020.HK,2025-05-26,1.4,1.42,1.37,1.4,0
|
||||
0020.HK,2025-05-27,1.4,1.41,1.38,1.39,-0.0071
|
||||
0020.HK,2025-05-28,1.39,1.4,1.38,1.38,-0.0072
|
||||
0020.HK,2025-05-29,1.38,1.43,1.37,1.42,0.029
|
||||
0020.HK,2025-05-30,1.41,1.42,1.39,1.4,-0.0141
|
||||
0020.HK,2025-06-02,1.39,1.39,1.33,1.38,-0.0143
|
||||
0020.HK,2025-06-03,1.38,1.41,1.36,1.37,-0.0072
|
||||
0020.HK,2025-06-04,1.37,1.39,1.36,1.36,-0.0073
|
||||
0020.HK,2025-06-05,1.37,1.42,1.37,1.4,0.0294
|
||||
0020.HK,2025-06-06,1.4,1.41,1.36,1.4,0
|
||||
0020.HK,2025-06-09,1.41,1.5,1.4,1.47,0.05
|
||||
0020.HK,2025-06-10,1.48,1.48,1.42,1.46,-0.0068
|
||||
0020.HK,2025-06-11,1.46,1.49,1.45,1.47,0.0068
|
||||
0020.HK,2025-06-12,1.46,1.48,1.43,1.47,0
|
||||
0020.HK,2025-06-13,1.46,1.47,1.4,1.4,-0.0476
|
|
|
@ -0,0 +1,158 @@
|
|||
# 量化分析工程模块
|
||||
|
||||
## 功能介绍
|
||||
|
||||
这个模块主要用于A股量化分析,包括:
|
||||
|
||||
1. **财务数据采集** - 自动从东方财富网采集上市公司财务报表数据
|
||||
2. **数据存储** - 将数据结构化存储到MongoDB数据库
|
||||
3. **量化分析** - 基于财务数据进行量化分析和策略开发
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
quantitative_analysis/
|
||||
├── __init__.py # 模块初始化文件
|
||||
├── financial_data_collector.py # 财务数据采集器
|
||||
├── README.md # 使用说明
|
||||
└── examples/ # 使用示例
|
||||
```
|
||||
|
||||
## 财务数据采集器
|
||||
|
||||
### 功能特点
|
||||
|
||||
- **全面的财务数据**:采集资产负债表、利润表、现金流量表、杜邦分析等
|
||||
- **批量处理**:支持单只股票和批量股票数据采集
|
||||
- **数据完整性**:2019-2024年完整的季度和年度财务数据
|
||||
- **智能存储**:自动去重,使用股票代码+报告日期作为唯一标识
|
||||
- **错误处理**:完善的异常处理和日志记录
|
||||
|
||||
### 使用方法
|
||||
|
||||
#### 1. 单只股票采集
|
||||
|
||||
```python
|
||||
from src.quantitative_analysis.financial_data_collector import FinancialDataCollector
|
||||
|
||||
# 创建采集器实例
|
||||
collector = FinancialDataCollector()
|
||||
|
||||
# 采集单只股票财务数据
|
||||
success = collector.collect_financial_data('300750.SZ') # 宁德时代
|
||||
|
||||
if success:
|
||||
print("财务数据采集成功")
|
||||
else:
|
||||
print("财务数据采集失败")
|
||||
|
||||
# 关闭连接
|
||||
collector.close_connection()
|
||||
```
|
||||
|
||||
#### 2. 批量股票采集
|
||||
|
||||
```python
|
||||
from src.quantitative_analysis.financial_data_collector import FinancialDataCollector
|
||||
|
||||
# 创建采集器实例
|
||||
collector = FinancialDataCollector()
|
||||
|
||||
# 股票代码列表
|
||||
stock_list = [
|
||||
'300750.SZ', # 宁德时代
|
||||
'000858.SZ', # 五粮液
|
||||
'002415.SZ', # 海康威视
|
||||
'000001.SZ', # 平安银行
|
||||
'600519.SH' # 贵州茅台
|
||||
]
|
||||
|
||||
# 批量采集
|
||||
results = collector.batch_collect_financial_data(stock_list)
|
||||
print(f"采集结果: {results}")
|
||||
|
||||
# 关闭连接
|
||||
collector.close_connection()
|
||||
```
|
||||
|
||||
### 数据结构
|
||||
|
||||
采集的数据会以以下结构存储到MongoDB:
|
||||
|
||||
```json
|
||||
{
|
||||
"stock_code": "300750.SZ",
|
||||
"report_date": "2024-06-30",
|
||||
"collect_time": "2024-07-15T10:30:00",
|
||||
"dupont_analysis": {
|
||||
"roe": 15.2,
|
||||
"dupont_assetstoequity": 1.8,
|
||||
"assetsturn": 0.9,
|
||||
"dupont_np": 8.5,
|
||||
"profittogr": 12.3
|
||||
},
|
||||
"balance_sheet": {
|
||||
"property": {
|
||||
"monetary_cap": 1000000000,
|
||||
"tot_assets": 5000000000,
|
||||
...
|
||||
},
|
||||
"liabilities": {
|
||||
"tot_liab": 2000000000,
|
||||
...
|
||||
},
|
||||
"owner_equity": {
|
||||
"tot_equity": 3000000000,
|
||||
...
|
||||
}
|
||||
},
|
||||
"profit_statement": {
|
||||
"tot_oper_rev": 800000000,
|
||||
"net_profit_is": 100000000,
|
||||
...
|
||||
},
|
||||
"cash_flow_statement": {
|
||||
"net_cash_flows_oper_act": 150000000,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 配置说明
|
||||
|
||||
- 使用 `src/valuation_analysis/config.py` 中的 `MONGO_CONFIG2` 配置
|
||||
- MongoDB服务器:192.168.20.110:27017
|
||||
- 数据库:judge
|
||||
- 集合:wind_financial_analysis
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. **请求频率控制**:代码中已添加请求间隔,避免被东方财富网限制
|
||||
2. **数据完整性**:会检查四类财务数据是否都获取成功
|
||||
3. **错误处理**:单个股票失败不会影响其他股票的采集
|
||||
4. **日志记录**:详细的日志记录,便于排查问题
|
||||
|
||||
### 运行测试
|
||||
|
||||
直接运行财务数据采集器进行测试:
|
||||
|
||||
```bash
|
||||
cd src/quantitative_analysis
|
||||
python financial_data_collector.py
|
||||
```
|
||||
|
||||
这将测试采集宁德时代(300750.SZ)的财务数据。
|
||||
|
||||
## 扩展开发
|
||||
|
||||
后续可以在此模块基础上开发:
|
||||
|
||||
1. **财务指标计算** - 基于原始财务数据计算各类财务指标
|
||||
2. **估值模型** - DCF、PE、PB等估值模型
|
||||
3. **量化策略** - 基于财务数据的选股策略
|
||||
4. **风险分析** - 财务风险评估模型
|
||||
5. **回测框架** - 策略回测和绩效分析
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请联系开发团队。
|
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
量化分析工程模块
|
||||
|
||||
主要功能:
|
||||
1. 财务数据采集
|
||||
2. 数据处理和分析
|
||||
3. 量化策略开发
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
|
@ -0,0 +1,183 @@
|
|||
import requests
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import os
|
||||
import redis
|
||||
import json
|
||||
|
||||
# 添加项目根目录到路径,便于导入scripts.config
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(project_root)
|
||||
|
||||
# 读取雪球headers和Redis配置
|
||||
try:
|
||||
from src.scripts.config import XUEQIU_HEADERS
|
||||
from src.valuation_analysis.config import REDIS_CONFIG
|
||||
except ImportError:
|
||||
XUEQIU_HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0',
|
||||
'Cookie': '', # 需要填写雪球cookie
|
||||
}
|
||||
REDIS_CONFIG = {
|
||||
'host': 'localhost',
|
||||
'port': 6379,
|
||||
'db': 0,
|
||||
'password': None
|
||||
}
|
||||
|
||||
REDIS_KEY = 'xq_stock_changes_latest' # 存放行情的主键
|
||||
|
||||
|
||||
def get_redis_conn():
|
||||
"""获取Redis连接"""
|
||||
pool = redis.ConnectionPool(
|
||||
host=REDIS_CONFIG['host'],
|
||||
port=REDIS_CONFIG['port'],
|
||||
db=REDIS_CONFIG.get('db', 0),
|
||||
password=REDIS_CONFIG.get('password', None),
|
||||
decode_responses=True
|
||||
)
|
||||
return redis.Redis(connection_pool=pool)
|
||||
|
||||
|
||||
def fetch_and_store_stock_data(page_size=90):
|
||||
"""
|
||||
批量采集雪球A股(上证、深证、科创板)股票的最新行情数据,并保存到Redis。
|
||||
:param page_size: 每页采集数量
|
||||
"""
|
||||
base_url = 'https://stock.xueqiu.com/v5/stock/screener/quote/list.json'
|
||||
types = ['sha', 'sza', 'kcb'] # 上证、深证、科创板
|
||||
headers = XUEQIU_HEADERS
|
||||
|
||||
all_data = []
|
||||
|
||||
for stock_type in types:
|
||||
params = {
|
||||
'page': 1,
|
||||
'size': page_size,
|
||||
'order': 'desc',
|
||||
'order_by': 'percent',
|
||||
'market': 'CN',
|
||||
'type': stock_type
|
||||
}
|
||||
|
||||
# 初次请求以获取总页数
|
||||
response = requests.get(base_url, headers=headers, params=params)
|
||||
if response.status_code != 200:
|
||||
print(f"请求 {stock_type} 数据失败,状态码:{response.status_code}")
|
||||
continue
|
||||
|
||||
data = response.json()
|
||||
total_count = data['data']['count']
|
||||
total_pages = (total_count // page_size) + 1
|
||||
|
||||
for page in range(1, total_pages + 1):
|
||||
params['page'] = page
|
||||
response = requests.get(base_url, headers=headers, params=params)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
all_data.extend(data['data']['list'])
|
||||
else:
|
||||
print(f"请求 {stock_type} 数据第 {page} 页失败,状态码:{response.status_code}")
|
||||
# 转换为 DataFrame
|
||||
df = pd.DataFrame(all_data)
|
||||
|
||||
if not df.empty:
|
||||
df['fetch_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
# 存入Redis,使用hash结构,key为symbol,value为json字符串
|
||||
r = get_redis_conn()
|
||||
pipe = r.pipeline()
|
||||
# 先清空旧数据
|
||||
r.delete(REDIS_KEY)
|
||||
for _, row in df.iterrows():
|
||||
symbol = row.get('symbol')
|
||||
if not symbol:
|
||||
continue
|
||||
# 只保留必要字段,也可直接存row.to_dict()
|
||||
value = row.to_dict()
|
||||
pipe.hset(REDIS_KEY, symbol, json.dumps(value, ensure_ascii=False))
|
||||
pipe.execute()
|
||||
print(f"成功将数据写入Redis哈希 {REDIS_KEY},共{len(df)}条记录。")
|
||||
else:
|
||||
print("未获取到任何数据。")
|
||||
|
||||
|
||||
def format_stock_code(stock_code):
|
||||
"""
|
||||
统一股票代码格式,支持600001.SH、SH600001、000001.SZ、SZ000001等
|
||||
返回雪球格式(如SH600001、SZ000001)和Redis存储格式(如SZ000978)
|
||||
"""
|
||||
stock_code = stock_code.upper()
|
||||
if '.' in stock_code:
|
||||
code, market = stock_code.split('.')
|
||||
if market == 'SH':
|
||||
return f'SH{code}', f'{market}{code}'
|
||||
elif market == 'SZ':
|
||||
return f'SZ{code}', f'{market}{code}'
|
||||
elif market == 'BJ':
|
||||
return f'BJ{code}', f'{market}{code}'
|
||||
else:
|
||||
return stock_code, stock_code
|
||||
elif stock_code.startswith(('SH', 'SZ', 'BJ')):
|
||||
return stock_code, stock_code
|
||||
else:
|
||||
# 默认返回原始
|
||||
return stock_code, stock_code
|
||||
|
||||
|
||||
def get_stock_realtime_info_from_redis(stock_code):
|
||||
"""
|
||||
根据股票代码从Redis查询实时行情,并封装为指定结构。
|
||||
:param stock_code: 支持600001.SH、SH600001、000001.SZ、SZ000001等
|
||||
:return: dict or None
|
||||
"""
|
||||
_, redis_code = format_stock_code(stock_code)
|
||||
r = get_redis_conn()
|
||||
value = r.hget(REDIS_KEY, redis_code)
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(value)
|
||||
except Exception:
|
||||
return None
|
||||
# 封装为指定结构
|
||||
result = {
|
||||
"code": None,
|
||||
"crawlDate": None,
|
||||
"marketValue": None,
|
||||
"maxPrice": None,
|
||||
"minPrice": None,
|
||||
"nowPrice": None,
|
||||
"pbRate": None,
|
||||
"rangeRiseAndFall": None,
|
||||
"shortName": None,
|
||||
"todayStartPrice": None,
|
||||
"ttm": None,
|
||||
"turnoverRate": None,
|
||||
"yesterdayEndPrice": None
|
||||
}
|
||||
# 赋值映射
|
||||
result["code"] = data.get("symbol")
|
||||
result["crawlDate"] = data.get("fetch_time")
|
||||
result["marketValue"] = data.get("market_capital")
|
||||
result["maxPrice"] = data.get("high") if "high" in data else data.get("high52w")
|
||||
result["minPrice"] = data.get("low") if "low" in data else data.get("low52w")
|
||||
result["nowPrice"] = data.get("current")
|
||||
result["pbRate"] = data.get("pb")
|
||||
result["rangeRiseAndFall"] = data.get("percent")
|
||||
result["shortName"] = data.get("name")
|
||||
result["todayStartPrice"] = data.get("open")
|
||||
result["ttm"] = data.get("pe_ttm")
|
||||
result["turnoverRate"] = data.get("turnover_rate")
|
||||
result["yesterdayEndPrice"] = data.get("last_close") if "last_close" in data else data.get("pre_close")
|
||||
# 兼容部分字段缺失
|
||||
if result["maxPrice"] is None and "high" in data:
|
||||
result["maxPrice"] = data["high"]
|
||||
if result["minPrice"] is None and "low" in data:
|
||||
result["minPrice"] = data["low"]
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
fetch_and_store_stock_data()
|
|
@ -0,0 +1,220 @@
|
|||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
|
||||
# 新增:设置matplotlib支持中文和负号
|
||||
import matplotlib
|
||||
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
|
||||
matplotlib.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
# 新增:屏蔽UserWarning
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=UserWarning)
|
||||
|
||||
# 回测参数
|
||||
INIT_CASH = 4000000
|
||||
FEE_RATE = 0.003 # 千分之3
|
||||
GRID_NUM = 15 # 网格数量
|
||||
PER_GRID_CASH = INIT_CASH / GRID_NUM # 每格买入金额
|
||||
|
||||
def generate_composite_grids(price_min, price_max):
|
||||
"""生成等差网格"""
|
||||
step = (price_max - price_min) / GRID_NUM
|
||||
return np.arange(price_min, price_max, step)
|
||||
|
||||
def should_trade(macd, signal):
|
||||
"""MACD趋势过滤
|
||||
金叉时做多,死叉时暂停
|
||||
"""
|
||||
return macd > signal
|
||||
|
||||
def composite_grid_backtest(prices):
|
||||
cash = INIT_CASH
|
||||
position = 0
|
||||
cost = 0
|
||||
history = [] # 记录每日资金、持仓
|
||||
trade_log = [] # 买卖点
|
||||
last_grid = None
|
||||
|
||||
# 计算MACD
|
||||
exp1 = prices['close'].ewm(span=12, adjust=False).mean()
|
||||
exp2 = prices['close'].ewm(span=26, adjust=False).mean()
|
||||
prices['MACD'] = exp1 - exp2
|
||||
prices['Signal'] = prices['MACD'].ewm(span=9, adjust=False).mean()
|
||||
|
||||
# 生成固定网格
|
||||
price_min = prices['close'].min()
|
||||
price_max = prices['close'].max()
|
||||
grids = generate_composite_grids(price_min, price_max)
|
||||
|
||||
def get_grid_index(price):
|
||||
"""获取价格所在的网格索引"""
|
||||
grid_idx = np.searchsorted(grids, price, side='right') - 1
|
||||
if grid_idx < 0:
|
||||
grid_idx = 0
|
||||
if grid_idx >= len(grids)-1:
|
||||
grid_idx = len(grids)-2
|
||||
return grid_idx
|
||||
|
||||
def execute_trade(price, trade_type, grid_idx, last_grid_idx, date, allow_trade):
|
||||
"""执行交易"""
|
||||
nonlocal cash, position, cost
|
||||
if not allow_trade:
|
||||
return False
|
||||
if trade_type == 'buy' and cash >= PER_GRID_CASH:
|
||||
buy_price = price * (1 + FEE_RATE)
|
||||
buy_amount = PER_GRID_CASH / buy_price
|
||||
cash -= buy_amount * buy_price
|
||||
position += buy_amount
|
||||
cost += buy_amount * buy_price
|
||||
trade_log.append({'date': date, 'price': price, 'type': 'buy'})
|
||||
return True
|
||||
elif trade_type == 'sell' and position > 0:
|
||||
sell_price = price * (1 - FEE_RATE)
|
||||
sell_amount = PER_GRID_CASH / price
|
||||
if sell_amount > position:
|
||||
sell_amount = position
|
||||
cash += sell_amount * sell_price
|
||||
position -= sell_amount
|
||||
cost -= sell_amount * price
|
||||
trade_log.append({'date': date, 'price': price, 'type': 'sell'})
|
||||
return True
|
||||
return False
|
||||
|
||||
for i, row in prices.iterrows():
|
||||
date = row['date']
|
||||
open_price = row['open']
|
||||
close_price = row['close']
|
||||
|
||||
# 判断是否允许交易(MACD趋势过滤)
|
||||
allow_trade = should_trade(row['MACD'], row['Signal'])
|
||||
|
||||
# 获取开盘价和收盘价所在的网格位置
|
||||
open_grid_idx = get_grid_index(open_price)
|
||||
close_grid_idx = get_grid_index(close_price)
|
||||
|
||||
# 开盘价交易处理
|
||||
if last_grid is not None:
|
||||
# 开盘价买入处理
|
||||
if open_grid_idx < last_grid:
|
||||
for grid_idx in range(open_grid_idx, last_grid):
|
||||
if not execute_trade(open_price, 'buy', grid_idx, last_grid, date, allow_trade):
|
||||
break
|
||||
# 开盘价卖出处理
|
||||
elif open_grid_idx > last_grid:
|
||||
for grid_idx in range(last_grid + 1, open_grid_idx + 1):
|
||||
if not execute_trade(open_price, 'sell', grid_idx, last_grid, date, allow_trade):
|
||||
break
|
||||
|
||||
# 收盘价交易处理
|
||||
if last_grid is not None:
|
||||
# 收盘价买入处理
|
||||
if close_grid_idx < open_grid_idx:
|
||||
for grid_idx in range(close_grid_idx, open_grid_idx):
|
||||
if not execute_trade(close_price, 'buy', grid_idx, open_grid_idx, date, allow_trade):
|
||||
break
|
||||
# 收盘价卖出处理
|
||||
elif close_grid_idx > open_grid_idx:
|
||||
for grid_idx in range(open_grid_idx + 1, close_grid_idx + 1):
|
||||
if not execute_trade(close_price, 'sell', grid_idx, open_grid_idx, date, allow_trade):
|
||||
break
|
||||
|
||||
# 更新last_grid为收盘价所在网格
|
||||
last_grid = close_grid_idx
|
||||
|
||||
# 记录每日净值
|
||||
total = cash + position * close_price
|
||||
history.append({
|
||||
'date': date,
|
||||
'net': total,
|
||||
'cash': cash,
|
||||
'position': position,
|
||||
'price': close_price,
|
||||
'macd': row['MACD'],
|
||||
'signal': row['Signal'],
|
||||
'open_grid': open_grid_idx,
|
||||
'close_grid': close_grid_idx
|
||||
})
|
||||
|
||||
# 统计
|
||||
df_hist = pd.DataFrame(history)
|
||||
max_drawdown = ((df_hist['net'].cummax() - df_hist['net']) / df_hist['net'].cummax()).max()
|
||||
total_return = (df_hist['net'].iloc[-1] - INIT_CASH) / INIT_CASH
|
||||
|
||||
# 胜率统计
|
||||
win, lose = 0, 0
|
||||
for i in range(1, len(trade_log)):
|
||||
if trade_log[i-1]['type']=='buy' and trade_log[i]['type']=='sell':
|
||||
if trade_log[i]['price'] > trade_log[i-1]['price']:
|
||||
win += 1
|
||||
else:
|
||||
lose += 1
|
||||
win_rate = win / (win+lose) if (win+lose)>0 else 0
|
||||
|
||||
return df_hist, trade_log, total_return, max_drawdown, len([t for t in trade_log if t['type']=='buy']), len([t for t in trade_log if t['type']=='sell']), win_rate
|
||||
|
||||
def plot_results(df_hist, trade_log, grids=None):
|
||||
"""绘制回测结果"""
|
||||
fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(14,10), height_ratios=[3, 1])
|
||||
ax2 = ax1.twinx()
|
||||
|
||||
# 绘制价格和净值
|
||||
ax1.plot(df_hist['date'], df_hist['price'], label='收盘价', color='gray', alpha=0.7)
|
||||
relative_net = df_hist['net'] / INIT_CASH
|
||||
ax2.plot(df_hist['date'], relative_net, label='净值', color='blue')
|
||||
|
||||
# 绘制交易点
|
||||
for t in trade_log:
|
||||
if t['type']=='buy':
|
||||
ax1.scatter(t['date'], t['price'], marker='^', color='red', label='买入' if '买入' not in ax1.get_legend_handles_labels()[1] else "")
|
||||
else:
|
||||
ax1.scatter(t['date'], t['price'], marker='v', color='green', label='卖出' if '卖出' not in ax1.get_legend_handles_labels()[1] else "")
|
||||
|
||||
# 绘制网格线
|
||||
if grids is not None:
|
||||
for g in grids:
|
||||
ax1.axhline(g, color='orange', linestyle='--', alpha=0.2)
|
||||
|
||||
# 绘制MACD
|
||||
ax3.plot(df_hist['date'], df_hist['macd'], label='MACD', color='blue')
|
||||
ax3.plot(df_hist['date'], df_hist['signal'], label='Signal', color='red')
|
||||
ax3.fill_between(df_hist['date'], df_hist['macd'] - df_hist['signal'],
|
||||
0, where=(df_hist['macd'] >= df_hist['signal']),
|
||||
color='green', alpha=0.3, label='做多区域')
|
||||
ax3.grid(True, alpha=0.3)
|
||||
ax3.legend(loc='upper left')
|
||||
|
||||
plt.title('复合策略网格回测')
|
||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||
ax1.legend(lines1 + lines2, labels1 + labels2)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 读取数据
|
||||
DATA_PATH = os.path.join(os.path.dirname(__file__), '0020.HK.csv')
|
||||
# 处理文件头乱码和尾部注释
|
||||
raw = pd.read_csv(DATA_PATH, header=None, skiprows=1, names=['code','date','open','high','low','close','pct'])
|
||||
raw = raw[raw['close'].apply(lambda x: str(x).replace('.','',1).isdigit())] # 去除空行和注释
|
||||
raw['date'] = pd.to_datetime(raw['date'])
|
||||
raw['close'] = raw['close'].astype(float)
|
||||
raw['open'] = raw['open'].astype(float)
|
||||
raw = raw.sort_values('date').reset_index(drop=True)
|
||||
|
||||
# 运行回测
|
||||
df_hist, trade_log, total_return, max_drawdown, buy_cnt, sell_cnt, win_rate = composite_grid_backtest(raw)
|
||||
|
||||
# 打印统计结果
|
||||
print('\n==== 复合策略网格回测结果 ===')
|
||||
print(f'总收益率: {total_return*100:.2f}%')
|
||||
print(f'最大回撤: {max_drawdown*100:.2f}%')
|
||||
print(f'买入次数: {buy_cnt},卖出次数: {sell_cnt}')
|
||||
print(f'胜率: {win_rate*100:.2f}%')
|
||||
|
||||
# 绘制结果
|
||||
price_min = raw['close'].min()
|
||||
price_max = raw['close'].max()
|
||||
grids = generate_composite_grids(price_min, price_max)
|
||||
plot_results(df_hist, trade_log, grids)
|
|
@ -0,0 +1,232 @@
|
|||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
|
||||
# 新增:设置matplotlib支持中文和负号
|
||||
import matplotlib
|
||||
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
|
||||
matplotlib.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
# 新增:屏蔽UserWarning
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=UserWarning)
|
||||
|
||||
# 回测参数
|
||||
INIT_CASH = 4000000
|
||||
FEE_RATE = 0.003 # 千分之3
|
||||
GRID_NUM = 15 # 网格数量
|
||||
PER_GRID_CASH = INIT_CASH / GRID_NUM # 每格买入金额
|
||||
|
||||
def generate_dynamic_grids(price, ma120):
|
||||
"""生成动态网格
|
||||
根据价格与MA120的偏离程度动态调整网格密度
|
||||
核心区(±8%):密集网格(1.5%-2%)
|
||||
缓冲区(±8%-20%):中等间距(3%-4%)
|
||||
极端区(超出±20%):疏松间距(5%-6%)
|
||||
"""
|
||||
deviation = (price - ma120) / ma120 # 计算价格相对MA120的偏离率
|
||||
|
||||
if abs(deviation) <= 0.08: # 核心区(±8%)
|
||||
# 在当前价格上下8%范围内生成密集网格
|
||||
grid_min = price * 0.92
|
||||
grid_max = price * 1.08
|
||||
return np.linspace(grid_min, grid_max, GRID_NUM) # 约1.5-2%间距
|
||||
|
||||
elif abs(deviation) <= 0.20: # 缓冲区(±8%-20%)
|
||||
# 扩大网格范围,增加间距
|
||||
if deviation > 0: # 价格在MA120上方
|
||||
grid_min = ma120 * 1.08 # 从MA120+8%开始
|
||||
grid_max = price * 1.15 # 到当前价格+15%
|
||||
else: # 价格在MA120下方
|
||||
grid_min = price * 0.85 # 从当前价格-15%开始
|
||||
grid_max = ma120 * 0.92 # 到MA120-8%
|
||||
return np.linspace(grid_min, grid_max, GRID_NUM) # 约3-4%间距
|
||||
|
||||
else: # 极端区(超出±20%)
|
||||
# 在极端区域使用更大的网格间距
|
||||
if deviation > 0: # 价格远高于MA120
|
||||
grid_min = ma120 * 1.20 # 从MA120+20%开始
|
||||
grid_max = price * 1.10 # 到当前价格+10%
|
||||
else: # 价格远低于MA120
|
||||
grid_min = price * 0.90 # 从当前价格-10%开始
|
||||
grid_max = ma120 * 0.80 # 到MA120-20%
|
||||
return np.linspace(grid_min, grid_max, GRID_NUM) # 约5-6%间距
|
||||
|
||||
def dynamic_grid_backtest(prices):
|
||||
cash = INIT_CASH
|
||||
position = 0
|
||||
cost = 0
|
||||
history = [] # 记录每日资金、持仓
|
||||
trade_log = [] # 买卖点
|
||||
last_grid = None
|
||||
|
||||
# 计算120日移动平均作为动态参考价
|
||||
prices['MA120'] = prices['close'].rolling(window=120).mean()
|
||||
|
||||
def get_grid_index(price, current_grids):
|
||||
"""获取价格所在的网格索引"""
|
||||
if len(current_grids) == 0:
|
||||
return -1
|
||||
grid_idx = np.searchsorted(current_grids, price, side='right') - 1
|
||||
if grid_idx < 0:
|
||||
grid_idx = 0
|
||||
if grid_idx >= len(current_grids)-1:
|
||||
grid_idx = len(current_grids)-2
|
||||
return grid_idx
|
||||
|
||||
def execute_trade(price, trade_type, grid_idx, last_grid_idx, date):
|
||||
"""执行交易"""
|
||||
nonlocal cash, position, cost
|
||||
if trade_type == 'buy' and cash >= PER_GRID_CASH:
|
||||
buy_price = price * (1 + FEE_RATE)
|
||||
buy_amount = PER_GRID_CASH / buy_price
|
||||
cash -= buy_amount * buy_price
|
||||
position += buy_amount
|
||||
cost += buy_amount * buy_price
|
||||
trade_log.append({'date': date, 'price': price, 'type': 'buy'})
|
||||
return True
|
||||
elif trade_type == 'sell' and position > 0:
|
||||
sell_price = price * (1 - FEE_RATE)
|
||||
sell_amount = PER_GRID_CASH / price
|
||||
if sell_amount > position:
|
||||
sell_amount = position
|
||||
cash += sell_amount * sell_price
|
||||
position -= sell_amount
|
||||
cost -= sell_amount * price
|
||||
trade_log.append({'date': date, 'price': price, 'type': 'sell'})
|
||||
return True
|
||||
return False
|
||||
|
||||
for i, row in prices.iterrows():
|
||||
if pd.isna(row['MA120']): # 跳过MA120未形成的时期
|
||||
continue
|
||||
|
||||
date = row['date']
|
||||
open_price = row['open']
|
||||
close_price = row['close']
|
||||
ma120 = row['MA120']
|
||||
|
||||
# 生成当前的动态网格
|
||||
open_grids = generate_dynamic_grids(open_price, ma120)
|
||||
close_grids = generate_dynamic_grids(close_price, ma120)
|
||||
|
||||
# 获取开盘价和收盘价所在的网格位置
|
||||
open_grid_idx = get_grid_index(open_price, open_grids)
|
||||
close_grid_idx = get_grid_index(close_price, close_grids)
|
||||
|
||||
# 开盘价交易处理
|
||||
if last_grid is not None and open_grid_idx >= 0:
|
||||
# 开盘价买入处理
|
||||
if open_grid_idx < last_grid:
|
||||
for grid_idx in range(open_grid_idx, last_grid):
|
||||
if not execute_trade(open_price, 'buy', grid_idx, last_grid, date):
|
||||
break
|
||||
# 开盘价卖出处理
|
||||
elif open_grid_idx > last_grid:
|
||||
for grid_idx in range(last_grid + 1, open_grid_idx + 1):
|
||||
if not execute_trade(open_price, 'sell', grid_idx, last_grid, date):
|
||||
break
|
||||
|
||||
# 收盘价交易处理
|
||||
if last_grid is not None and close_grid_idx >= 0:
|
||||
# 收盘价买入处理
|
||||
if close_grid_idx < open_grid_idx:
|
||||
for grid_idx in range(close_grid_idx, open_grid_idx):
|
||||
if not execute_trade(close_price, 'buy', grid_idx, open_grid_idx, date):
|
||||
break
|
||||
# 收盘价卖出处理
|
||||
elif close_grid_idx > open_grid_idx:
|
||||
for grid_idx in range(open_grid_idx + 1, close_grid_idx + 1):
|
||||
if not execute_trade(close_price, 'sell', grid_idx, open_grid_idx, date):
|
||||
break
|
||||
|
||||
# 更新last_grid为收盘价所在网格
|
||||
last_grid = close_grid_idx
|
||||
|
||||
# 记录每日净值
|
||||
total = cash + position * close_price
|
||||
history.append({
|
||||
'date': date,
|
||||
'net': total,
|
||||
'cash': cash,
|
||||
'position': position,
|
||||
'price': close_price,
|
||||
'ma120': ma120,
|
||||
'open_grid': open_grid_idx,
|
||||
'close_grid': close_grid_idx
|
||||
})
|
||||
|
||||
# 统计
|
||||
df_hist = pd.DataFrame(history)
|
||||
max_drawdown = ((df_hist['net'].cummax() - df_hist['net']) / df_hist['net'].cummax()).max()
|
||||
total_return = (df_hist['net'].iloc[-1] - INIT_CASH) / INIT_CASH
|
||||
|
||||
# 胜率统计
|
||||
win, lose = 0, 0
|
||||
for i in range(1, len(trade_log)):
|
||||
if trade_log[i-1]['type']=='buy' and trade_log[i]['type']=='sell':
|
||||
if trade_log[i]['price'] > trade_log[i-1]['price']:
|
||||
win += 1
|
||||
else:
|
||||
lose += 1
|
||||
win_rate = win / (win+lose) if (win+lose)>0 else 0
|
||||
|
||||
return df_hist, trade_log, total_return, max_drawdown, len([t for t in trade_log if t['type']=='buy']), len([t for t in trade_log if t['type']=='sell']), win_rate
|
||||
|
||||
def plot_results(df_hist, trade_log, grids=None):
|
||||
"""绘制回测结果"""
|
||||
fig, ax1 = plt.subplots(figsize=(14,6))
|
||||
ax2 = ax1.twinx()
|
||||
|
||||
# 绘制价格、MA120和净值
|
||||
ax1.plot(df_hist['date'], df_hist['price'], label='收盘价', color='gray', alpha=0.7)
|
||||
ax1.plot(df_hist['date'], df_hist['ma120'], label='MA120', color='purple', alpha=0.5)
|
||||
relative_net = df_hist['net'] / INIT_CASH
|
||||
ax2.plot(df_hist['date'], relative_net, label='净值', color='blue')
|
||||
|
||||
# 绘制交易点
|
||||
for t in trade_log:
|
||||
if t['type']=='buy':
|
||||
ax1.scatter(t['date'], t['price'], marker='^', color='red', label='买入' if '买入' not in ax1.get_legend_handles_labels()[1] else "")
|
||||
else:
|
||||
ax1.scatter(t['date'], t['price'], marker='v', color='green', label='卖出' if '卖出' not in ax1.get_legend_handles_labels()[1] else "")
|
||||
|
||||
# 绘制最后一个时间点的网格线
|
||||
if grids is not None and len(grids) > 0:
|
||||
for g in grids:
|
||||
ax1.axhline(g, color='orange', linestyle='--', alpha=0.2)
|
||||
|
||||
plt.title('动态网格回测')
|
||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||
ax1.legend(lines1 + lines2, labels1 + labels2)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 读取数据
|
||||
DATA_PATH = os.path.join(os.path.dirname(__file__), '0020.HK.csv')
|
||||
# 处理文件头乱码和尾部注释
|
||||
raw = pd.read_csv(DATA_PATH, header=None, skiprows=1, names=['code','date','open','high','low','close','pct'])
|
||||
raw = raw[raw['close'].apply(lambda x: str(x).replace('.','',1).isdigit())] # 去除空行和注释
|
||||
raw['date'] = pd.to_datetime(raw['date'])
|
||||
raw['close'] = raw['close'].astype(float)
|
||||
raw['open'] = raw['open'].astype(float)
|
||||
raw = raw.sort_values('date').reset_index(drop=True)
|
||||
|
||||
# 运行回测
|
||||
df_hist, trade_log, total_return, max_drawdown, buy_cnt, sell_cnt, win_rate = dynamic_grid_backtest(raw)
|
||||
|
||||
# 打印统计结果
|
||||
print('\n==== 动态网格回测结果 ===')
|
||||
print(f'总收益率: {total_return*100:.2f}%')
|
||||
print(f'最大回撤: {max_drawdown*100:.2f}%')
|
||||
print(f'买入次数: {buy_cnt},卖出次数: {sell_cnt}')
|
||||
print(f'胜率: {win_rate*100:.2f}%')
|
||||
|
||||
# 绘制结果
|
||||
last_price = df_hist['price'].iloc[-1]
|
||||
last_ma120 = df_hist['ma120'].iloc[-1]
|
||||
last_grids = generate_dynamic_grids(last_price, last_ma120)
|
||||
plot_results(df_hist, trade_log, last_grids)
|
|
@ -0,0 +1,603 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
东方财富财务数据采集器 V2.0
|
||||
适配2025年新版接口
|
||||
|
||||
从东方财富网自动采集A股上市公司的财务报表数据,包括:
|
||||
1. 资产负债表
|
||||
2. 利润表
|
||||
3. 现金流量表
|
||||
|
||||
数据存储到MongoDB数据库中,保留所有原始字段
|
||||
"""
|
||||
|
||||
import requests
|
||||
import pymongo
|
||||
import logging
|
||||
import datetime
|
||||
import time
|
||||
import json
|
||||
import pandas as pd
|
||||
from typing import List, Dict, Optional
|
||||
import sys
|
||||
import os
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
# 添加项目根目录到路径
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(project_root)
|
||||
|
||||
# 导入配置
|
||||
try:
|
||||
from valuation_analysis.config import MONGO_CONFIG2, DB_URL
|
||||
except ImportError:
|
||||
# 如果上面的导入失败,尝试直接导入
|
||||
import importlib.util
|
||||
config_path = os.path.join(project_root, 'valuation_analysis', 'config.py')
|
||||
spec = importlib.util.spec_from_file_location("config", config_path)
|
||||
config_module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(config_module)
|
||||
MONGO_CONFIG2 = config_module.MONGO_CONFIG2
|
||||
DB_URL = config_module.DB_URL
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('financial_data_collector_v2.log', encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 为StreamHandler也设置编码(如果可能)
|
||||
try:
|
||||
if hasattr(sys.stdout, 'reconfigure'):
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
if hasattr(sys.stderr, 'reconfigure'):
|
||||
sys.stderr.reconfigure(encoding='utf-8')
|
||||
except:
|
||||
pass # 如果设置失败就忽略
|
||||
|
||||
|
||||
class FinancialDataCollectorV2:
|
||||
"""财务数据采集器 V2.0 - 适配新版东方财富接口"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化"""
|
||||
self.mongo_client = None
|
||||
self.db = None
|
||||
# 使用新的集合名称存储新版本数据
|
||||
self.collection_name = 'eastmoney_financial_data_v2'
|
||||
self.collection = None
|
||||
|
||||
# 初始化MySQL连接
|
||||
self.mysql_engine = None
|
||||
|
||||
self.connect_mongodb()
|
||||
self.connect_mysql()
|
||||
|
||||
def connect_mongodb(self):
|
||||
"""连接MongoDB数据库"""
|
||||
try:
|
||||
# 使用参数形式连接MongoDB,而不是连接字符串
|
||||
self.mongo_client = pymongo.MongoClient(
|
||||
host=MONGO_CONFIG2['host'],
|
||||
port=MONGO_CONFIG2['port'],
|
||||
username=MONGO_CONFIG2['username'],
|
||||
password=MONGO_CONFIG2['password']
|
||||
)
|
||||
self.db = self.mongo_client[MONGO_CONFIG2['db']]
|
||||
self.collection = self.db[self.collection_name]
|
||||
|
||||
# 测试连接
|
||||
self.mongo_client.admin.command('ping')
|
||||
logger.info(f"MongoDB连接成功,使用集合: {self.collection_name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"MongoDB连接失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def connect_mysql(self):
|
||||
"""连接MySQL数据库"""
|
||||
try:
|
||||
self.mysql_engine = create_engine(
|
||||
DB_URL,
|
||||
pool_size=5,
|
||||
max_overflow=10,
|
||||
pool_recycle=3600
|
||||
)
|
||||
|
||||
# 测试连接
|
||||
with self.mysql_engine.connect() as conn:
|
||||
conn.execute(text("SELECT 1"))
|
||||
|
||||
logger.info("MySQL数据库连接成功")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"MySQL数据库连接失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def get_all_stock_codes(self) -> List[str]:
|
||||
"""
|
||||
从数据库中获取所有股票代码
|
||||
|
||||
Returns:
|
||||
List[str]: 股票代码列表
|
||||
"""
|
||||
try:
|
||||
query = "SELECT DISTINCT gp_code_two FROM gp_code_all WHERE gp_code_two IS NOT NULL AND gp_code_two != ''"
|
||||
|
||||
with self.mysql_engine.connect() as conn:
|
||||
df = pd.read_sql(text(query), conn)
|
||||
|
||||
stock_codes = df['gp_code_two'].tolist()
|
||||
logger.info(f"从数据库获取到 {len(stock_codes)} 只股票")
|
||||
|
||||
return stock_codes
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取股票代码列表失败: {str(e)}")
|
||||
return []
|
||||
|
||||
def build_date_filter(self, stock_code: str, periods: int = 21) -> str:
|
||||
"""
|
||||
构建日期过滤条件 - 适配新版接口
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码,如'300750.SZ'
|
||||
periods: 获取多少个报告期,默认21个季度
|
||||
|
||||
Returns:
|
||||
str: 日期过滤字符串
|
||||
"""
|
||||
# 计算需要的日期范围,从当前日期往前推
|
||||
current_date = datetime.datetime.now()
|
||||
dates = []
|
||||
|
||||
# 生成最近21个季度的日期
|
||||
for i in range(periods):
|
||||
# 从当前季度往前推
|
||||
year = current_date.year
|
||||
month = current_date.month
|
||||
|
||||
# 计算季度
|
||||
if month <= 3:
|
||||
quarter_month = 3
|
||||
year_offset = i // 4
|
||||
quarter_offset = i % 4
|
||||
if quarter_offset > 0:
|
||||
year_offset += 1
|
||||
quarter_month = [12, 9, 6, 3][quarter_offset - 1]
|
||||
target_year = year - year_offset
|
||||
elif month <= 6:
|
||||
quarter_month = 6
|
||||
year_offset = i // 4
|
||||
quarter_offset = i % 4
|
||||
if quarter_offset >= 1:
|
||||
if quarter_offset == 1:
|
||||
quarter_month = 3
|
||||
elif quarter_offset == 2:
|
||||
quarter_month = 12
|
||||
target_year = year - 1
|
||||
elif quarter_offset == 3:
|
||||
quarter_month = 9
|
||||
target_year = year - 1
|
||||
else:
|
||||
target_year = year
|
||||
if quarter_offset >= 2:
|
||||
year_offset += 1
|
||||
target_year = year - year_offset
|
||||
elif month <= 9:
|
||||
quarter_month = 9
|
||||
year_offset = i // 4
|
||||
quarter_offset = i % 4
|
||||
if quarter_offset >= 1:
|
||||
if quarter_offset == 1:
|
||||
quarter_month = 6
|
||||
elif quarter_offset == 2:
|
||||
quarter_month = 3
|
||||
elif quarter_offset == 3:
|
||||
quarter_month = 12
|
||||
target_year = year - 1
|
||||
else:
|
||||
target_year = year
|
||||
if quarter_offset >= 3:
|
||||
year_offset += 1
|
||||
target_year = year - year_offset
|
||||
else:
|
||||
quarter_month = 12
|
||||
year_offset = i // 4
|
||||
quarter_offset = i % 4
|
||||
if quarter_offset >= 1:
|
||||
if quarter_offset == 1:
|
||||
quarter_month = 9
|
||||
elif quarter_offset == 2:
|
||||
quarter_month = 6
|
||||
elif quarter_offset == 3:
|
||||
quarter_month = 3
|
||||
else:
|
||||
target_year = year
|
||||
target_year = year - year_offset
|
||||
|
||||
# 简化逻辑:直接从2025年开始往前推21个季度
|
||||
base_year = 2025
|
||||
base_quarters = [
|
||||
(2025, 3), (2024, 12), (2024, 9), (2024, 6), (2024, 3),
|
||||
(2023, 12), (2023, 9), (2023, 6), (2023, 3),
|
||||
(2022, 12), (2022, 9), (2022, 6), (2022, 3),
|
||||
(2021, 12), (2021, 9), (2021, 6), (2021, 3),
|
||||
(2020, 12), (2020, 9), (2020, 6), (2020, 3)
|
||||
]
|
||||
|
||||
if i < len(base_quarters):
|
||||
year, month = base_quarters[i]
|
||||
if month == 3:
|
||||
date_str = f'{year}-03-31'
|
||||
elif month == 6:
|
||||
date_str = f'{year}-06-30'
|
||||
elif month == 9:
|
||||
date_str = f'{year}-09-30'
|
||||
else:
|
||||
date_str = f'{year}-12-31'
|
||||
dates.append(date_str)
|
||||
|
||||
# 构建过滤字符串
|
||||
date_filter = f'(SECUCODE%3D%22{stock_code}%22)(REPORT_DATE%20in%20('
|
||||
date_filter += '%2C'.join([f'%27{date}%27' for date in dates])
|
||||
date_filter += '))'
|
||||
|
||||
return date_filter
|
||||
|
||||
def fetch_profit_statement(self, stock_code: str, periods: int = 21) -> List[Dict]:
|
||||
"""获取利润表数据"""
|
||||
date_filter = self.build_date_filter(stock_code, periods)
|
||||
url = f'https://datacenter.eastmoney.com/securities/api/data/get?type=RPT_F10_FINANCE_GINCOME&sty=APP_F10_GINCOME&filter={date_filter}&p=1&ps={periods}&sr=-1&st=REPORT_DATE&source=HSF10&client=PC'
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if 'result' in data and 'data' in data['result']:
|
||||
logger.info(f"成功获取利润表数据,共 {len(data['result']['data'])} 个报告期")
|
||||
return data['result']['data']
|
||||
else:
|
||||
logger.warning("利润表数据格式异常")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取利润表失败: {str(e)}")
|
||||
return []
|
||||
|
||||
def fetch_balance_sheet(self, stock_code: str, periods: int = 21) -> List[Dict]:
|
||||
"""获取资产负债表数据"""
|
||||
date_filter = self.build_date_filter(stock_code, periods)
|
||||
url = f'https://datacenter.eastmoney.com/securities/api/data/get?type=RPT_F10_FINANCE_GBALANCE&sty=F10_FINANCE_GBALANCE&filter={date_filter}&p=1&ps={periods}&sr=-1&st=REPORT_DATE&source=HSF10&client=PC&v=012481899342117453'
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if 'result' in data and 'data' in data['result']:
|
||||
logger.info(f"成功获取资产负债表数据,共 {len(data['result']['data'])} 个报告期")
|
||||
return data['result']['data']
|
||||
else:
|
||||
logger.warning("资产负债表数据格式异常")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取资产负债表失败: {str(e)}")
|
||||
return []
|
||||
|
||||
def fetch_cash_flow_statement(self, stock_code: str, periods: int = 21) -> List[Dict]:
|
||||
"""获取现金流量表数据"""
|
||||
date_filter = self.build_date_filter(stock_code, periods)
|
||||
url = f'https://datacenter.eastmoney.com/securities/api/data/get?type=RPT_F10_FINANCE_GCASHFLOW&sty=APP_F10_GCASHFLOW&filter={date_filter}&p=1&ps={periods}&sr=-1&st=REPORT_DATE&source=HSF10&client=PC&v=04664977872701077'
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if 'result' in data and 'data' in data['result']:
|
||||
logger.info(f"成功获取现金流量表数据,共 {len(data['result']['data'])} 个报告期")
|
||||
return data['result']['data']
|
||||
else:
|
||||
logger.warning("现金流量表数据格式异常")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取现金流量表失败: {str(e)}")
|
||||
return []
|
||||
|
||||
def process_financial_data(self, stock_code: str, profit_data: List[Dict],
|
||||
balance_data: List[Dict], cash_data: List[Dict]) -> List[Dict]:
|
||||
"""
|
||||
处理财务数据,按报告期合并三张表的数据
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
profit_data: 利润表数据
|
||||
balance_data: 资产负债表数据
|
||||
cash_data: 现金流量表数据
|
||||
|
||||
Returns:
|
||||
List[Dict]: 处理后的财务数据列表
|
||||
"""
|
||||
financial_data_list = []
|
||||
|
||||
# 创建按报告日期索引的字典
|
||||
profit_dict = {item['REPORT_DATE']: item for item in profit_data}
|
||||
balance_dict = {item['REPORT_DATE']: item for item in balance_data}
|
||||
cash_dict = {item['REPORT_DATE']: item for item in cash_data}
|
||||
|
||||
# 获取所有报告日期
|
||||
all_dates = set()
|
||||
all_dates.update(profit_dict.keys())
|
||||
all_dates.update(balance_dict.keys())
|
||||
all_dates.update(cash_dict.keys())
|
||||
|
||||
# 按日期排序
|
||||
sorted_dates = sorted(all_dates, reverse=True)
|
||||
|
||||
for report_date in sorted_dates:
|
||||
try:
|
||||
# 构建完整的财务数据记录
|
||||
financial_record = {
|
||||
'stock_code': stock_code,
|
||||
'report_date': report_date[:10] if report_date else None, # 只取日期部分
|
||||
'collect_time': datetime.datetime.now(),
|
||||
'data_source': 'eastmoney_v2',
|
||||
'profit_statement': profit_dict.get(report_date, {}),
|
||||
'balance_sheet': balance_dict.get(report_date, {}),
|
||||
'cash_flow_statement': cash_dict.get(report_date, {})
|
||||
}
|
||||
|
||||
# 只有当至少有一张表有数据时才添加记录
|
||||
if (financial_record['profit_statement'] or
|
||||
financial_record['balance_sheet'] or
|
||||
financial_record['cash_flow_statement']):
|
||||
financial_data_list.append(financial_record)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理报告期 {report_date} 数据时出错: {str(e)}")
|
||||
continue
|
||||
|
||||
logger.info(f"成功处理 {len(financial_data_list)} 个报告期的数据")
|
||||
return financial_data_list
|
||||
|
||||
def save_to_mongodb(self, financial_data_list: List[Dict]) -> bool:
|
||||
"""
|
||||
保存数据到MongoDB,如果数据已存在则跳过,只新增不存在的数据
|
||||
|
||||
Args:
|
||||
financial_data_list: 财务数据列表
|
||||
|
||||
Returns:
|
||||
bool: 是否保存成功
|
||||
"""
|
||||
try:
|
||||
if not financial_data_list:
|
||||
logger.warning("没有数据需要保存")
|
||||
return False
|
||||
|
||||
# 统计插入和跳过的数量
|
||||
inserted_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for data in financial_data_list:
|
||||
# 使用股票代码和报告日期作为唯一标识
|
||||
filter_condition = {
|
||||
'stock_code': data['stock_code'],
|
||||
'report_date': data['report_date']
|
||||
}
|
||||
|
||||
# 检查数据是否已存在
|
||||
existing_record = self.collection.find_one(filter_condition)
|
||||
|
||||
if existing_record:
|
||||
# 数据已存在,跳过
|
||||
skipped_count += 1
|
||||
logger.debug(f"跳过已存在的数据: {data['stock_code']} - {data['report_date']}")
|
||||
else:
|
||||
# 数据不存在,插入新数据
|
||||
self.collection.insert_one(data)
|
||||
inserted_count += 1
|
||||
logger.debug(f"插入新数据: {data['stock_code']} - {data['report_date']}")
|
||||
|
||||
logger.info(f"数据保存完成 - 新增: {inserted_count} 条,跳过: {skipped_count} 条")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"保存数据到MongoDB失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def collect_financial_data(self, stock_code: str, periods: int = 21) -> bool:
|
||||
"""
|
||||
采集单只股票的财务数据
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码,如'300750.SZ'
|
||||
periods: 获取多少个报告期,默认21个季度
|
||||
|
||||
Returns:
|
||||
bool: 是否采集成功
|
||||
"""
|
||||
try:
|
||||
logger.info(f"开始采集股票 {stock_code} 的财务数据({periods}个报告期)")
|
||||
|
||||
# 获取三张财务报表数据
|
||||
profit_data = self.fetch_profit_statement(stock_code, periods)
|
||||
time.sleep(1) # 避免请求过于频繁
|
||||
|
||||
balance_data = self.fetch_balance_sheet(stock_code, periods)
|
||||
time.sleep(1)
|
||||
|
||||
cash_data = self.fetch_cash_flow_statement(stock_code, periods)
|
||||
time.sleep(1)
|
||||
|
||||
# 检查至少有一张表有数据
|
||||
if not any([profit_data, balance_data, cash_data]):
|
||||
logger.error(f"股票 {stock_code} 没有获取到任何财务数据")
|
||||
return False
|
||||
|
||||
# 处理财务数据
|
||||
financial_data_list = self.process_financial_data(
|
||||
stock_code, profit_data, balance_data, cash_data
|
||||
)
|
||||
|
||||
if not financial_data_list:
|
||||
logger.error(f"股票 {stock_code} 的财务数据处理失败")
|
||||
return False
|
||||
|
||||
# 保存到MongoDB
|
||||
success = self.save_to_mongodb(financial_data_list)
|
||||
|
||||
if success:
|
||||
logger.info(f"股票 {stock_code} 的财务数据采集完成")
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"采集股票 {stock_code} 的财务数据失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def batch_collect_financial_data(self, stock_codes: List[str], periods: int = 21) -> Dict:
|
||||
"""
|
||||
批量采集多只股票的财务数据
|
||||
|
||||
Args:
|
||||
stock_codes: 股票代码列表
|
||||
periods: 获取多少个报告期,默认21个季度
|
||||
|
||||
Returns:
|
||||
Dict: 采集结果统计
|
||||
"""
|
||||
results = {'success': 0, 'failed': 0, 'failed_stocks': []}
|
||||
total_stocks = len(stock_codes)
|
||||
|
||||
logger.info(f"开始批量采集 {total_stocks} 只股票的财务数据")
|
||||
|
||||
for index, stock_code in enumerate(stock_codes, 1):
|
||||
try:
|
||||
# 显示进度
|
||||
progress = (index / total_stocks) * 100
|
||||
logger.info(f"进度 [{index}/{total_stocks}] ({progress:.1f}%) - 正在处理: {stock_code}")
|
||||
|
||||
success = self.collect_financial_data(stock_code, periods)
|
||||
if success:
|
||||
results['success'] += 1
|
||||
logger.info(f"SUCCESS [{index}/{total_stocks}] {stock_code} 采集成功")
|
||||
else:
|
||||
results['failed'] += 1
|
||||
results['failed_stocks'].append(stock_code)
|
||||
logger.warning(f"FAILED [{index}/{total_stocks}] {stock_code} 采集失败")
|
||||
|
||||
# 每只股票之间暂停一下,避免请求过于频繁
|
||||
time.sleep(2)
|
||||
|
||||
# 每100只股票输出一次统计信息
|
||||
if index % 100 == 0:
|
||||
current_success_rate = (results['success'] / index) * 100
|
||||
logger.info(f"阶段性统计: 已处理 {index}/{total_stocks} 只股票,成功率: {current_success_rate:.2f}%")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理股票 {stock_code} 时出错: {str(e)}")
|
||||
results['failed'] += 1
|
||||
results['failed_stocks'].append(stock_code)
|
||||
|
||||
# 继续处理下一只股票,不中断整个流程
|
||||
continue
|
||||
|
||||
success_rate = (results['success'] / total_stocks) * 100
|
||||
logger.info(f"批量采集完成: 成功{results['success']}只,失败{results['failed']}只,成功率: {success_rate:.2f}%")
|
||||
|
||||
if results['failed_stocks']:
|
||||
logger.info(f"失败的股票数量: {len(results['failed_stocks'])}")
|
||||
# 记录前10个失败的股票到日志
|
||||
failed_sample = results['failed_stocks'][:10]
|
||||
logger.info(f"失败股票示例: {', '.join(failed_sample)}")
|
||||
|
||||
return results
|
||||
|
||||
def close_connection(self):
|
||||
"""关闭数据库连接"""
|
||||
if self.mongo_client:
|
||||
self.mongo_client.close()
|
||||
logger.info("MongoDB连接已关闭")
|
||||
|
||||
if self.mysql_engine:
|
||||
self.mysql_engine.dispose()
|
||||
logger.info("MySQL连接已关闭")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数 - 批量采集所有股票的财务数据"""
|
||||
collector = FinancialDataCollectorV2()
|
||||
|
||||
try:
|
||||
# 从数据库获取所有股票代码
|
||||
logger.info("正在从数据库获取股票列表...")
|
||||
stock_codes = collector.get_all_stock_codes()
|
||||
|
||||
if not stock_codes:
|
||||
logger.error("未获取到任何股票代码,程序退出")
|
||||
return
|
||||
|
||||
logger.info(f"从数据库获取到 {len(stock_codes)} 只股票")
|
||||
|
||||
# 可以选择采集所有股票或者部分股票进行测试
|
||||
# 如果要测试,可以取前几只股票
|
||||
# 测试模式:只采集前10只股票
|
||||
TEST_MODE = False # 设置为False将采集所有股票
|
||||
|
||||
if TEST_MODE:
|
||||
test_count = min(10, len(stock_codes)) # 最多取10只股票测试
|
||||
stock_codes = stock_codes[:test_count]
|
||||
logger.info(f"TEST MODE: 仅采集前 {test_count} 只股票")
|
||||
else:
|
||||
logger.info(f"PRODUCTION MODE: 将采集全部 {len(stock_codes)} 只股票")
|
||||
|
||||
logger.info(f"开始批量采集 {len(stock_codes)} 只股票的财务数据")
|
||||
|
||||
# 批量采集
|
||||
results = collector.batch_collect_financial_data(stock_codes, periods=21)
|
||||
|
||||
# 输出最终结果
|
||||
print(f"\n{'='*50}")
|
||||
print(f"批量采集完成统计")
|
||||
print(f"{'='*50}")
|
||||
print(f"SUCCESS 成功采集: {results['success']} 只股票")
|
||||
print(f"FAILED 采集失败: {results['failed']} 只股票")
|
||||
print(f"SUCCESS RATE 成功率: {(results['success'] / len(stock_codes) * 100):.2f}%")
|
||||
|
||||
if results['failed_stocks']:
|
||||
print(f"\n失败的股票列表:")
|
||||
for i, stock in enumerate(results['failed_stocks'][:10], 1): # 只显示前10个
|
||||
print(f" {i}. {stock}")
|
||||
if len(results['failed_stocks']) > 10:
|
||||
print(f" ... 还有 {len(results['failed_stocks']) - 10} 只股票")
|
||||
|
||||
logger.info("所有任务已完成")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("用户中断程序执行")
|
||||
print("\n警告: 程序被用户中断")
|
||||
except Exception as e:
|
||||
logger.error(f"采集过程中出现错误: {str(e)}")
|
||||
print(f"\n错误: 程序执行出错: {str(e)}")
|
||||
finally:
|
||||
collector.close_connection()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,200 @@
|
|||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
|
||||
# 新增:设置matplotlib支持中文和负号
|
||||
import matplotlib
|
||||
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
|
||||
matplotlib.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
# 新增:屏蔽UserWarning
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=UserWarning)
|
||||
|
||||
# 读取数据
|
||||
DATA_PATH = os.path.join(os.path.dirname(__file__), '0020.HK.csv')
|
||||
# 处理文件头乱码和尾部注释
|
||||
raw = pd.read_csv(DATA_PATH, header=None, skiprows=1, names=['code','date','open','high','low','close','pct'])
|
||||
raw = raw[raw['close'].apply(lambda x: str(x).replace('.','',1).isdigit())] # 去除空行和注释
|
||||
raw['date'] = pd.to_datetime(raw['date'])
|
||||
raw['close'] = raw['close'].astype(float)
|
||||
raw = raw.sort_values('date').reset_index(drop=True)
|
||||
|
||||
# 回测参数
|
||||
INIT_CASH = 4000000
|
||||
FEE_RATE = 0.003 # 千分之3
|
||||
GRID_TYPES = ['等差网格', '等比网格', 'ATR网格']
|
||||
GRID_NUM = 15 # 网格数量
|
||||
PER_GRID_CASH = INIT_CASH / GRID_NUM # 每格买入金额
|
||||
|
||||
# 自动确定网格上下限
|
||||
price_min = raw['close'].min()
|
||||
price_max = raw['close'].max()
|
||||
price_mean = raw['close'].mean()
|
||||
price_std = raw['close'].std()
|
||||
|
||||
# 网格参数生成
|
||||
# 1. 等差网格
|
||||
step_linear = (price_max - price_min) / GRID_NUM
|
||||
linear_grids = np.arange(price_min, price_max, step_linear)
|
||||
# 2. 等比网格
|
||||
step_ratio = (price_max / price_min) ** (1/GRID_NUM)
|
||||
ratio_grids = [price_min * (step_ratio ** i) for i in range(GRID_NUM+1)]
|
||||
# 3. ATR网格
|
||||
atr_window = 20
|
||||
raw['tr'] = np.maximum(raw['high']-raw['low'], np.abs(raw['high']-raw['close'].shift(1)), np.abs(raw['low']-raw['close'].shift(1)))
|
||||
raw['atr'] = raw['tr'].rolling(atr_window).mean()
|
||||
avg_atr = raw['atr'].mean()
|
||||
atr_step = avg_atr * 1.0 # 可调参数
|
||||
atr_grids = np.arange(price_min, price_max, atr_step)
|
||||
|
||||
# 网格集合
|
||||
grid_dict = {
|
||||
'等差网格': linear_grids,
|
||||
'等比网格': ratio_grids,
|
||||
'ATR网格': atr_grids
|
||||
}
|
||||
|
||||
# 回测主函数
|
||||
def grid_backtest(prices, grids, grid_type):
|
||||
cash = INIT_CASH
|
||||
position = 0
|
||||
cost = 0
|
||||
history = [] # 记录每日资金、持仓
|
||||
trade_log = [] # 买卖点
|
||||
last_grid = None
|
||||
|
||||
def get_grid_index(price):
|
||||
"""获取价格所在的网格索引"""
|
||||
grid_idx = np.searchsorted(grids, price, side='right') - 1
|
||||
if grid_idx < 0:
|
||||
grid_idx = 0
|
||||
if grid_idx >= len(grids)-1:
|
||||
grid_idx = len(grids)-2
|
||||
return grid_idx
|
||||
|
||||
def execute_trade(price, trade_type, grid_idx, last_grid_idx):
|
||||
"""执行交易"""
|
||||
nonlocal cash, position, cost
|
||||
if trade_type == 'buy' and cash >= PER_GRID_CASH:
|
||||
buy_price = price * (1 + FEE_RATE)
|
||||
buy_amount = PER_GRID_CASH / buy_price
|
||||
cash -= buy_amount * buy_price
|
||||
position += buy_amount
|
||||
cost += buy_amount * buy_price
|
||||
trade_log.append({'date': date, 'price': price, 'type': 'buy'})
|
||||
return True
|
||||
elif trade_type == 'sell' and position > 0:
|
||||
sell_price = price * (1 - FEE_RATE)
|
||||
sell_amount = PER_GRID_CASH / price
|
||||
if sell_amount > position:
|
||||
sell_amount = position
|
||||
cash += sell_amount * sell_price
|
||||
position -= sell_amount
|
||||
cost -= sell_amount * price
|
||||
trade_log.append({'date': date, 'price': price, 'type': 'sell'})
|
||||
return True
|
||||
return False
|
||||
|
||||
for i, row in prices.iterrows():
|
||||
date = row['date']
|
||||
open_price = row['open']
|
||||
close_price = row['close']
|
||||
|
||||
# 获取开盘价和收盘价所在的网格位置
|
||||
open_grid_idx = get_grid_index(open_price)
|
||||
close_grid_idx = get_grid_index(close_price)
|
||||
|
||||
# 开盘价交易处理
|
||||
if last_grid is not None:
|
||||
# 开盘价买入处理
|
||||
if open_grid_idx < last_grid:
|
||||
# 处理多个网格的买入
|
||||
for grid_idx in range(open_grid_idx, last_grid):
|
||||
if not execute_trade(open_price, 'buy', grid_idx, last_grid):
|
||||
break
|
||||
# 开盘价卖出处理
|
||||
elif open_grid_idx > last_grid:
|
||||
# 处理多个网格的卖出
|
||||
for grid_idx in range(last_grid + 1, open_grid_idx + 1):
|
||||
if not execute_trade(open_price, 'sell', grid_idx, last_grid):
|
||||
break
|
||||
|
||||
# 收盘价交易处理
|
||||
if last_grid is not None:
|
||||
# 收盘价买入处理
|
||||
if close_grid_idx < open_grid_idx:
|
||||
# 处理多个网格的买入
|
||||
for grid_idx in range(close_grid_idx, open_grid_idx):
|
||||
if not execute_trade(close_price, 'buy', grid_idx, open_grid_idx):
|
||||
break
|
||||
# 收盘价卖出处理
|
||||
elif close_grid_idx > open_grid_idx:
|
||||
# 处理多个网格的卖出
|
||||
for grid_idx in range(open_grid_idx + 1, close_grid_idx + 1):
|
||||
if not execute_trade(close_price, 'sell', grid_idx, open_grid_idx):
|
||||
break
|
||||
|
||||
# 更新last_grid为收盘价所在网格
|
||||
last_grid = close_grid_idx
|
||||
|
||||
# 记录每日净值
|
||||
total = cash + position * close_price
|
||||
history.append({
|
||||
'date': date,
|
||||
'net': total,
|
||||
'cash': cash,
|
||||
'position': position,
|
||||
'price': close_price,
|
||||
'open_grid': open_grid_idx,
|
||||
'close_grid': close_grid_idx
|
||||
})
|
||||
|
||||
# 统计
|
||||
df_hist = pd.DataFrame(history)
|
||||
max_drawdown = ((df_hist['net'].cummax() - df_hist['net']) / df_hist['net'].cummax()).max()
|
||||
total_return = (df_hist['net'].iloc[-1] - INIT_CASH) / INIT_CASH
|
||||
|
||||
# 胜率统计
|
||||
win, lose = 0, 0
|
||||
for i in range(1, len(trade_log)):
|
||||
if trade_log[i-1]['type']=='buy' and trade_log[i]['type']=='sell':
|
||||
if trade_log[i]['price'] > trade_log[i-1]['price']:
|
||||
win += 1
|
||||
else:
|
||||
lose += 1
|
||||
win_rate = win / (win+lose) if (win+lose)>0 else 0
|
||||
|
||||
return df_hist, trade_log, total_return, max_drawdown, len([t for t in trade_log if t['type']=='buy']), len([t for t in trade_log if t['type']=='sell']), win_rate
|
||||
|
||||
# 主流程
|
||||
if __name__ == '__main__':
|
||||
for grid_type in GRID_TYPES:
|
||||
grids = grid_dict[grid_type]
|
||||
df_hist, trade_log, total_return, max_drawdown, buy_cnt, sell_cnt, win_rate = grid_backtest(raw, grids, grid_type)
|
||||
print(f'\n==== {grid_type} 回测结果 ===')
|
||||
print(f'总收益率: {total_return*100:.2f}%')
|
||||
print(f'最大回撤: {max_drawdown*100:.2f}%')
|
||||
print(f'买入次数: {buy_cnt},卖出次数: {sell_cnt}')
|
||||
print(f'胜率: {win_rate*100:.2f}%')
|
||||
# 绘图
|
||||
fig, ax1 = plt.subplots(figsize=(14,6))
|
||||
ax2 = ax1.twinx()
|
||||
ax1.plot(df_hist['date'], df_hist['price'], label='收盘价', color='gray', alpha=0.7)
|
||||
# 将净值转换为相对净值(从1开始)
|
||||
relative_net = df_hist['net'] / INIT_CASH
|
||||
ax2.plot(df_hist['date'], relative_net, label='净值', color='blue')
|
||||
for t in trade_log:
|
||||
if t['type']=='buy':
|
||||
ax1.scatter(t['date'], t['price'], marker='^', color='red', label='买入' if '买入' not in ax1.get_legend_handles_labels()[1] else "")
|
||||
else:
|
||||
ax1.scatter(t['date'], t['price'], marker='v', color='green', label='卖出' if '卖出' not in ax1.get_legend_handles_labels()[1] else "")
|
||||
for g in grids:
|
||||
ax1.axhline(g, color='orange', linestyle='--', alpha=0.2)
|
||||
plt.title(f'{grid_type} 网格回测')
|
||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||
ax1.legend(lines1 + lines2, labels1 + labels2)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
|
@ -0,0 +1,214 @@
|
|||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
|
||||
# 新增:设置matplotlib支持中文和负号
|
||||
import matplotlib
|
||||
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
|
||||
matplotlib.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
# 新增:屏蔽UserWarning
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=UserWarning)
|
||||
|
||||
# 回测参数
|
||||
INIT_CASH = 4000000
|
||||
FEE_RATE = 0.003 # 千分之3
|
||||
GRID_NUM = 15 # 网格数量
|
||||
PER_GRID_CASH = INIT_CASH / GRID_NUM # 每格买入金额
|
||||
|
||||
def generate_trend_following_grids(price, ma20):
|
||||
"""生成趋势跟随网格
|
||||
上升趋势:
|
||||
- 买入:每跌1%加仓
|
||||
- 卖出:每涨3%减仓
|
||||
下降趋势:
|
||||
- 买入:每跌3%加仓
|
||||
- 卖出:每涨1%减仓
|
||||
"""
|
||||
if price > ma20: # 上升趋势
|
||||
buy_steps = np.arange(price * 0.99, price * 0.85, -price * 0.01) # 每跌1%加仓
|
||||
sell_steps = np.arange(price * 1.03, price * 1.30, price * 0.03) # 每涨3%减仓
|
||||
else: # 下降趋势
|
||||
buy_steps = np.arange(price * 0.97, price * 0.70, -price * 0.03) # 每跌3%加仓
|
||||
sell_steps = np.arange(price * 1.01, price * 1.15, price * 0.01) # 每涨1%减仓
|
||||
return np.sort(np.concatenate([buy_steps, sell_steps]))
|
||||
|
||||
def trend_following_grid_backtest(prices):
|
||||
cash = INIT_CASH
|
||||
position = 0
|
||||
cost = 0
|
||||
history = [] # 记录每日资金、持仓
|
||||
trade_log = [] # 买卖点
|
||||
last_grid = None
|
||||
|
||||
# 计算MA20
|
||||
prices['MA20'] = prices['close'].rolling(window=20).mean()
|
||||
|
||||
def get_grid_index(price, current_grids):
|
||||
"""获取价格所在的网格索引"""
|
||||
if len(current_grids) == 0:
|
||||
return -1
|
||||
grid_idx = np.searchsorted(current_grids, price, side='right') - 1
|
||||
if grid_idx < 0:
|
||||
grid_idx = 0
|
||||
if grid_idx >= len(current_grids)-1:
|
||||
grid_idx = len(current_grids)-2
|
||||
return grid_idx
|
||||
|
||||
def execute_trade(price, trade_type, grid_idx, last_grid_idx, date):
|
||||
"""执行交易"""
|
||||
nonlocal cash, position, cost
|
||||
if trade_type == 'buy' and cash >= PER_GRID_CASH:
|
||||
buy_price = price * (1 + FEE_RATE)
|
||||
buy_amount = PER_GRID_CASH / buy_price
|
||||
cash -= buy_amount * buy_price
|
||||
position += buy_amount
|
||||
cost += buy_amount * buy_price
|
||||
trade_log.append({'date': date, 'price': price, 'type': 'buy'})
|
||||
return True
|
||||
elif trade_type == 'sell' and position > 0:
|
||||
sell_price = price * (1 - FEE_RATE)
|
||||
sell_amount = PER_GRID_CASH / price
|
||||
if sell_amount > position:
|
||||
sell_amount = position
|
||||
cash += sell_amount * sell_price
|
||||
position -= sell_amount
|
||||
cost -= sell_amount * price
|
||||
trade_log.append({'date': date, 'price': price, 'type': 'sell'})
|
||||
return True
|
||||
return False
|
||||
|
||||
for i, row in prices.iterrows():
|
||||
if pd.isna(row['MA20']): # 跳过MA20未形成的时期
|
||||
continue
|
||||
|
||||
date = row['date']
|
||||
open_price = row['open']
|
||||
close_price = row['close']
|
||||
ma20 = row['MA20']
|
||||
|
||||
# 生成当前的趋势跟随网格
|
||||
open_grids = generate_trend_following_grids(open_price, ma20)
|
||||
close_grids = generate_trend_following_grids(close_price, ma20)
|
||||
|
||||
# 获取开盘价和收盘价所在的网格位置
|
||||
open_grid_idx = get_grid_index(open_price, open_grids)
|
||||
close_grid_idx = get_grid_index(close_price, close_grids)
|
||||
|
||||
# 开盘价交易处理
|
||||
if last_grid is not None and open_grid_idx >= 0:
|
||||
# 开盘价买入处理
|
||||
if open_grid_idx < last_grid:
|
||||
for grid_idx in range(open_grid_idx, last_grid):
|
||||
if not execute_trade(open_price, 'buy', grid_idx, last_grid, date):
|
||||
break
|
||||
# 开盘价卖出处理
|
||||
elif open_grid_idx > last_grid:
|
||||
for grid_idx in range(last_grid + 1, open_grid_idx + 1):
|
||||
if not execute_trade(open_price, 'sell', grid_idx, last_grid, date):
|
||||
break
|
||||
|
||||
# 收盘价交易处理
|
||||
if last_grid is not None and close_grid_idx >= 0:
|
||||
# 收盘价买入处理
|
||||
if close_grid_idx < open_grid_idx:
|
||||
for grid_idx in range(close_grid_idx, open_grid_idx):
|
||||
if not execute_trade(close_price, 'buy', grid_idx, open_grid_idx, date):
|
||||
break
|
||||
# 收盘价卖出处理
|
||||
elif close_grid_idx > open_grid_idx:
|
||||
for grid_idx in range(open_grid_idx + 1, close_grid_idx + 1):
|
||||
if not execute_trade(close_price, 'sell', grid_idx, open_grid_idx, date):
|
||||
break
|
||||
|
||||
# 更新last_grid为收盘价所在网格
|
||||
last_grid = close_grid_idx
|
||||
|
||||
# 记录每日净值
|
||||
total = cash + position * close_price
|
||||
history.append({
|
||||
'date': date,
|
||||
'net': total,
|
||||
'cash': cash,
|
||||
'position': position,
|
||||
'price': close_price,
|
||||
'ma20': ma20,
|
||||
'open_grid': open_grid_idx,
|
||||
'close_grid': close_grid_idx
|
||||
})
|
||||
|
||||
# 统计
|
||||
df_hist = pd.DataFrame(history)
|
||||
max_drawdown = ((df_hist['net'].cummax() - df_hist['net']) / df_hist['net'].cummax()).max()
|
||||
total_return = (df_hist['net'].iloc[-1] - INIT_CASH) / INIT_CASH
|
||||
|
||||
# 胜率统计
|
||||
win, lose = 0, 0
|
||||
for i in range(1, len(trade_log)):
|
||||
if trade_log[i-1]['type']=='buy' and trade_log[i]['type']=='sell':
|
||||
if trade_log[i]['price'] > trade_log[i-1]['price']:
|
||||
win += 1
|
||||
else:
|
||||
lose += 1
|
||||
win_rate = win / (win+lose) if (win+lose)>0 else 0
|
||||
|
||||
return df_hist, trade_log, total_return, max_drawdown, len([t for t in trade_log if t['type']=='buy']), len([t for t in trade_log if t['type']=='sell']), win_rate
|
||||
|
||||
def plot_results(df_hist, trade_log, grids=None):
|
||||
"""绘制回测结果"""
|
||||
fig, ax1 = plt.subplots(figsize=(14,6))
|
||||
ax2 = ax1.twinx()
|
||||
|
||||
# 绘制价格、MA20和净值
|
||||
ax1.plot(df_hist['date'], df_hist['price'], label='收盘价', color='gray', alpha=0.7)
|
||||
ax1.plot(df_hist['date'], df_hist['ma20'], label='MA20', color='purple', alpha=0.5)
|
||||
relative_net = df_hist['net'] / INIT_CASH
|
||||
ax2.plot(df_hist['date'], relative_net, label='净值', color='blue')
|
||||
|
||||
# 绘制交易点
|
||||
for t in trade_log:
|
||||
if t['type']=='buy':
|
||||
ax1.scatter(t['date'], t['price'], marker='^', color='red', label='买入' if '买入' not in ax1.get_legend_handles_labels()[1] else "")
|
||||
else:
|
||||
ax1.scatter(t['date'], t['price'], marker='v', color='green', label='卖出' if '卖出' not in ax1.get_legend_handles_labels()[1] else "")
|
||||
|
||||
# 绘制最后一个时间点的网格线
|
||||
if grids is not None and len(grids) > 0:
|
||||
for g in grids:
|
||||
ax1.axhline(g, color='orange', linestyle='--', alpha=0.2)
|
||||
|
||||
plt.title('趋势跟随网格回测')
|
||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||
ax1.legend(lines1 + lines2, labels1 + labels2)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 读取数据
|
||||
DATA_PATH = os.path.join(os.path.dirname(__file__), '0020.HK.csv')
|
||||
# 处理文件头乱码和尾部注释
|
||||
raw = pd.read_csv(DATA_PATH, header=None, skiprows=1, names=['code','date','open','high','low','close','pct'])
|
||||
raw = raw[raw['close'].apply(lambda x: str(x).replace('.','',1).isdigit())] # 去除空行和注释
|
||||
raw['date'] = pd.to_datetime(raw['date'])
|
||||
raw['close'] = raw['close'].astype(float)
|
||||
raw['open'] = raw['open'].astype(float)
|
||||
raw = raw.sort_values('date').reset_index(drop=True)
|
||||
|
||||
# 运行回测
|
||||
df_hist, trade_log, total_return, max_drawdown, buy_cnt, sell_cnt, win_rate = trend_following_grid_backtest(raw)
|
||||
|
||||
# 打印统计结果
|
||||
print('\n==== 趋势跟随网格回测结果 ===')
|
||||
print(f'总收益率: {total_return*100:.2f}%')
|
||||
print(f'最大回撤: {max_drawdown*100:.2f}%')
|
||||
print(f'买入次数: {buy_cnt},卖出次数: {sell_cnt}')
|
||||
print(f'胜率: {win_rate*100:.2f}%')
|
||||
|
||||
# 绘制结果
|
||||
last_price = df_hist['price'].iloc[-1]
|
||||
last_ma20 = df_hist['ma20'].iloc[-1]
|
||||
last_grids = generate_trend_following_grids(last_price, last_ma20)
|
||||
plot_results(df_hist, trade_log, last_grids)
|
|
@ -11,7 +11,7 @@ XUEQIU_HEADERS = {
|
|||
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
'Client-Version': 'v2.44.75',
|
||||
'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; smidV2=20250327160437f244626e8b47ca2a7992f30f389e4e790074ae48656a22f10; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; Hm_lvt_1db88642e346389874251b5a1eded6e3=1746410725; __utma=1.434320573.1747189698.1747189698.1747189698.1; __utmc=1; __utmz=1.1747189698.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); snbim_minify=true; acw_tc=0a27a9dd17489230816243798e0070441d5e7160c0ed179607143a953db903; xq_a_token=ef79e6da376751a4bf6c1538103e9894d44473e1; xqat=ef79e6da376751a4bf6c1538103e9894d44473e1; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzUxNTE1MDgxLCJjdG0iOjE3NDg5MjMwODE2NDQsImNpZCI6ImQ5ZDBuNEFadXAifQ.gQrIt4VI73JLUFGVSTKpXidhFIMwlusBKyrzYwClwCBszXCooQY3WnFqlbXqSX3SwnMapuveOFUM5sGIOoZ8oDF8cZYs3HDz5vezR-2nes9gfZr2nZcUfZzNRJ299wlX3Zis5NbnzNlfnisUhv9GUfEZjQ_Rs37B4qRbQZVC2kdN1Z0xB8j1MplSTOsYj4IliQntuaTo-8SBh-4zz5244dnF85xREBVxtFzzCtHUhn9B-mzxE81_42nwrDscvow-4_jtlJXlqbehiAFxld-dCWDXwmCju9lRWu_WzdoQe19n-c6jhCZZ1pU1JGsYyhIAsd1gV064jQ6FxfN38so1Eg; xq_r_token=30a80318ebcabffbe194e7deecb108b665e8c894; is_overseas=0; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1748923088; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=b+jlfRtg2lC80dGHk9izZ9Od1QBbaKrdx1aAMbruXo2ULyhkygsXnhJoa7lOWNgnQAphRKw3864D5K+U2pTL5g%3D%3D; ssxmod_itna=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0shqDyji2YsBGdTYKYUxGXxN4xiNDAc40iDC3WLPeUpx5h5o5Gmxt3qU6P5b48r89Y4sKs=BkpxKFTG4SQW4odeGLDY=DCTKKSMiD4b3Dt4DIDAYDDxDWm4DLDYoDY3uexGPo2mTNpm2bD0YDzqDgD7jbmeDEDG3D0bbetGDo1Q4DGqDSWZHTxD3Dffb4DDN4zIG0GmDDbrR=qmcbC=7O9Wtox0tWDBL5YvysdVC441TXpw8w7WaaxBQD7d9Q5na7fCW13rWkYY0Yeoe7hx+BxYrKch4SbKOAYY7hq7hR0D3E5YD5QADW0D/hQ7Emh07hiY7xdUginMzSTblushiee2YKbK5nYO0t3Ede7d46DqEQMA557QODdNG4WG+slx5bhWiDD; ssxmod_itna2=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0shqDyji2YsBGdTYKY4xDfiOYiiBq4YDj44KWGfmoD/8okGxAeG/0Dt6Q7D6cGudn3qfM5QntBLc5pp/FFY4hly50hUr5qB2v45io/FQi4eD',
|
||||
'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; snbim_minify=true; xq_a_token=ef79e6da376751a4bf6c1538103e9894d44473e1; xqat=ef79e6da376751a4bf6c1538103e9894d44473e1; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzUxNTE1MDgxLCJjdG0iOjE3NDg5MjMwODE2NDQsImNpZCI6ImQ5ZDBuNEFadXAifQ.gQrIt4VI73JLUFGVSTKpXidhFIMwlusBKyrzYwClwCBszXCooQY3WnFqlbXqSX3SwnMapuveOFUM5sGIOoZ8oDF8cZYs3HDz5vezR-2nes9gfZr2nZcUfZzNRJ299wlX3Zis5NbnzNlfnisUhv9GUfEZjQ_Rs37B4qRbQZVC2kdN1Z0xB8j1MplSTOsYj4IliQntuaTo-8SBh-4zz5244dnF85xREBVxtFzzCtHUhn9B-mzxE81_42nwrDscvow-4_jtlJXlqbehiAFxld-dCWDXwmCju9lRWu_WzdoQe19n-c6jhCZZ1pU1JGsYyhIAsd1gV064jQ6FxfN38so1Eg; xq_r_token=30a80318ebcabffbe194e7deecb108b665e8c894; Hm_lvt_1db88642e346389874251b5a1eded6e3=1749028611; is_overseas=0; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1750034926; ssxmod_itna=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q07hqDyliiYwirG4tshiQBKD/KlYeDZDGFdDqx0Ei6FiBFKCezjCGbKBACQ5xC3o0aOyndbV3Ab3t8NXiK3y4xB3DExGkR0iYeK4DxrPD5xDTDWeDGDD3WxGaDmeDeho+D0bmHUOvrU7oD7eDXxGCDQFor4GWDiPD7Po45iim=KxD0xD1ESkEDDPDaroxDG5NlQ9weDi3rfgsLFbLDdwIqQimD753DlcqwXLXmktxGfoyzd=bdI8fDCKDjxdIx93QW33vimn4xqiDb37Yn4qe2qW+QKGxAiUmrKiirFBDFAQQDT7GN0xq3DPQGQBwOb7Y3FrjAPdmrRiYIvZRHm9HV4hY3Ybz777DbRGxi4T/48Bxt3GhUhtBhNfqtRDWq2qADPfYYsihWYPeD; ssxmod_itna2=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q07hqDyliiYwirG4tshiQYeDA4pg2w+RYD7POqKepgqDlcb9wAqieP5+6V+KK5w6Q4dMdqCLomgLyxQn93CdLjc3pXEYq0/0h+jCdiOWudEmrIjmRf1+lLX0OGj02GXiiblBod5++dyGbWSnufTL+nxBWIQimWCI3ueZSne50WYT6afRSyCo79FGa6WEk2j30a5d9LFRZFb==8bO73cfarqe=kkkK09RmTUISi6qQwqZfChNd3Ktj6E3tj9GjXLWwV59vpUqOnFXIp9/rujWHt7v3KhIHMUrH70=mn1em1A7ujba3Y4jwqKyWRDR4q7/rCDFoyF7AiK4rNz018Ix0rYfYx+OYm2=nxNlxPGTKYStcOPuEDD',
|
||||
'Referer': 'https://weibo.com/u/7735765253',
|
||||
'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
|
||||
'Sec-Ch-Ua-Mobile': '?0',
|
||||
|
|
|
@ -13,7 +13,14 @@ DB_CONFIG = {
|
|||
'password': 'Chlry#$.8',
|
||||
'database': 'db_gp_cj'
|
||||
}
|
||||
|
||||
# redis配置
|
||||
REDIS_CONFIG = {
|
||||
'host': '192.168.18.208',
|
||||
'port': 6379,
|
||||
'password': 'wlkj2018',
|
||||
'db': 13,
|
||||
'socket_timeout': 5
|
||||
}
|
||||
# 创建数据库连接URL
|
||||
DB_URL = f"mysql+pymysql://{DB_CONFIG['user']}:{DB_CONFIG['password']}@{DB_CONFIG['host']}:{DB_CONFIG['port']}/{DB_CONFIG['database']}"
|
||||
|
||||
|
|
|
@ -877,11 +877,12 @@ class FinancialAnalyzer:
|
|||
|
||||
# 缓存结果,有效期1天(86400秒)
|
||||
try:
|
||||
redis_client.set(
|
||||
kkk = redis_client.set(
|
||||
cache_key,
|
||||
json.dumps(result, default=str), # 使用default=str处理日期等特殊类型
|
||||
ex=86400 # 1天的秒数
|
||||
)
|
||||
print(kkk)
|
||||
logger.info(f"已缓存股票 {stock_code} 的财务分析数据,有效期为1天")
|
||||
except Exception as cache_error:
|
||||
logger.warning(f"缓存财务分析数据失败: {cache_error}")
|
||||
|
|
|
@ -824,4 +824,178 @@ class IndustryAnalyzer:
|
|||
|
||||
except Exception as e:
|
||||
logger.error(f"获取概念板块综合分析失败: {e}")
|
||||
return {"success": False, "message": f"获取概念板块综合分析失败: {e}"}
|
||||
return {"success": False, "message": f"获取概念板块综合分析失败: {e}"}
|
||||
|
||||
def batch_calculate_industry_crowding(self, industries: List[str], concepts: List[str] = None) -> None:
|
||||
"""
|
||||
批量计算多个行业和概念板块的拥挤度指标
|
||||
|
||||
Args:
|
||||
industries: 行业列表
|
||||
concepts: 概念板块列表,默认为None
|
||||
"""
|
||||
try:
|
||||
# 1. 获取3年数据的时间范围
|
||||
end_date = datetime.datetime.now().strftime('%Y-%m-%d')
|
||||
start_date = (datetime.datetime.now() - datetime.timedelta(days=3*365)).strftime('%Y-%m-%d')
|
||||
|
||||
# 2. 一次性获取全市场所有股票的交易数据
|
||||
query = text("""
|
||||
SELECT
|
||||
symbol,
|
||||
`timestamp` AS trade_date,
|
||||
amount
|
||||
FROM
|
||||
gp_day_data
|
||||
WHERE
|
||||
`timestamp` BETWEEN :start_date AND :end_date
|
||||
""")
|
||||
|
||||
with self.engine.connect() as conn:
|
||||
df_all = pd.read_sql(query, conn, params={"start_date": start_date, "end_date": end_date})
|
||||
|
||||
# 3. 计算每日市场总成交额
|
||||
df_total = df_all.groupby('trade_date')['amount'].sum().reset_index()
|
||||
df_total.columns = ['trade_date', 'total_market_amount']
|
||||
|
||||
# 4. 获取所有行业和概念板块的股票映射
|
||||
industry_stocks = {}
|
||||
for industry in industries:
|
||||
stocks = self.get_industry_stocks(industry)
|
||||
if stocks:
|
||||
industry_stocks[industry] = stocks
|
||||
|
||||
if concepts:
|
||||
concept_stocks = {}
|
||||
for concept in concepts:
|
||||
stocks = self.get_concept_stocks(concept)
|
||||
if stocks:
|
||||
concept_stocks[concept] = stocks
|
||||
|
||||
# 5. 批量计算行业拥挤度
|
||||
for industry, stocks in industry_stocks.items():
|
||||
try:
|
||||
# 计算行业成交额
|
||||
df_industry = df_all[df_all['symbol'].isin(stocks)].groupby('trade_date')['amount'].sum().reset_index()
|
||||
df_industry.columns = ['trade_date', 'sector_amount']
|
||||
|
||||
# 合并数据
|
||||
df = pd.merge(df_total, df_industry, on='trade_date', how='inner')
|
||||
|
||||
# 计算指标
|
||||
df['industry_amount_ratio'] = (df['sector_amount'] / df['total_market_amount']) * 100
|
||||
df['percentile'] = df['industry_amount_ratio'].rank(pct=True) * 100
|
||||
df['crowding_level'] = pd.cut(
|
||||
df['percentile'],
|
||||
bins=[0, 20, 40, 60, 80, 100],
|
||||
labels=['不拥挤', '较不拥挤', '中性', '较为拥挤', '极度拥挤']
|
||||
)
|
||||
|
||||
# 缓存结果
|
||||
cache_key = f"industry_crowding:{industry}"
|
||||
redis_client.set(
|
||||
cache_key,
|
||||
json.dumps(df.to_dict(orient='records'), default=str),
|
||||
ex=86400
|
||||
)
|
||||
|
||||
logger.info(f"成功计算行业 {industry} 的拥挤度指标,共 {len(df)} 条记录")
|
||||
except Exception as e:
|
||||
logger.error(f"计算行业 {industry} 的拥挤度指标时出错: {str(e)}")
|
||||
continue
|
||||
|
||||
# 6. 批量计算概念板块拥挤度
|
||||
if concepts:
|
||||
for concept, stocks in concept_stocks.items():
|
||||
try:
|
||||
# 计算概念板块成交额
|
||||
df_concept = df_all[df_all['symbol'].isin(stocks)].groupby('trade_date')['amount'].sum().reset_index()
|
||||
df_concept.columns = ['trade_date', 'sector_amount']
|
||||
|
||||
# 合并数据
|
||||
df = pd.merge(df_total, df_concept, on='trade_date', how='inner')
|
||||
|
||||
# 计算指标
|
||||
df['industry_amount_ratio'] = (df['sector_amount'] / df['total_market_amount']) * 100
|
||||
df['percentile'] = df['industry_amount_ratio'].rank(pct=True) * 100
|
||||
df['crowding_level'] = pd.cut(
|
||||
df['percentile'],
|
||||
bins=[0, 20, 40, 60, 80, 100],
|
||||
labels=['不拥挤', '较不拥挤', '中性', '较为拥挤', '极度拥挤']
|
||||
)
|
||||
|
||||
# 缓存结果
|
||||
cache_key = f"concept_crowding:{concept}"
|
||||
redis_client.set(
|
||||
cache_key,
|
||||
json.dumps(df.to_dict(orient='records'), default=str),
|
||||
ex=86400
|
||||
)
|
||||
|
||||
logger.info(f"成功计算概念板块 {concept} 的拥挤度指标,共 {len(df)} 条记录")
|
||||
except Exception as e:
|
||||
logger.error(f"计算概念板块 {concept} 的拥挤度指标时出错: {str(e)}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"批量计算行业拥挤度指标失败: {str(e)}")
|
||||
|
||||
def filter_crowding_by_percentile(self, min_percentile: float, max_percentile: float) -> dict:
|
||||
"""
|
||||
查询所有缓存中的行业和概念板块拥挤度,筛选最后一个交易日拥挤度百分位在[min, max]区间的行业/概念。
|
||||
返回格式:{'industry': [...], 'concept': [...]},每个元素包含名称、最后日期、拥挤度百分位、拥挤度等级等。
|
||||
"""
|
||||
result = {'industry': [], 'concept': []}
|
||||
try:
|
||||
# 获取所有行业和概念板块的缓存key
|
||||
industry_keys = redis_client.keys('industry_crowding:*')
|
||||
concept_keys = redis_client.keys('concept_crowding:*')
|
||||
|
||||
# 行业
|
||||
for key in industry_keys:
|
||||
try:
|
||||
name = key.split(':', 1)[1]
|
||||
cached_data = redis_client.get(key)
|
||||
if not cached_data:
|
||||
continue
|
||||
df = pd.DataFrame(json.loads(cached_data))
|
||||
if df.empty:
|
||||
continue
|
||||
last_row = df.iloc[-1]
|
||||
percentile = float(last_row['percentile'])
|
||||
if min_percentile <= percentile <= max_percentile:
|
||||
result['industry'].append({
|
||||
'name': name,
|
||||
'last_date': last_row['trade_date'],
|
||||
'percentile': percentile,
|
||||
'crowding_level': last_row.get('crowding_level', None)
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning(f"处理行业缓存 {key} 时出错: {e}")
|
||||
continue
|
||||
|
||||
# 概念板块
|
||||
for key in concept_keys:
|
||||
try:
|
||||
name = key.split(':', 1)[1]
|
||||
cached_data = redis_client.get(key)
|
||||
if not cached_data:
|
||||
continue
|
||||
df = pd.DataFrame(json.loads(cached_data))
|
||||
if df.empty:
|
||||
continue
|
||||
last_row = df.iloc[-1]
|
||||
percentile = float(last_row['percentile'])
|
||||
if min_percentile <= percentile <= max_percentile:
|
||||
result['concept'].append({
|
||||
'name': name,
|
||||
'last_date': last_row['trade_date'],
|
||||
'percentile': percentile,
|
||||
'crowding_level': last_row.get('crowding_level', None)
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning(f"处理概念缓存 {key} 时出错: {e}")
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error(f"筛选拥挤度缓存时出错: {e}")
|
||||
return result
|
Loading…
Reference in New Issue