本文叙说关于动量战略的一个实例。动量战略是最著名的定量长短期股票战略之一。自从Jegadeesh和Titman(1993)首次提出这个概念appstore以来APP,它已广泛出现在学术研究和出售方面的著作中。出资者在动量战略中信赖,个股中,曾经的赢家将跨越曾经的输家。

最常用的动量要素是股票除去最近一个月在曾经12个月的收益。 在学术出版物中,动量战略通常是一个月调整一次且持有期也是一个月。在本例中,咱们每天从头平衡咱们的出资组合的1 / 21,并持有新份额21天app装置下载。为简略起见,咱们不考虑买卖成本。

进程 1:加载股票买卖数据,对数据进行清洗和挑选,然后为每个公司股票构建一个曾经12个月疏忽最近一个月的动量信号。

def load_price_data(df):
USstocks = df[df.date.dt.weekday.between(0, 4), df.PRC.notnull(), df.VOL.notnull()][
['PERMNO', 'date', 'PRC', 'VOL', 'appstoreRET', 'SHapp装置下载ROUT']
].sort_values(appearby=['PERMNO', 'date'])
UappstoreSstocks['PRC'] = USstocks.PRC.abs()
USstocks[appstore'MV'] = USstocks.SHROUT * USstocks.PRC
USstocks['cumretIndex'] = (USstocks + 1)['RET'].groupby('PERMNO', lazy=True).cumprod()
USstocks['signal'] = (USstocks.shift(21) / UappreciateSstocks.shift(252) - 1).groupby(
'PERMNO', lazy=TrAPPue)['cumretIndex'].transform()
rappointmenteturn USstocks
df = orca.reaapp装置下载d_csv('C:/DolphinDB/Orca/databases/USstocks.csv')
price_data = load_price_data(APPdappleidf)

留神:以上代码运用了Orca的两个扩展功用。

  1. Orca支撑在条件过滤语句中运用逗号替代&,在某些场景下会更高效,拜见教程。
  2. Orca的groupby函数供给了lazy参数,协作transform函数运用可以结束DolphinDBappear的contextAPP by功用,拜见教程。

进程 2:为动量appreciate战略生成出资组合。

首要,挑选满足以下条件的流通股:动量信application号值无缺失、appearance当日是正买卖量、市值跨越1APP亿美金、以及每application股价格跨越5美元。

def gen_trappearade_tables(df):
USstocks = df[(df.PRC > 5), (df.MV > 100000), (df.VOL > 0), (df.signal.noapp装置下载tnull())]
USstocks = USstocks[['date', 'PERMNO', 'MV', 'signal']].sort_values(by='date')
returnapp装置下载 USstocks
tradaapplicationbles = gen_trade_tables(price_data)

其次,依据动量信号,拟定10组可买卖股票。只保存2个最极点的集体(赢家apple和输家)。假定咱们总是想在21天内,每app装置下载天多头1美元和空头1,所以咱们每天在赢家组多头1,appleid所以咱们每天在赢家组多头1/21,在输家组每天空头$1/21。在每组中,咱们可以运用等权重或值权重, 来核算出资appstore组合构成日期appleid上每个股票的权重。

def form_portfolio(start_date, end_date, tradables,approach holding_days, groups, wt_schemapplicatione):
ports = tradables[tradables.date.between(start_date, end_date)].groupby('date').filter('count(PERMNO) >= 100')
ports['rank'] = ports.groupby('date')['signalappreciate'].transfoapplerm('rank{{,true,{groups}}}'.format(grouappstoreps=grouappreciateps))
ports['wt'] = 0.0
ports_rank_eAPPq_0 = (ports['rank'] == 0)
ports_ranapproachk_eq_groups_sub_1 = (ports['rank'] == groups-1)
if wt_scheme == 1:
ports.loc[ports_rank_eq_0, 'wt']appointment = 
pappstoreorts[ports_rank_eq_0].groupby(['dappstoreate'])['PERMNO'].transform(
r'(Papp装置下载ERMNO->-1count(PERMNO){holding_days})'.format(holding_days=holding_days)
)
ports.loc[ports_rank_eq_groups_sappstoreub_1, 'wt'] = 
ports[ports_rank_eq_groups_sub_1].groupby(['date'])['PERMNOappearance'].transform(
r'APP(PERMNO->1count(PERMNO)approach{hoapplicationlding_days})'appear.format(holding_days=holding_days)
)
elif wt_scheme == 2:
ports.loc[ports_rank_eq_0, 'wt'] = 
ports[ports_rank_eq_0].appeargrouAPPpby(['date'])['MV'].transfoapplerapproachm(
r'(MV->-MVsum(MV){holding_days})'.appearanceformatapplication(holding_days=holding_days)
)
pappearanceorts.loc[ports_rank_eq_groups_sub_1, 'wt'] = 
ports[ports_raapplicationnappointmentk_eq_groups_sub_1].groupbyappreciate(['date'])['MV'].transappearform(
r'(MV->MVsum(MV){holding_days})'.forapplicationmat(holding_days=holding_daAPPys)
)
ports = ports.loc[ports.wt != 0, ['PERMNO', 'datapproache', 'wt']].sort_vaapplicationlues(by=['PERMNO', 'date'])
ports.rename(columns={'date':approach 'tranche'}, inplace=True)
return ports
start_date,app装置下载 end_date = orca.Timestamp("1996.01.01"), orca.Timestamp("2017.01.01")
holding_days = 5
groups = 10
ports = form_portfolio(start_date, end_date, tradables, holding_days, groups, 2)
daily_rtappreciaten = priceapplication_data.loc[price_data.datappeare.applebetween(stapp装置下载art_date, end_date), ['date', 'PERMNOappear', 'RET']]

**留神:**以上代码运用了Orca的扩展功用,即,容许filter, transform等高阶函数,承受一个表明DolphinDB函数或脚本的字符串。拜见教程。

进程3:核算咱们的出资组合中的每支股票随后的21天获利/丢掉。出资组合构成日后21天封闭股票。

def calc_stock_papproachnl(ports,appear daily_rtn, holding_days, end_dapp装置下载ate, last_days):
dates = ports[['trappreciateanche']].drop_duplicates().sort_values(by='tranche')
dateAPPs_after_ages = orca.DataappearFrame()
for age in range(1, holding_days+1):
dates_afterappointment_age_iappreciate = dates.copyAPP()
dates_after_agAPPe_i['ageappointment'] = age
dates_after_age_i['date_after_age'] = dates_after_age_i['tranche'].shift(-age)
dates_after_ages.append(dates_after_appstoreage_i, inplace=True)
pos = ports.merge(datapproaches_after_ages, on='tranche')
pos = pos.joinapplication(last_days, on='PERMNO')
pos = pos.loc[(pos.date_afappleidter_APPage.notnull() & (pos.date_after_age <= pos.last_day.clip(upper=end_date))),
['date_after_age', 'PERMNO', 'tranche', 'age', 'wt']]
pos = pos.compute()
pos.rename(columns={'date_after_age': 'appstoredate', 'wt': 'expr'}, inplace=True)
pos['ret'] = 0.0
pos['pnl'] = 0.0
# use setappointment_index to make it easy to equal join two Frames
daily_rtn.set_index([appleid'dateAPP', 'PERMNO'], inplace=True)
posappreciate.set_index(['date', 'PERMNO'], iappearancenplace=True)
pos['reappointmentt'] = daappreciateily_rtn['RET']
pos.reset_index(inplace=True)
pos['expr'] = (pos.expr * (1 + pos.ret).cumprod()).groupby(
['PERMNO', 'tranche'], lazy=True).transform()
pos['pnl'approach] = pos.applicationexpr * pos.ret / (1 + pos.ret)
return pos
last_days = price_data.groupby('PERMappleNO')['date'].max()
last_days.renappointmentame("last_day", inplace=True)
stock_pnl = calc_stockapproach_pnl(ports, daily_rtn, holdiappreciateng_days, end_date, last_days)

留神:以上代码有一句pos.compute()语句,将一个中心表达式(带有条app装置下载件过滤的approachDataFrame)的效果直接核算出来。因为咱们只需要对APP过滤往后application的效果进行赋值操作。

此外appear,以上代码对两个DataFrame调用了set_index函数,然后将一个DataFrame的列赋值给另一个,这类似于将两个DataFrame按索引列进行left join,然后将效果中的对应列赋值。如果直接实施脚本pos['ret'] = pos.merge(daily_rtn, on=['date', 'PERMappleidNO'])['RET'],则会在核算appstore时进行一次join,赋值时又进行一次join,带来不必要的核算量。

进程 4:核算出资组合的获利/丢掉,并绘制跟着时间推移的动量战略的累积酬谢。

port_pnl = stock_pnl.APPgroupby('date')['pnl'].sum()
cumulative_return = port_pnl.cumsum()
cumulative_return.plot()
plt.show()

留神: plot函数会将整个DataFrame所对应application的DolphAPPinDB表下载到客户端,然后对齐绘图。在运用时应当留神数据量,避免许多的网络传输带来的功能问题。拜见appointment教程。

点此检查完好的代码。关于如何用DolphinDB database脚本结束这个战略,请参阅官方榜样。