stocks HS300 from 2010


Homework 1
1. In HW1.xls, you will find the daily closing prices of 50 stocks and the HS300 from 2010 to 2021. Suppose that you always use the data from the past five years to estimate the expected return and covariance matrix, and you rebalance your portfolio every 4 months by solving the Markowitz portfolio selection problem every 4 months, starting from the first trading day of 2015. Suppose that the risk-free rate is 3% per annum and your target annualized expected return is 6%, shorting is allowed. Plot the performance of this strategy over the period of 2015-2021, and also plot the performance of HS300 over the same period. What can you learn from the comparison?
2. In HW1.xls, pick up any six stocks. For each stock, compute the beta value using 12 years of data and using HS300 to represent the market portfolio, and test whether the corresponding alpha = 0 at the 95% level. Assuming the risk-free rate is 3% per annum.
3. Suppose that we have a European put option whose strike price K = 40, and the stock price is currently at $40 as well. The option expires in 18 months. Furthermore, suppose that the volatility of the stock is 40% per annum and the risk-free interest rate is 3% per annum.
(a) Construct an m-step binomial tree model to price this option. You may set m=10, 50 and
100, and compare the values with the Black-Scholes formula.
(b) If the option is American style, use the binomial tree model with m=100 to determine its
price.
The homework is due on October 11. You may hand it in to the professor before the class starts.

获取到 1483 只成分股 处理批次 1/38, 股票数量: 40 Traceback (most recent call last): File "D:\Program Files (x86)\Python\Python311\Lib\site-packages\pandas\core\indexes\base.py", line 3812, in get_loc return self._engine.get_loc(casted_key) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "pandas/_libs/index.pyx", line 167, in pandas._libs.index.IndexEngine.get_loc File "pandas/_libs/index.pyx", line 196, in pandas._libs.index.IndexEngine.get_loc File "pandas/_libs/hashtable_class_helper.pxi", line 7088, in pandas._libs.hashtable.PyObjectHashTable.get_item File "pandas/_libs/hashtable_class_helper.pxi", line 7096, in pandas._libs.hashtable.PyObjectHashTable.get_item KeyError: '最低' The above exception was the direct cause of the following exception: Traceback (most recent call last): File "D:\NEWS\Python\script\6.py", line 107, in <module> new_low_stocks = batch_process_stocks(all_stocks) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\NEWS\Python\script\6.py", line 89, in batch_process_stocks result = future.result() ^^^^^^^^^^^^^^^ File "D:\Program Files (x86)\Python\Python311\Lib\concurrent\futures\_base.py", line 449, in result return self.__get_result() ^^^^^^^^^^^^^^^^^^^ File "D:\Program Files (x86)\Python\Python311\Lib\concurrent\futures\_base.py", line 401, in __get_result raise self._exception File "D:\Program Files (x86)\Python\Python311\Lib\concurrent\futures\thread.py", line 58, in run result = self.fn(*self.args, **self.kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\NEWS\Python\script\6.py", line 62, in check_new_low min_5_days = last_5_days['最低'].min() ~~~~~~~~~~~^^^^^^ File "D:\Program Files (x86)\Python\Python311\Lib\site-packages\pandas\core\frame.py", line 4107, in __getitem__ indexer = self.columns.get_loc(key) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\Program Files (x86)\Python\Python311\Lib\site-packages\pandas\core\indexes\base.py", line 3819, in get_loc raise KeyError(key) from err KeyError: '最低'
08-22
<think>我们遇到了一个KeyError: '最低'的错误,这是因为在从AKShare获取的数据中,列名可能是'low'而不是中文的'最低'。 根据AKShare的接口文档,输出参数中最低价对应的列名是'low'(在示例中我们看到列名是英文的),但我们在代码中使用了中文'最低'。 因此,我们需要调整列名,使用英文的'low'。 修改点: 在check_new_low函数中,将'最低'改为'low' 同时,我们注意到输出参数中列名都是英文的,包括: 'date', 'open', 'close', 'high', 'low', 'amount' 所以我们需要将代码中所有使用中文列名的地方改为英文列名。 另外,在获取成分股时,我们使用的函数`ak.index_stock_cons`返回的数据结构可能也需要检查,确保我们获取的列名正确。 修正步骤: 1. 将`last_5_days['最低']`改为`last_5_days['low']` 2. 将`prev_55_days['最低']`改为`prev_55_days['low']` 同时,我们还需要注意,获取成分股的函数返回的列名也可能需要调整。根据AKShare文档,`index_stock_cons`返回的沪深300成分股的列名是'品种代码',但中证500和中证1000的列名可能不同?我们需要统一。 实际上,我们可以打印一下获取的成分股数据,确保我们取的是正确的列。 为了保险起见,我们修改一下获取成分股的函数,确保合并时使用正确的列名。 根据AKShare的源码,不同指数的成分股接口返回的列名可能不一致。我们这里使用统一的列名处理。 查看AKShare的index_stock_cons函数文档: 沪深300: 返回字段包括 '品种代码', '品种名称' 中证500: 返回字段包括 '品种代码', '品种名称' 中证1000: 返回字段包括 '品种代码', '品种名称' 所以,我们使用'品种代码'是正确的。 因此,我们只需要修改历史数据中的列名即可。 此外,我们还需要注意,获取的历史数据可能为空,所以我们在计算前需要检查。 修改后的check_new_low函数: ```python def check_new_low(stock_code): """检查股票是否创55日新低""" hist_data = get_stock_history(stock_code) if hist_data is None or len(hist_data) < 60: return None # 计算最近5个交易日最低价(含当日) last_5_days = hist_data.tail(5) min_5_days = last_5_days['low'].min() # 计算55日新低(不含最近5个交易日) prev_55_days = hist_data.head(55) min_55_days = prev_55_days['low'].min() # 判断是否创新低 if min_5_days < min_55_days: return stock_code return None ``` 同时,在获取历史数据后,我们可以打印一下列名,以确保列名正确(调试时使用,正式代码可注释掉)。 修改get_stock_history函数,添加一行打印列名(可选,用于调试): ```python try: df = ak.stock_zh_a_hist_tx( symbol=symbol, start_date=start_date, end_date=end_date, adjust="qfq", # 使用前复权数据 timeout=10 ) # 调试:打印列名 # print(f"{stock_code} 的列名: {df.columns}") return df.tail(days) if len(df) >= days else None except Exception as e: print(f"获取 {stock_code} 数据失败: {str(e)}") return None ``` 另外,我们还需要注意,获取的历史数据可能不是按日期排序的?但AKShare返回的数据是按日期从远到近(升序)排列的。我们的操作: - head(55) 取前55行(即最早的55个交易日,也就是55个交易日之前到55天前的数据) - tail(5) 取最后5行(即最近的5个交易日) 这样符合要求吗? 要求: 最近5个交易日:包含当日,向上连续5个交易日(即最近5天) 55日新低:当日行情之外,向上连续的55个交易日(即除去最近5个交易日,再往前55个交易日?) 但是注意,我们的数据是升序排列(日期从小到大): - 第0行是最早的日期 - 最后一行是最近的日期 所以: - 整个数据框:从100天前到今天(共60个交易日,因为周末节假日,实际可能不足60行,所以我们要求至少60行) - 前55行:从最早到倒数第6个交易日(即55个交易日,但不包含最近5个交易日) - 后5行:最近5个交易日 因此,我们的计算逻辑正确。 现在,我们修改列名后重新运行。 同时,为了避免其他错误,我们可以增加一些异常处理。 修改后的完整代码: ```python import akshare as ak import pandas as pd import time import datetime from concurrent.futures import ThreadPoolExecutor, as_completed # 1. 获取成分股列表 def get_index_constituents(): """获取三大指数成分股合并列表""" # 获取沪深300成分股 hs300 = ak.index_stock_cons(symbol="000300") # 获取中证500成分股 zz500 = ak.index_stock_cons(symbol="000905") # 获取中证1000成分股 zz1000 = ak.index_stock_cons(symbol="000852") # 合并并去重 all_stocks = pd.concat([hs300, zz500, zz1000])[['品种代码']].drop_duplicates() return [code for code in all_stocks['品种代码']] # 2. 添加市场标识 def add_market_prefix(stock_code): """根据股票代码添加市场标识""" if stock_code.startswith('6'): return f"sh{stock_code}" elif stock_code.startswith('0') or stock_code.startswith('3'): return f"sz{stock_code}" return None # 3. 获取股票历史数据 def get_stock_history(stock_code, days=60): """获取股票历史行情数据""" symbol = add_market_prefix(stock_code) if not symbol: return None end_date = datetime.datetime.now().strftime("%Y%m%d") start_date = (datetime.datetime.now() - datetime.timedelta(days=100)).strftime("%Y%m%d") try: df = ak.stock_zh_a_hist_tx( symbol=symbol, start_date=start_date, end_date=end_date, adjust="qfq", # 使用前复权数据 timeout=10 ) # 确保数据足够 if len(df) < days: return None return df except Exception as e: print(f"获取 {stock_code} 数据失败: {str(e)}") return None # 4. 检查是否创新低 def check_new_low(stock_code): """检查股票是否创55日新低""" hist_data = get_stock_history(stock_code) if hist_data is None or len(hist_data) < 60: return None # 计算最近5个交易日最低价(含当日) last_5_days = hist_data.tail(5) min_5_days = last_5_days['low'].min() # 计算55日新低(不含最近5个交易日) prev_55_days = hist_data.head(55) # 注意:这里取的是前55行(即最早的55个交易日,但实际上是55个交易日之前的数据,不包含最近5个交易日) # 但是,我们的数据是升序排列,所以前55行是较早的55个交易日,而最近5个交易日是最后5行。 # 因此,prev_55_days就是最近5个交易日之前的55个交易日。 min_55_days = prev_55_days['low'].min() # 判断是否创新低 if min_5_days < min_55_days: return stock_code return None # 5. 分批处理函数 def batch_process_stocks(stock_list, batch_size=40, delay=20): """分批处理股票列表""" results = [] total_batches = (len(stock_list) + batch_size - 1) // batch_size for i in range(total_batches): start_idx = i * batch_size end_idx = min((i + 1) * batch_size, len(stock_list)) batch = stock_list[start_idx:end_idx] print(f"处理批次 {i+1}/{total_batches}, 股票数量: {len(batch)}") with ThreadPoolExecutor(max_workers=5) as executor: futures = {executor.submit(check_new_low, code): code for code in batch} for future in as_completed(futures): result = future.result() if result: print(f"发现新低股票: {result}") results.append(result) if i < total_batches - 1: print(f"等待 {delay} 秒...") time.sleep(delay) return results # 主程序 if __name__ == "__main__": # 获取所有成分股 all_stocks = get_index_constituents() print(f"获取到 {len(all_stocks)} 只成分股") # 分批处理并筛选创新低股票 new_low_stocks = batch_process_stocks(all_stocks) # 保存结果到文件 save_path = r"D:\NEWS\new_low_stocks.txt" with open(save_path, 'w', encoding='utf-8') as f: for stock in new_low_stocks: f.write(f"{stock}\n") print(f"发现 {len(new_low_stocks)} 只创新低股票,结果已保存到 {save_path}") ``` 注意:我们修改了列名为'low',并调整了获取历史数据的函数,确保返回的数据足够60行才继续处理。 如果仍然遇到列名错误,可以打印列名进行调试(在get_stock_history函数中取消注释打印列名的行)。 现在,我们重新运行代码,应该可以避免列名错误。 §§相关问题§§: 1. 如何确保获取的历史数据包含足够的交易日(考虑节假日)? 2. 如果数据源返回的列名发生变化,如何使代码更具适应性? 3. 如何将结果保存到数据库(如MySQL)而不是文本文件? 4. 如何扩展此代码以同时检查多个条件(如同时创新低且成交量放大)?</think>您的代码遇到了一个关键错误:`KeyError: '最低'`。这是因为在从AKShare获取的数据中,最低价列的列名是英文的`'low'`,而您尝试使用中文的`'最低'`来访问它。让我修复这个问题并提供完整的解决方案: ```python import akshare as ak import pandas as pd import time import datetime import numpy as np from concurrent.futures import ThreadPoolExecutor, as_completed # 1. 获取成分股列表(修复列名问题) def get_index_constituents(): """获取三大指数成分股合并列表""" # 获取沪深300成分股 hs300 = ak.index_stock_cons(symbol="000300") # 获取中证500成分股 zz500 = ak.index_stock_cons(symbol="000905") # 获取中证1000成分股 zz1000 = ak.index_stock_cons(symbol="000852") # 合并并去重 - 使用正确的列名 all_stocks = pd.concat([ hs300[['品种代码']], zz500[['品种代码']], zz1000[['品种代码']] ]).drop_duplicates() return [code for code in all_stocks['品种代码']] # 2. 添加市场标识(保持不变) def add_market_prefix(stock_code): """根据股票代码添加市场标识""" if stock_code.startswith('6'): return f"sh{stock_code}" elif stock_code.startswith('0') or stock_code.startswith('3'): return f"sz{stock_code}" return None # 3. 获取股票历史数据(添加错误处理和日志) def get_stock_history(stock_code, days=60): """获取股票历史行情数据""" symbol = add_market_prefix(stock_code) if not symbol: print(f"跳过无效代码: {stock_code}") return None end_date = datetime.datetime.now().strftime("%Y%m%d") start_date = (datetime.datetime.now() - datetime.timedelta(days=100)).strftime("%Y%m%d") try: df = ak.stock_zh_a_hist_tx( symbol=symbol, start_date=start_date, end_date=end_date, adjust="qfq", # 使用前复权数据 timeout=15 ) # 检查数据是否足够 if len(df) < days: print(f"{stock_code} 数据不足: {len(df)} < {days}") return None # 重命名列名确保一致性 df = df.rename(columns={ '日期': 'date', '开盘': 'open', '收盘': 'close', '最高': 'high', '最低': 'low', '成交量': 'volume' }) return df except Exception as e: print(f"获取 {stock_code} 数据失败: {str(e)}") return None # 4. 检查是否创新低(修复列名问题) def check_new_low(stock_code): """检查股票是否创55日新低""" hist_data = get_stock_history(stock_code) if hist_data is None or len(hist_data) < 60: return None # 计算最近5个交易日最低价(含当日) last_5_days = hist_data.tail(5) min_5_days = last_5_days['low'].min() # 计算55日新低(不含最近5个交易日) prev_55_days = hist_data.head(55) min_55_days = prev_55_days['low'].min() # 判断是否创新低 if min_5_days < min_55_days: print(f"发现新低股票: {stock_code} (5日最低:{min_5_days} < 55日最低:{min_55_days})") return stock_code return None # 5. 分批处理函数(添加更详细的日志) def batch_process_stocks(stock_list, batch_size=40, delay=20): """分批处理股票列表""" results = [] total_batches = (len(stock_list) + batch_size - 1) // batch_size for i in range(total_batches): start_idx = i * batch_size end_idx = min((i + 1) * batch_size, len(stock_list)) batch = stock_list[start_idx:end_idx] print(f"\n处理批次 {i+1}/{total_batches}, 股票数量: {len(batch)}") print("="*50) batch_results = [] with ThreadPoolExecutor(max_workers=5) as executor: futures = {executor.submit(check_new_low, code): code for code in batch} for future in as_completed(futures): try: result = future.result() if result: batch_results.append(result) except Exception as e: stock_code = futures[future] print(f"处理 {stock_code} 时出错: {str(e)}") results.extend(batch_results) print(f"本批次发现 {len(batch_results)} 只创新低股票") if i < total_batches - 1: print(f"等待 {delay} 秒...") time.sleep(delay) return results # 主程序(添加进度跟踪) if __name__ == "__main__": print("开始获取成分股列表...") all_stocks = get_index_constituents() print(f"获取到 {len(all_stocks)} 只成分股") print("\n开始筛选创新低股票...") start_time = time.time() new_low_stocks = batch_process_stocks(all_stocks) # 保存结果到文件 save_path = r"D:\NEWS\new_low_stocks.txt" with open(save_path, 'w', encoding='utf-8') as f: for stock in new_low_stocks: f.write(f"{stock}\n") elapsed = time.time() - start_time print(f"\n完成! 发现 {len(new_low_stocks)} 只创新低股票") print(f"总耗时: {elapsed:.2f}秒") print(f"结果已保存到 {save_path}") ``` ### 修复的关键问题: 1. **列名不一致问题**: - AKShare返回的数据使用中文列名('最低')而非英文 - 添加了列名重命名步骤确保一致性: ```python df = df.rename(columns={ '日期': 'date', '开盘': 'open', '收盘': 'close', '最高': 'high', '最低': 'low', '成交量': 'volume' }) ``` 2. **增强错误处理**: - 添加了更详细的错误日志 - 在批量处理中捕获单个股票的错误 - 添加数据长度检查(确保有足够交易日) 3. **改进日志输出**: - 添加批次分隔线 - 显示每批次发现的创新低股票数量 - 显示总处理时间 4. **数据验证**: - 添加股票代码有效性检查 - 确保数据有足够天数(60天) ### 使用建议: 1. **首次运行时**: ```python # 测试少量股票 test_stocks = ['000001', '600000', '000002'] batch_process_stocks(test_stocks, batch_size=2) ``` 2. **完整运行时**: ```python # 使用推荐的批量大小和延迟 batch_process_stocks(all_stocks, batch_size=40, delay=20) ``` 3. **如果遇到IP限制**: ```python # 增加延迟和减少批量大小 batch_process_stocks(all_stocks, batch_size=20, delay=30) ``` ### 优化方向: 1. 添加数据缓存机制避免重复请求 2. 使用指数成分股API直接获取成分股列表 3. 添加断点续传功能 4. 实现更智能的节流控制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值