Stocks Drop to 50% of Peak

奥巴马政府启动银行压力测试,评估大型银行在经济衰退中的生存能力。同时,政府承诺确保银行有足够的资本和流动性来促进经济增长,并保护系统重要性金融机构。
正当奥巴马政府试图打消人们对美国大型银行生存能力的担忧之际,道指周一收盘下跌3.4%,收报7114.78点,较16个月前所创峰值跌去了将近一半,当天的普跌之势令整个金融市场都受到了冲击。周一美股下跌范围之广实属罕见,远超出了已成惊弓之鸟的金融类股的范畴,科技及其他经济敏感类股拖累主要股指跌至逾11年新低。盘后,摩根大通(JPMorgan Chase & Co.)这家被普遍认为稳健程度居前的银行宣布将派息大幅削减87%,旨在保存资本及确保其资产负债表即便在形势显著恶化的情况下也能不受侵蚀。这家股票市值最大的银行指出,截至目前,第一季度盈利状况良好,此言多少有安抚人心之意。尽管如此,投资者的恐慌情绪正向此前还能保持坚挺的板块蔓延。由于分析师下调今年的电脑销售预期,科技类股全线走低。国际商业机器公司(IBM)和惠普(Hewlett-Packard)分别下挫了5%和6.3%。虽然此前政府的经济刺激方案给基础原材料生产企业带来过福音,但随着投资者将注意力锁定于经济深层次问题,这一板块也在劫难逃。美国铝业(Alcoa)和美国钢铁公司(U.S. Steel Corp.)分别下跌了7.6%和13%。在市场风传政府将把大型银行收归国有之际,奥巴马政府周一试图强调对这种做法的反对立场。然而与此同时,有高官称政府恐怕不可避免地要加大对部分银行的所有权,至少暂时看来是这样。美国几家机构罕见地发表了一份联合声明,以澄清政府周三将开始的“压力测试”是何目的。该测试旨在评估美国大型银行在经济持续衰退情况下的生存能力。美国财政部(Treasury Department)联手美国联邦储备委员会(Fed, 简称:美联储)美国财政部金融局(Office of Comptroller of the Currency)等机构发表声明称,政府将保证银行拥有必要的资本和流动性,以提供恢复经济增长所必须的信贷;此外,政府还决心保证那些具有系统重要性的金融机构能够生存下去以便它们得以履行其使命。为了遏制经济滑坡,奥巴马政府的应对努力可谓多管齐下,但其成功与否似乎取决于银行系统能否很快重新站稳脚跟。虽然政府推出了价值7,870亿美元的经济刺激计划,但经济学家指出倘若信贷系统仍不能正常运转,那么任何来自财政的推动都只是昙花一现。政府已经准备好给银行系统撑腰,存款保险的规模已经扩大至前所未见的水平,政府甚至还给多家银行发行的短期债券提供了担保。银行系统疲弱不堪的现状加大了企业和消费者获得贷款的难度,也推高了借贷成本。但是拿出一份重塑社会对银行系统信心的解决方案已经成为了整个经济救助计划中最令人犯难的一部分,这部分是因为投资者无法确定政府采取的方式是否正确。奥巴马政府几周前就宣布要进行压力测试,但当时没有披露什么具体内容。周一政府几大机构发布了一份500字的联合声明,称即将开始在银行系统内进行一系列严格测试,以评估受测银行的资金实力是否可以确保它们能在经济低迷时期继续放贷。联邦政府的声明仍未说明,在衡量一家银行的健康水平时,政府会考虑哪些经济条件。不过,政府官员周一说,他们很可能会考虑一系列噩梦般的经济景象,包括房价暴跌失业率上升和经济的持续负增长。政府将要求约20家银行作出预测,如果出现这种情况,它们在汽车贷款按揭贷款和商业信贷等资产类别上将蒙受的损失。根据政府的声明,需要资金的银行可以要求财政部购买其可转换优先股,当银行需要更多的普通股时,这类股票可以转换成普通股。这种方法可能会提高银行防范损失的资本水平,不过也会增加政府在企业里的持股。声明说,银行注资计划的一大假设前提是银行应该保持私有性质。不过政府官员周一承认,政府在一些机构的持股可能会暂时增多。政府官员说,政策制定者仍在讨论政府向需要大量救助的机构注入更多资金的条件,以及在这些情况下美国政府作为投资者应保持多大的活跃程度。自政府首次公布“压力测试”计划以来,银行股受到了重挫。周一的声明仍未能解释清楚,是否所有的银行都会被接管;如果是,接管又将以怎样的形式进行。波士顿大学银行及金融法中心主任赫尔利(Cornelius Hurley)说,如果他们进行了压力测试,而企业的资本无法承受住测试,那么他们有两种选择,一种是用纳税人的钱向银行注资,另一种就是接管银行。资产管理公司Congress Asset Management Co.的投资组合经理安德森(Peter Andersen)说,信息的缺乏令交易策略陷入了混乱状态。一些相对一目了然的交易都不能保证取得预期效果。比如,交易员通常可能会认为,如果政府接管了银行,银行的股票会比它的债券表现得更糟,原因是政府接管后,股东可能会一分钱也拿不到,而债券持有人很可能会如数收回全部资金。不过最近发生的一些事让交易员对这种理论产生了怀疑。另外一家大型金融机构Washington Mutual去年秋季被摩根大通收购时,它的债券持有人没能收回资金。Credit Derivatives Research LLC的高级研究分析师克莱因(Dave Klein)说,过去人们的想法是,如果政府不得不接管一些银行的话,银行的股票会受到重挫,债券将表现不俗。不过最近,人们开始越来越担心债券持有人也会遭受严重打击。人们开始担心重组的结果是债券投资者无法拿回全部投资。过去几周,花旗集团(Citigroup)债券价格下跌。2018年到期的债券周一跌至票面价值的89%,2013年到期的债券跌至85%,收益率接近11%。2月初,很多花旗债券的价格还在票面价格的95%以上。知情人士说,花旗和联邦政府官员仍在继续谈判,很可能会以美国政府大幅增持花旗股份告终。双方很可能几天内就会达成协议,不过仍有一些没有铲除的障碍,包括政府将持有多少花旗普通股,其他大股东是否愿意把他们的优先股转换成普通股。政府官员周一说,预计压力测试要花数周的时间。不过,一些官员担心市场可能没耐心等待4月份出测试结果了。虽然财政部一直在努力不为股市波动所动,也没有打算为银行类股设置价格底线,不过,如果银行类股跌得过低,市场参与者停止了银行间的资金拆借,政府可能会被迫采取措施。Damian Paletta / Deborah Solomon / Serena Ng / Randall Smith(更新完成)相关阅读美国众监管部门:政府将坚决支持银行系统 2009-02-24跌的不只是金融股 2009-02-24美股再度暴跌 主要股指创11年新低 2009-02-24美股市场陷入信任危机 2009-02-23


Financial markets shuddered yesterday with the Dow Jones Industrial Average falling 3.4% to 7114.78 -- or nearly half the peak it hit just 16 months ago -- even as the Obama administration tried to quell fears about the viability of major U.S. banks.The decline in the stock market was unusually broad and went well beyond the jittery financial sector, with technology and other economically sensitive categories driving major market indexes to their lowest closing levels in more than 11 years.After the markets closed, J.P. Morgan Chase & Co., the biggest U.S. bank in stock market value and widely considered one of the healthiest, said it would cut its dividend 87% in a bid to preserve capital and to ensure that its 'fortress balance sheet remains intact -- even if conditions worsen significantly.' In a somewhat reassuring move, the big banking company said its first-quarter financial performance so far is 'solidly profitable.'Among investors, though, the jitters are spreading to parts of the market that had previously held up. Technology shares tumbled as analysts downgraded their forecasts for computer sales this year. International Business Machines Corp. dropped 5% and Hewlett-Packard Co. slid 6.3%. Makers of basic materials, buoyed lately by the government's stimulus package, dropped as investors fixated on the economy's woes. Alcoa Inc. declined 7.6% and U.S. Steel Corp. tumbled 13%.Amid widespread talk of nationalization of big U.S. banks, the Obama administration tried to underscore its opposition to such a move Monday. But at the same time, top officials suggested that greater government ownership of some banks may be inevitable, at least temporarily.In an unusual joint statement, several U.S. agencies tried to clarify the government's goals as they prepare to launch so-called stress tests on Wednesday, an attempt to measure the ability of large U.S. banks to survive a protracted recession.'The government will ensure that banks have the capital and liquidity they need to provide the credit necessary to restore economic growth,' the Treasury Department, Federal Reserve and other regulators said Monday. 'Moreover, we reiterate our determination to preserve the viability of systemically important financial institutions so that they are able to meet their commitments.'The Obama administration has launched a multipronged effort to arrest the economic downturn, but its success likely depends on how quickly the banking sector regains its footing. Despite a $787 billion stimulus package, economists say that any fiscal boost would only be temporary if credit markets remain dysfunctional. The government is already largely standing behind much of the banking sector, insuring unprecedented levels of deposits and even guaranteeing short-term debt issued by many banks.A weakened banking industry makes it harder and costlier for businesses and consumers to obtain loans. But coming up with a solution to restore confidence in the banking sector is proving one of the trickiest parts of the economic plan, in part because investors are unconvinced the government is taking the right approach.The Obama administration announced its plan to conduct stress tests several weeks ago but initially provided little information. On Monday, the government issued a 500-word statement that the Fed, Office of the Comptroller of the Currency and other government officials plan to begin running banks through rigorous tests to measure whether they hold enough of a cushion to continue lending during the economic downturn.The federal statement still stopped short of explaining what economic conditions they would consider to determine a bank's health. But government officials said Monday they will likely consider a series of nightmarish economic scenarios, including drastically lower housing prices, rising unemployment and continued negative growth. They will ask some 20 banks to predict losses for these events against asset classes such as auto loans, mortgages and commercial credits.According to the government's statement, firms that need capital would be allowed to turn to the Treasury for convertible preferred shares, which can convert to common shares as banks need more common equity. This could improve the bank's cushion against losses but would also boost the government's ownership stake.'The strong presumption' of their plan to pump capital into banks 'is that banks should remain in private hands,' the statement said. But administration officials acknowledged Monday that some institutions may temporarily wind up with greater government ownership.Policymakers are still discussing under what terms the government would pour more capital into institutions that need substantial assistance and how active an investor the U.S. would be in those situations, government officials say.Bank stocks have been hammered since the government first announced its stress-test plans. Monday's statement did little to resolve confusion over whether any banks would be taken over and, if they were, what form a seizure would take.'If they do a stress test and the company can't stand the stress on its capital, then they have two choices,' said Cornelius Hurley, director of the Morin Center for Banking and Financial Law at Boston University. 'One is to pump it up with taxpayers' capital, and the other is to take it over.'The lack of information has thrown trading strategies into disarray, says Peter Andersen, a portfolio manager at Congress Asset Management Co. in Boston.Some relatively straightforward trades aren't panning out. For example, traders might typically bet that a bank's shares would fare worse than its bonds in the event of a government takeover, because under a government takeover stockholders could be wiped out, while bondholders are likely to get all their money back.But recent events have cast doubt on that theory. Debt holders of another big financial institution, Washington Mutual, went unpaid when that bank was absorbed last fall by J.P. Morgan Chase.'The idea was that bank stocks would be crushed and their debt would outperform if the government had to take over some banks. But recently, fears of bondholders taking a major hit began to grow,' says Dave Klein, a senior research analyst at Credit Derivatives Research LLC. The new worry is that restructurings won't result in bond investors getting all their money back.Citigroup bonds have slumped in the past few weeks. A bond that matures in 2018 traded at 89 cents on the dollar Monday, while a bond due in 2013 fell to 85 cents, yielding nearly 11%. At the start of February, many Citigroup bonds were trading at over 95 cents.Citigroup and federal officials are continuing their talks that are likely to result in the U.S. government substantially expanding its ownership of the bank, according to people familiar with the situation. A deal is likely within days, although remaining hurdles include how much of Citigroup's common stock the government would hold and whether other large investors are willing to convert their preferred shares into common stock.The stress tests are expected to take weeks to complete, government officials said Monday. But some officials expressed concern that the markets may not be willing to wait until April for them to be finished. While Treasury has been trying not to respond to the stock-market volatility and isn't looking to put a floor under bank stocks, it could be forced to take steps if the stocks go so low that market participants halt interbank lending.Damian Paletta / Deborah Solomon / Serena Ng / Randall Smith
用文字描述下面代码的策略: import os import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from datetime import datetime, timedelta import warnings import akshare as ak warnings.filterwarnings('ignore') # 设置中文字体 from matplotlib.font_manager import FontProperties # 步骤一 font = FontProperties(fname="/System/Library/Fonts/Hiragino Sans GB.ttc", size=14) plt.rcParams['font.family'] = font.get_name() plt.rcParams['axes.unicode_minus'] = False class MultiFactorArbitrageStrategy: def __init__(self, start_date: str = '2014-01-01', end_date: str = '2024-12-31'): """ 多因子多空套利策略 Args: start_date: 回测开始日期 end_date: 回测结束日期 """ self.start_date = pd.to_datetime(start_date) self.end_date = pd.to_datetime(end_date) self.data = None self.factor_data = None self.strategy_results = None # 策略参数 self.window_size = 6 # 6个月滚动窗口 self.top_n = 20 # 选股数量 self.short_n = 20 # 做空数量 self.rebalance_freq = 'M' # 月度调仓 def get_stock_list(self, sample_size: int = 200): """获取A股股票列表""" print("正在获取A股股票列表...") try: # 获取所有A股股票 stock_list = ak.stock_info_a_code_name() # 过滤掉ST股票和退市股票 stock_list = stock_list[ ~stock_list['name'].str.contains('ST|退', na=False) ] print(f"获取到 {len(stock_list)} 只A股股票") # 随机选取指定数量的股票 if len(stock_list) > sample_size: # 设置随机种子确保结果可重复 np.random.seed(42) selected_stocks = stock_list.sample(n=sample_size, random_state=42) print(f"随机选取 {sample_size} 只股票进行回测") return selected_stocks['code'].tolist() else: print(f"股票数量不足 {sample_size},使用全部 {len(stock_list)} 只股票") return stock_list['code'].tolist() except Exception as e: print(f"获取股票列表失败: {e}") # 如果API失败,使用一些主要股票作为示例 return ['000001', '000002', '000858', '002415', '600000', '600036', '600519', '600887', '000858', '002594'] def get_stock_data(self, symbol: str, start_date: str, end_date: str) -> pd.DataFrame: """获取单只股票的历史数据""" try: # 获取日线数据 df = ak.stock_zh_a_hist( symbol=symbol, period="daily", start_date=start_date, end_date=end_date, adjust="qfq" ) if df.empty: return pd.DataFrame() # 重命名列 df = df.rename(columns={ '日期': 'trade_date', '开盘': 'open', '最高': 'high', '最低': 'low', '收盘': 'close', '成交量': 'volume', '成交额': 'amount', '振幅': 'amplitude', '涨跌幅': 'pct_change', '涨跌额': 'change', '换手率': 'turnover_rate' }) # 获取基本面数据 try: # 获取实时行情数据 real_time = ak.stock_zh_a_spot_em() stock_info = real_time[real_time['代码'] == symbol] if not stock_info.empty: # 添加基本面数据 df['pe_ratio'] = stock_info['市盈率'].iloc[0] if '市盈率' in stock_info.columns else np.nan df['pb_ratio'] = stock_info['市净率'].iloc[0] if '市净率' in stock_info.columns else np.nan df['ps_ratio'] = stock_info['市销率'].iloc[0] if '市销率' in stock_info.columns else np.nan df['market_value'] = stock_info['总市值'].iloc[0] if '总市值' in stock_info.columns else np.nan df['circ_market_value'] = stock_info['流通市值'].iloc[0] if '流通市值' in stock_info.columns else np.nan except: # 如果获取实时数据失败,使用默认值 df['pe_ratio'] = np.nan df['pb_ratio'] = np.nan df['ps_ratio'] = np.nan df['market_value'] = np.nan df['circ_market_value'] = np.nan # 转换日期格式 df['trade_date'] = pd.to_datetime(df['trade_date']) df = df.sort_values('trade_date') # 添加股票代码 df['ts_code'] = symbol return df except Exception as e: print(f"获取股票 {symbol} 数据失败: {e}") return pd.DataFrame() def load_and_preprocess_data(self, max_stocks: int = 200): """加载和预处理数据""" print("正在从akshare加载数据...") # 获取股票列表(随机选取指定数量) stock_codes = self.get_stock_list(sample_size=max_stocks) print(f"准备加载 {len(stock_codes)} 只股票的数据") all_data = [] start_date_str = self.start_date.strftime('%Y%m%d') end_date_str = self.end_date.strftime('%Y%m%d') for i, symbol in enumerate(stock_codes): if (i + 1) % 10 == 0: print(f"已处理 {i + 1}/{len(stock_codes)} 只股票") df = self.get_stock_data(symbol, start_date_str, end_date_str) if not df.empty: all_data.append(df) if not all_data: raise ValueError("没有获取到任何股票数据") # 合并所有数据 self.data = pd.concat(all_data, ignore_index=True) # 重命名列以匹配原有代码 column_mapping = { 'ts_code': 'Stkcd', 'trade_date': 'TradingMonth', 'open': 'Opnprc', 'high': 'Hiprc', 'low': 'Loprc', 'close': 'Clsprc', 'volume': 'Dnvaltrd', 'amount': 'Dnvaltrd', 'market_value': 'Dsmvtll', 'circ_market_value': 'Dsmvtll' } self.data = self.data.rename(columns=column_mapping) # 过滤时间范围 self.data = self.data[ (self.data['TradingMonth'] >= self.start_date) & (self.data['TradingMonth'] <= self.end_date) ].copy() # 确保数据按时间和股票代码排序 self.data = self.data.sort_values(['TradingMonth', 'Stkcd']).reset_index(drop=True) # 删除重复数据 self.data = self.data.drop_duplicates(subset=['TradingMonth', 'Stkcd'], keep='first') print(f"数据加载完成,共 {len(self.data)} 条记录") print(f"时间范围: {self.data['TradingMonth'].min()} 到 {self.data['TradingMonth'].max()}") print(f"股票数量: {self.data['Stkcd'].nunique()}") def calculate_technical_factors(self, group: pd.DataFrame) -> dict: """计算技术因子""" factors = {} try: # 价格相关因子 if 'Clsprc' in group.columns: close = group['Clsprc'] high = group['Hiprc'] if 'Hiprc' in group.columns else close low = group['Loprc'] if 'Loprc' in group.columns else close # 移动平均线 for window in [5, 10, 20, 60]: ma = close.rolling(window=window).mean().iloc[-1] if not pd.isna(ma): factors[f'ma{window}'] = ma # MACD exp1 = close.ewm(span=12, adjust=False).mean() exp2 = close.ewm(span=26, adjust=False).mean() macd = exp1 - exp2 signal = macd.ewm(span=9, adjust=False).mean() if not pd.isna(macd.iloc[-1]): factors['macd'] = macd.iloc[-1] if not pd.isna(signal.iloc[-1]): factors['macd_signal'] = signal.iloc[-1] # RSI delta = close.diff() gain = (delta.where(delta > 0, 0)).rolling(window=14).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean() rs = gain / loss rsi = 100 - (100 / (1 + rs.iloc[-1])) if not pd.isna(rsi): factors['rsi'] = rsi # 布林带 bb_middle = close.rolling(window=20).mean().iloc[-1] if not pd.isna(bb_middle): factors['bb_middle'] = bb_middle std = close.rolling(window=20).std().iloc[-1] if not pd.isna(std): factors['bb_upper'] = bb_middle + 2 * std factors['bb_lower'] = bb_middle - 2 * std # 收益率相关因子 returns = close.pct_change() factors['return_mean'] = returns.mean() factors['return_std'] = returns.std() factors['return_skew'] = returns.skew() factors['return_kurt'] = returns.kurtosis() # 价格位置 factors['price_position'] = (close.iloc[-1] - low.min()) / (high.max() - low.min()) # 价格动量 factors['momentum_5'] = close.iloc[-1] / close.iloc[-5] - 1 if len(close) >= 5 else 0 factors['momentum_10'] = close.iloc[-1] / close.iloc[-10] - 1 if len(close) >= 10 else 0 factors['momentum_20'] = close.iloc[-1] / close.iloc[-20] - 1 if len(close) >= 20 else 0 except Exception as e: pass return factors def calculate_market_factors(self, group: pd.DataFrame) -> dict: """计算市场因子""" factors = {} try: # 市值因子 if 'Dsmvtll' in group.columns: market_value = group['Dsmvtll'].mean() if not pd.isna(market_value): factors['market_value'] = market_value factors['log_market_value'] = np.log(market_value) # 成交量因子 if 'Dnvaltrd' in group.columns: volume = group['Dnvaltrd'] factors['volume_mean'] = volume.mean() factors['volume_std'] = volume.std() factors['volume_ratio'] = volume.iloc[-1] / volume.mean() if volume.mean() > 0 else 1 # 成交量变化率 factors['volume_change'] = volume.iloc[-1] / volume.iloc[-5] - 1 if len(volume) >= 5 else 0 # 波动率因子 if 'Clsprc' in group.columns: returns = group['Clsprc'].pct_change() factors['volatility'] = returns.std() * np.sqrt(252) # 年化波动率 # 真实波动率 if 'Hiprc' in group.columns and 'Loprc' in group.columns: high_low = group['Hiprc'] - group['Loprc'] factors['true_range'] = high_low.mean() # 换手率因子 if 'turnover_rate' in group.columns: factors['turnover_rate'] = group['turnover_rate'].mean() # 估值因子 if 'pe_ratio' in group.columns: factors['pe_ratio'] = group['pe_ratio'].mean() if 'pb_ratio' in group.columns: factors['pb_ratio'] = group['pb_ratio'].mean() if 'ps_ratio' in group.columns: factors['ps_ratio'] = group['ps_ratio'].mean() except Exception as e: pass return factors def calculate_all_factors(self): """计算所有因子""" print("正在计算因子...") results = [] grouped_data = self.data.groupby(['TradingMonth', 'Stkcd']) for (trading_month, stkcd), group in grouped_data: factor_values = { 'TradingMonth': trading_month, 'Stkcd': stkcd } # 计算技术因子和市场因子 technical_factors = self.calculate_technical_factors(group) market_factors = self.calculate_market_factors(group) factor_values.update(technical_factors) factor_values.update(market_factors) results.append(factor_values) self.factor_data = pd.DataFrame(results) # 处理缺失值 factor_cols = [col for col in self.factor_data.columns if col not in ['TradingMonth', 'Stkcd']] self.factor_data[factor_cols] = self.factor_data[factor_cols].fillna(method='ffill').fillna(method='bfill') # 标准化因子 for col in factor_cols: if self.factor_data[col].std() > 0: self.factor_data[col] = (self.factor_data[col] - self.factor_data[col].mean()) / self.factor_data[col].std() print(f"因子计算完成,共 {len(factor_cols)} 个因子") print(f"因子数据形状: {self.factor_data.shape}") def dynamic_factor_selection(self, date: pd.Timestamp, window_months: int = 6) -> list: """动态因子选择""" # 获取历史数据窗口 start_date = date - pd.DateOffset(months=window_months) historical_data = self.factor_data[ (self.factor_data['TradingMonth'] >= start_date) & (self.factor_data['TradingMonth'] < date) ] if len(historical_data) == 0: return [] # 计算因子与未来收益的相关性 factor_cols = [col for col in historical_data.columns if col not in ['TradingMonth', 'Stkcd']] # 计算未来1个月收益 historical_data = historical_data.sort_values(['Stkcd', 'TradingMonth']) historical_data['future_return'] = historical_data.groupby('Stkcd')['TradingMonth'].shift(-1) # 计算因子重要性(基于与未来收益的相关性) factor_importance = {} for factor in factor_cols: try: correlation = historical_data[factor].corr(historical_data['future_return']) if not pd.isna(correlation): factor_importance[factor] = abs(correlation) except: factor_importance[factor] = 0 # 选择最重要的因子 sorted_factors = sorted(factor_importance.items(), key=lambda x: x[1], reverse=True) selected_factors = [factor for factor, importance in sorted_factors[:20]] # 选择前20个因子 return selected_factors def calculate_stock_scores(self, date: pd.Timestamp, selected_factors: list) -> pd.DataFrame: """计算股票评分""" current_data = self.factor_data[self.factor_data['TradingMonth'] == date].copy() if len(current_data) == 0 or len(selected_factors) == 0: return pd.DataFrame() # 计算综合评分 score = 0 for factor in selected_factors: if factor in current_data.columns: # 对于某些因子,负值可能更好(如估值因子) if factor in ['pe_ratio', 'pb_ratio', 'ps_ratio', 'volatility']: score -= current_data[factor] else: score += current_data[factor] current_data['score'] = score return current_data.sort_values('score', ascending=False) def calculate_returns(self, date: pd.Timestamp, positions: dict) -> float: """计算策略收益""" next_month = date + pd.DateOffset(months=1) current_prices = self.data[self.data['TradingMonth'] == date][['Stkcd', 'Clsprc']].set_index('Stkcd')['Clsprc'] next_prices = self.data[self.data['TradingMonth'] == next_month][['Stkcd', 'Clsprc']].set_index('Stkcd')['Clsprc'] total_return = 0 for stkcd, position in positions.items(): if stkcd in current_prices.index and stkcd in next_prices.index: current_price = current_prices[stkcd] next_price = next_prices[stkcd] if current_price > 0: stock_return = (next_price - current_price) / current_price total_return += position * stock_return return total_return / len(positions) if positions else 0 def run_backtest(self): """运行回测""" print("开始回测...") # 获取所有调仓日期 rebalance_dates = pd.date_range( start=self.factor_data['TradingMonth'].min() + pd.DateOffset(months=self.window_size), end=self.factor_data['TradingMonth'].max(), freq=self.rebalance_freq ) backtest_results = [] current_positions = {} for date in rebalance_dates: # 动态因子选择 selected_factors = self.dynamic_factor_selection(date) if len(selected_factors) == 0: continue # 计算股票评分 scored_stocks = self.calculate_stock_scores(date, selected_factors) if len(scored_stocks) == 0: continue # 选择多空股票 long_stocks = scored_stocks.head(self.top_n)['Stkcd'].tolist() short_stocks = scored_stocks.tail(self.short_n)['Stkcd'].tolist() # 更新持仓 new_positions = {} for stock in long_stocks: new_positions[stock] = 1.0 / self.top_n # 等权重 for stock in short_stocks: new_positions[stock] = -1.0 / self.short_n # 等权重做空 # 计算换手率 turnover = 0 if current_positions: all_stocks = set(current_positions.keys()) | set(new_positions.keys()) for stock in all_stocks: old_pos = current_positions.get(stock, 0) new_pos = new_positions.get(stock, 0) turnover += abs(new_pos - old_pos) turnover /= 2 # 计算收益 if current_positions: monthly_return = self.calculate_returns(date, current_positions) else: monthly_return = 0 # 记录结果 backtest_results.append({ 'date': date, 'monthly_return': monthly_return, 'turnover': turnover, 'long_count': len(long_stocks), 'short_count': len(short_stocks), 'selected_factors': selected_factors[:5], # 记录前5个因子 'long_stocks': long_stocks[:10], # 记录前10只做多股票 'short_stocks': short_stocks[:10] # 记录前10只做空股票 }) current_positions = new_positions self.strategy_results = pd.DataFrame(backtest_results) # 计算累计收益 self.strategy_results['cumulative_return'] = (1 + self.strategy_results['monthly_return']).cumprod() print(f"回测完成,共 {len(self.strategy_results)} 个调仓周期") def calculate_performance_metrics(self) -> dict: """计算策略绩效指标""" if self.strategy_results is None or len(self.strategy_results) == 0: return {} returns = self.strategy_results['monthly_return'].values cumulative_returns = self.strategy_results['cumulative_return'].values # 年化收益率 total_return = cumulative_returns[-1] - 1 years = len(returns) / 12 annual_return = (1 + total_return) ** (1 / years) - 1 # 年化波动率 annual_volatility = np.std(returns) * np.sqrt(12) # 夏普比率 risk_free_rate = 0.03 # 假设无风险利率为3% sharpe_ratio = (annual_return - risk_free_rate) / annual_volatility if annual_volatility > 0 else 0 # 最大回撤 peak = np.maximum.accumulate(cumulative_returns) drawdown = (cumulative_returns - peak) / peak max_drawdown = np.min(drawdown) # 胜率 win_rate = np.sum(returns > 0) / len(returns) # 平均换手率 avg_turnover = self.strategy_results['turnover'].mean() # 卡玛比率 calmar_ratio = annual_return / abs(max_drawdown) if max_drawdown != 0 else 0 metrics = { 'annual_return': annual_return, 'annual_volatility': annual_volatility, 'sharpe_ratio': sharpe_ratio, 'max_drawdown': max_drawdown, 'win_rate': win_rate, 'avg_turnover': avg_turnover, 'calmar_ratio': calmar_ratio, 'total_return': total_return, 'total_months': len(returns) } return metrics def plot_performance(self): """绘制策略绩效图表""" if self.strategy_results is None: print("没有回测结果可供绘图") return # 创建子图 fig, axes = plt.subplots(2, 2, figsize=(15, 12)) fig.suptitle('多因子多空套利策略回测结果 (基于AKShare数据)', fontsize=16, fontweight='bold') # 1. 累计收益率 axes[0, 0].plot(self.strategy_results['date'], self.strategy_results['cumulative_return'], linewidth=2, color='blue', label='策略累计收益') axes[0, 0].axhline(y=1, color='red', linestyle='--', alpha=0.5, label='基准线') axes[0, 0].set_title('累计收益率') axes[0, 0].set_xlabel('日期') axes[0, 0].set_ylabel('累计收益率') axes[0, 0].legend() axes[0, 0].grid(True, alpha=0.3) # 2. 月度收益率分布 axes[0, 1].hist(self.strategy_results['monthly_return'], bins=30, alpha=0.7, color='green', edgecolor='black') axes[0, 1].axvline(x=0, color='red', linestyle='--', alpha=0.7, label='零收益线') axes[0, 1].set_title('月度收益率分布') axes[0, 1].set_xlabel('月度收益率') axes[0, 1].set_ylabel('频次') axes[0, 1].legend() axes[0, 1].grid(True, alpha=0.3) # 3. 回撤曲线 cumulative_returns = self.strategy_results['cumulative_return'].values peak = np.maximum.accumulate(cumulative_returns) drawdown = (cumulative_returns - peak) / peak axes[1, 0].fill_between(self.strategy_results['date'], drawdown, 0, alpha=0.3, color='red', label='回撤') axes[1, 0].plot(self.strategy_results['date'], drawdown, color='red', linewidth=1) axes[1, 0].set_title('回撤曲线') axes[1, 0].set_xlabel('日期') axes[1, 0].set_ylabel('回撤率') axes[1, 0].legend() axes[1, 0].grid(True, alpha=0.3) # 4. 换手率 axes[1, 1].plot(self.strategy_results['date'], self.strategy_results['turnover'], linewidth=1, color='orange', label='换手率') axes[1, 1].axhline(y=self.strategy_results['turnover'].mean(), color='red', linestyle='--', alpha=0.7, label=f'平均换手率: {self.strategy_results["turnover"].mean():.2%}') axes[1, 1].set_title('换手率变化') axes[1, 1].set_xlabel('日期') axes[1, 1].set_ylabel('换手率') axes[1, 1].legend() axes[1, 1].grid(True, alpha=0.3) plt.tight_layout() plt.savefig('策略绩效图_akshare.png', dpi=300, bbox_inches='tight') plt.show() # 绘制持仓情况 self.plot_holdings() def plot_holdings(self): """绘制持仓情况""" if self.strategy_results is None: return # 统计持仓股票 all_long_stocks = [] all_short_stocks = [] for _, row in self.strategy_results.iterrows(): all_long_stocks.extend(row['long_stocks']) all_short_stocks.extend(row['short_stocks']) # 统计股票出现频次 long_freq = pd.Series(all_long_stocks).value_counts().head(20) short_freq = pd.Series(all_short_stocks).value_counts().head(20) # 绘制持仓频次图 fig, axes = plt.subplots(1, 2, figsize=(16, 8)) fig.suptitle('策略持仓股票频次统计 (基于AKShare数据)', fontsize=16, fontweight='bold') # 做多股票频次 axes[0].barh(range(len(long_freq)), long_freq.values, color='green', alpha=0.7) axes[0].set_yticks(range(len(long_freq))) axes[0].set_yticklabels(long_freq.index) axes[0].set_title('做多股票频次 (Top 20)') axes[0].set_xlabel('出现频次') axes[0].invert_yaxis() # 做空股票频次 axes[1].barh(range(len(short_freq)), short_freq.values, color='red', alpha=0.7) axes[1].set_yticks(range(len(short_freq))) axes[1].set_yticklabels(short_freq.index) axes[1].set_title('做空股票频次 (Top 20)') axes[1].set_xlabel('出现频次') axes[1].invert_yaxis() plt.tight_layout() plt.savefig('持仓情况图_akshare.png', dpi=300, bbox_inches='tight') plt.show() def print_performance_summary(self): """打印策略绩效摘要""" metrics = self.calculate_performance_metrics() if not metrics: print("无法计算绩效指标") return print("\n" + "="*60) print("多因子多空套利策略绩效摘要 (基于AKShare数据)") print("="*60) print(f"回测期间: {self.start_date.strftime('%Y-%m-%d')} 至 {self.end_date.strftime('%Y-%m-%d')}") print(f"总调仓次数: {metrics['total_months']}") print(f"总收益率: {metrics['total_return']:.2%}") print(f"年化收益率: {metrics['annual_return']:.2%}") print(f"年化波动率: {metrics['annual_volatility']:.2%}") print(f"夏普比率: {metrics['sharpe_ratio']:.3f}") print(f"最大回撤: {metrics['max_drawdown']:.2%}") print(f"胜率: {metrics['win_rate']:.2%}") print(f"平均换手率: {metrics['avg_turnover']:.2%}") print(f"卡玛比率: {metrics['calmar_ratio']:.3f}") print("="*60) # 打印最新持仓情况 if len(self.strategy_results) > 0: latest_result = self.strategy_results.iloc[-1] print(f"\n最新调仓日期: {latest_result['date'].strftime('%Y-%m-%d')}") print(f"做多股票数量: {latest_result['long_count']}") print(f"做空股票数量: {latest_result['short_count']}") print(f"主要因子: {latest_result['selected_factors']}") print(f"做多股票: {latest_result['long_stocks'][:5]}...") print(f"做空股票: {latest_result['short_stocks'][:5]}...") def run_strategy(self): """运行完整策略""" print("开始运行多因子多空套利策略 (基于AKShare数据)...") # 1. 加载数据 self.load_and_preprocess_data(max_stocks=500) # 从5244只股票中随机选取200只 # 2. 计算因子 self.calculate_all_factors() # 3. 运行回测 self.run_backtest() # 4. 计算绩效指标 metrics = self.calculate_performance_metrics() # 5. 打印结果 self.print_performance_summary() # 6. 绘制图表 self.plot_performance() print("策略运行完成!") def main(): """主函数""" # 创建策略实例 strategy = MultiFactorArbitrageStrategy( start_date='2014-01-01', # 缩短回测期间避免API限制 end_date='2024-12-31' ) # 运行策略 strategy.run_strategy() if __name__ == "__main__": main()
07-31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值