序列化内幕交易
登录后收藏学术论文
内幕交易模式 [点击查看论文]
- Auburn University
- Miami University
- ?Harbert College of Business, Auburn University
- ?Miami University of Ohio - Department of Finance
- University of Kansas
- ?University of Kansas - School of Business
策略概要
该策略针对纽约证券交易所、纳斯达克和美国证券交易所的股票,分析内幕交易。每月,内幕交易会被汇总并分类为卖出或买入,不包括“常规”交易者(连续三年在同一月份交易的交易者)。该策略重点关注高管(例如,首席执行官)的交易。内幕交易序列在最后一笔交易后两个月结束,并在添加股票到投资组合之前等待额外一个月进行确认。股票持有一个月,投资组合每月根据新完成的内幕交易序列进行重新平衡,利用内幕信号获取潜在的市场洞察。
II. 策略合理性
研究表明,内部人士掌握有关公司未来前景的非公开信息,从而影响他们的交易时机。内部人士的优势可能会持续较长时间,交易序列反映了逐渐影响股价的私人信息。此类信息需要更长时间才能纳入市场价格,这表明异常回报有利于内部人士。然而,这些回报通常仅在交易序列结束后才能实现,这突显了市场对内部人士驱动的、在较长时间范围内传播的私人信号的延迟反应。
回测表现
波动率14.26%
夏普比率1.55
索提诺比率-0.011
胜率59%
完整 Python 代码
from AlgorithmImports import *
from pandas.core.frame import DataFrame
from typing import List, Dict
import pandas as pd
from dateutil.relativedelta import relativedelta
#endregion
class SequencedInsiderTrading(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2014, 1, 1)
self.SetCash(100000)
self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
market: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.insider_data: Dict[Symbol, QuiverInsiderTrading] = {}
self.routine_traders_stocks: List[str] = []
self.leverage: int = 3
self.min_consecutive_month_count: int = 2
self.fundamental_count: int = 500
self.fundamental_sorting_key = lambda x: x.DollarVolume
self.selection_flag: bool = False
self.settings.daily_precise_end_time = False
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Schedule.On(self.DateRules.MonthStart(market), self.TimeRules.AfterMarketOpen(market), self.Selection)
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(self.leverage)
symbol: Symbol = security.Symbol
dataset_symbol: Symbol = self.AddData(QuiverInsiderTrading, symbol).Symbol
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
# monthly selection
if not self.selection_flag:
return Universe.Unchanged
selected: List[Fundamental] = [
x for x in fundamental if x.HasFundamentalData and x.MarketCap != 0 and \
x.SecurityReference.ExchangeId in self.exchange_codes
]
if len(selected) > self.fundamental_count:
selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
selected: Dict[str, Symbol] = {x.Symbol.Value: x.Symbol for x in selected}
aggregated_data: Dict[Tuple[datetime.date, str], float] = {}
curr_month: int = self.Time
insiders: Dict[str, List[Tuple[datetime.date, str, float]]] = {}
# aggregate data on monthly format
for name, data in self.insider_data.items():
if name not in insiders:
insiders[name] = []
for data_point in data:
date: str = data_point[0].strftime('%m-%Y')
key = (date, data_point[1])
if key in aggregated_data:
if data_point[2] is not None:
aggregated_data[key] += data_point[2]
else:
aggregated_data[key] = data_point[2]
insiders[name].append([(key[0], key[1], shares) for key, shares in aggregated_data.items()])
aggregated_data.clear()
# check for routine insider trades
months_to_check: List[str] = [(curr_month - relativedelta(months=i)).strftime('%m-%Y') for i in [1, 2, 13, 14, 25, 26, 37, 38]]
for name, data in insiders.items():
ticker: str = data[0][0][1]
if len(data[0]) > self.min_consecutive_month_count:
if (data[0][-1][0] == months_to_check[0] and data[0][-2][0] == months_to_check[1] and \
all(i[0] != month for month in months_to_check[2:] for i in data[0])) and \
ticker in selected:
self.routine_traders_stocks.append(selected[ticker])
return [x for x in selected.values()]
def OnData(self, data: Slice) -> None:
for insider_trades in data.Get(QuiverInsiderTrading).values():
for insider_trade in insider_trades:
if insider_trade.Name not in self.insider_data:
self.insider_data[insider_trade.Name] = []
self.insider_data[insider_trade.Name].append((insider_trade.Time, insider_trade.Symbol.Value, insider_trade.Shares))
# monthly rebalance
if not self.selection_flag:
return
self.selection_flag = False
targets: List[PortfolioTarget] = []
for symbol in self.routine_traders_stocks:
if symbol in data and data[symbol]:
targets.append(PortfolioTarget(symbol, 1 / len(self.routine_traders_stocks)))
self.SetHoldings(targets, True)
self.routine_traders_stocks.clear()
def Selection(self) -> None:
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
fee: float = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))