def init(self): df_future_histvol = self.df_f_c1.copy() df_future_histvol['amt_hv'] = histvol.hist_vol( df_future_histvol[c.Util.AMT_CLOSE], n=self.n_hv) self.dt_start = max(self.df_f_c1[c.Util.DT_DATE].values[0], self.df_metrics[c.Util.DT_DATE].values[0]) self.end_date = min(self.df_f_c1[c.Util.DT_DATE].values[-1], self.df_metrics[c.Util.DT_DATE].values[-1]) self.df_metrics = self.df_metrics[ self.df_metrics[c.Util.DT_DATE] >= self.dt_start].reset_index( drop=True) self.df_f_c1 = self.df_f_c1[ self.df_f_c1[c.Util.DT_DATE] >= self.dt_start].reset_index( drop=True) self.df_f_all = self.df_f_all[ self.df_f_all[c.Util.DT_DATE] >= self.dt_start].reset_index( drop=True) self.df_vol = pd.merge(self.df_vol, df_future_histvol[[c.Util.DT_DATE, 'amt_hv']], on=c.Util.DT_DATE).set_index(c.Util.DT_DATE) self.optionset = BaseOptionSet(self.df_metrics) self.optionset.init() self.hedging = SytheticOption(self.df_f_c1, rf=self.rf, df_futures_all_daily=self.df_f_all) self.hedging.init() self.hedging.amt_option = 1 / 1000 # 50ETF与IH点数之比 self.account = BaseAccount(init_fund=c.Util.BILLION, rf=self.rf) self.prepare_timing()
def __init__(self, start_date, end_date): self.ttm = 30 self.buywrite = BuyWrite.BUY self.fund = Util.BILLION self.invest_underlying_ratio = 0.7 # self.invest_underlying_ratio = 1 self.slippage = 2 self.start_date = start_date self.end_date = end_date hist_date = start_date - datetime.timedelta(days=40) df_future_c1 = get_dzqh_cf_c1_minute(start_date, end_date, 'if') df_future_c1_daily = get_dzqh_cf_c1_daily(hist_date, end_date, 'if') df_futures_all_daily = get_dzqh_cf_daily( start_date, end_date, 'if') # daily data of all future contracts df_index = get_index_mktdata( start_date, end_date, 'index_300sh') # daily data of underlying index df_index = df_index[df_index[Util.DT_DATE].isin( Util.DZQH_CF_DATA_MISSING_DATES) == False].reset_index(drop=True) # df_index.to_csv('df_index.csv') self.trade_dates = sorted(df_future_c1_daily[Util.DT_DATE].unique()) self.df_vol_1m = Histvol.hist_vol(df_future_c1_daily) # df_parkinson_1m = Histvol.parkinson_number(df_future_c1_daily) self.df_garman_klass = Histvol.garman_klass(df_future_c1_daily) # df_hist_vol = self.df_vol_1m.join(self.df_garman_klass, how='left') # df_hist_vol.to_csv('../../data/df_hist_vol.csv') self.underlying = BaseInstrument(df_data=df_index) self.underlying.init() self.synthetic_option = SytheticOption( df_c1_data=df_future_c1, # df_c1_daily=df_future_c1_daily, df_futures_all_daily=df_futures_all_daily, df_index_daily=df_index) self.synthetic_option.init() self.account = BaseAccount(self.fund, leverage=20.0, rf=0.0) self.trading_desk = Trade() self.init_spot = self.synthetic_option.underlying_state_daily[ Util.AMT_CLOSE] self.df_analysis = pd.DataFrame()
""" Volatility Strategy: Straddle """ d1 = df_future_c1_daily[c.Util.DT_DATE].values[0] d2 = df_metrics[c.Util.DT_DATE].values[0] d = max(d1, d2) print(d1, d2, d) df_metrics = df_metrics[df_metrics[c.Util.DT_DATE] >= d].reset_index(drop=True) df_c1 = df_future_c1_daily[ df_future_c1_daily[c.Util.DT_DATE] >= d].reset_index(drop=True) optionset = BaseOptionSet(df_metrics) optionset.init() d1 = optionset.eval_date df_c1 = df_c1.rename(columns={c.Util.ID_INSTRUMENT: 'id_future'}) df_c1.loc[:, c.Util.ID_INSTRUMENT] = name_code hedging = SytheticOption(df_c1, frequency=c.FrequentType.DAILY) hedging.init() account = BaseAccount(init_fund=c.Util.BILLION, leverage=1.0, rf=0.03) maturity1 = optionset.select_maturity_date(nbr_maturity=0, min_holding=15) empty_position = True unit_p = None unit_c = None atm_strike = None buy_write = c.BuyWrite.WRITE print(optionset.eval_date, hedging.eval_date) while optionset.eval_date <= end_date: if account.cash <= 0: break if maturity1 > end_date: # Final close out all. close_out_orders = account.creat_close_out_order() for order in close_out_orders:
""" Volatility Strategy: Straddle """ d1 = df_future_c1_daily[c.Util.DT_DATE].values[0] d2 = df_metrics[c.Util.DT_DATE].values[0] d = max(d1, d2) print(d1, d2, d) df_metrics = df_metrics[df_metrics[c.Util.DT_DATE] >= d].reset_index(drop=True) df_c1 = df_future_c1_daily[ df_future_c1_daily[c.Util.DT_DATE] >= d].reset_index(drop=True) df_c_all = df_futures_all_daily[ df_futures_all_daily[c.Util.DT_DATE] >= d].reset_index(drop=True) optionset = BaseOptionSet(df_metrics) optionset.init() d1 = optionset.eval_date hedging = SytheticOption(df_c1, frequency=c.FrequentType.DAILY, df_c1_daily=df_c1, df_futures_all_daily=df_c_all) hedging.init() print(optionset.eval_date, hedging.eval_date) account = BaseAccount(init_fund=c.Util.BILLION, leverage=1.0, rf=0.03) empty_position = True unit_p = None unit_c = None atm_strike = None buy_write = c.BuyWrite.WRITE maturity1 = optionset.select_maturity_date(nbr_maturity=0, min_holding=15) id_future = hedging.current_state[c.Util.ID_FUTURE] idx_hedge = 0 flag_hedge = False
df_futures_all_daily = get_dzqh_cf_daily( start_date, end_date, 'if') # daily data of all future contracts df_index = get_index_mktdata(start_date, end_date, 'index_300sh') # daily data of underlying index df_index = df_index[df_index[Util.DT_DATE].isin( Util.DZQH_CF_DATA_MISSING_DATES) == False].reset_index(drop=True) df_vol_1m = Histvol.hist_vol(df_future_c1_daily) # df_parkinson_1m = Histvol.parkinson_number(df_future_c1_daily) # df_garman_klass = Histvol.garman_klass(df_future_c1_daily) # df_hist_vol = df_vol_1m.join(df_parkinson_1m, how='left') # df_hist_vol = df_hist_vol.join(df_garman_klass, how='left') df_future_c1_daily = df_future_c1_daily[ df_future_c1_daily[Util.DT_DATE] >= start_date].reset_index(drop=True) synthetic_option = SytheticOption(df_c1_minute=df_future_c1, df_c1_daily=df_future_c1_daily, df_futures_all_daily=df_futures_all_daily, df_index_daily=df_index) synthetic_option.init() underlying = BaseInstrument(df_data=df_index) underlying.init() account = BaseAccount(2 * Util.BILLION, leverage=10.0, rf=0.0) trading_desk = Trade() ##################################################################### # """ Init position """ strike = synthetic_option.underlying_index_state_daily[Util.AMT_CLOSE] dt_maturity = synthetic_option.eval_date + datetime.timedelta(days=30) vol = 0.2 Option = EuropeanOption(strike, dt_maturity, OptionType.PUT) delta = synthetic_option.get_black_delta(Option, vol) id_future = synthetic_option.current_state[Util.ID_INSTRUMENT]
class SyntheticOptionHedgedPortfolio(): def __init__(self, start_date, end_date): self.ttm = 30 self.buywrite = BuyWrite.BUY self.fund = Util.BILLION self.invest_underlying_ratio = 0.7 # self.invest_underlying_ratio = 1 self.slippage = 2 self.start_date = start_date self.end_date = end_date hist_date = start_date - datetime.timedelta(days=40) df_future_c1 = get_dzqh_cf_c1_minute(start_date, end_date, 'if') df_future_c1_daily = get_dzqh_cf_c1_daily(hist_date, end_date, 'if') df_futures_all_daily = get_dzqh_cf_daily( start_date, end_date, 'if') # daily data of all future contracts df_index = get_index_mktdata( start_date, end_date, 'index_300sh') # daily data of underlying index df_index = df_index[df_index[Util.DT_DATE].isin( Util.DZQH_CF_DATA_MISSING_DATES) == False].reset_index(drop=True) # df_index.to_csv('df_index.csv') self.trade_dates = sorted(df_future_c1_daily[Util.DT_DATE].unique()) self.df_vol_1m = Histvol.hist_vol(df_future_c1_daily) # df_parkinson_1m = Histvol.parkinson_number(df_future_c1_daily) self.df_garman_klass = Histvol.garman_klass(df_future_c1_daily) # df_hist_vol = self.df_vol_1m.join(self.df_garman_klass, how='left') # df_hist_vol.to_csv('../../data/df_hist_vol.csv') self.underlying = BaseInstrument(df_data=df_index) self.underlying.init() self.synthetic_option = SytheticOption( df_c1_data=df_future_c1, # df_c1_daily=df_future_c1_daily, df_futures_all_daily=df_futures_all_daily, df_index_daily=df_index) self.synthetic_option.init() self.account = BaseAccount(self.fund, leverage=20.0, rf=0.0) self.trading_desk = Trade() self.init_spot = self.synthetic_option.underlying_state_daily[ Util.AMT_CLOSE] self.df_analysis = pd.DataFrame() def next(self): if self.synthetic_option.is_last_minute(): self.underlying.next() self.synthetic_option.next() def init_portfolio(self, fund): """ Init position """ dt_maturity = self.synthetic_option.eval_date + datetime.timedelta( days=self.ttm) # strike = self.underlying.mktprice_close() strike = self.synthetic_option.mktprice_close() # dt_maturity = self.synthetic_option.eval_date + datetime.timedelta(days=30) self.Option = EuropeanOption(strike, dt_maturity, OptionType.PUT) # self.Option = EuropeanOption(strike, dt_maturity, OptionType.CALL) """ 用第一天的日收盘价开仓标的现货多头头寸 """ underlying_unit = np.floor(fund * self.invest_underlying_ratio / self.underlying.mktprice_close()) order_underlying = self.account.create_trade_order( self.underlying, LongShort.LONG, underlying_unit) execution_record = self.underlying.execute_order( order_underlying, slippage=0, execute_type=ExecuteType.EXECUTE_ALL_UNITS) self.account.add_record(execution_record, self.underlying) self.synthetic_option.amt_option = underlying_unit """ 用第一天的成交量加权均价开仓/调整复制期权头寸 """ vol = self.get_vol() self.delta = self.synthetic_option.get_black_delta(self.Option, vol) synthetic_unit = self.synthetic_option.get_synthetic_unit( self.delta, self.buywrite) self.synthetic_option.synthetic_unit = synthetic_unit if synthetic_unit > 0: long_short = LongShort.LONG else: long_short = LongShort.SHORT order = self.account.create_trade_order(self.synthetic_option, long_short, synthetic_unit) execution_record = self.synthetic_option.execute_order_by_VWAP( order, slippage=0, execute_type=ExecuteType.EXECUTE_ALL_UNITS) self.account.add_record(execution_record, self.synthetic_option) self.account.daily_accounting(self.synthetic_option.eval_date) self.add_additional_to_account() self.disp() self.next() def rebalance_sythetic_option(self): """ Reset and Rebalance sythetic option """ dt_maturity = self.synthetic_option.eval_date + datetime.timedelta( days=self.ttm) # strike = self.underlying.mktprice_close() strike = self.synthetic_option.mktprice_close() self.Option = EuropeanOption(strike, dt_maturity, OptionType.PUT) # self.Option = EuropeanOption(strike, dt_maturity, OptionType.CALL) """ 用成交量加权均价调整复制期权头寸 """ vol = self.get_vol() self.delta = self.synthetic_option.get_black_delta(self.Option, vol) synthetic_unit = self.synthetic_option.get_rebalancing_unit( self.delta, self.Option, vol, self.synthetic_option.mktprice_close(), DeltaBound.NONE, self.buywrite) self.synthetic_option.synthetic_unit += synthetic_unit if synthetic_unit > 0: long_short = LongShort.LONG else: long_short = LongShort.SHORT order = self.account.create_trade_order(self.synthetic_option, long_short, synthetic_unit) execution_record = self.synthetic_option.execute_order_by_VWAP( order, slippage=0, execute_type=ExecuteType.EXECUTE_ALL_UNITS) self.account.add_record(execution_record, self.synthetic_option) self.account.daily_accounting(self.synthetic_option.eval_date) self.add_additional_to_account() self.disp() self.next() return def hedge(self, dt_end=None): id_future = self.synthetic_option.current_state[Util.ID_INSTRUMENT] if dt_end is None: dt_end = self.Option.dt_maturity dt_time_end = datetime.datetime(dt_end.year, dt_end.month, dt_end.day, 15, 00, 0) while self.synthetic_option.has_next( ) and self.synthetic_option.eval_datetime <= dt_time_end: if id_future != self.synthetic_option.current_state[ Util.ID_INSTRUMENT]: long_short = self.account.trade_book.loc[id_future, Util.TRADE_LONG_SHORT] hold_unit = -self.account.trade_book.loc[id_future, Util.TRADE_UNIT] spot = self.synthetic_option.mktprice_close() vol = self.get_vol() self.delta = self.synthetic_option.get_black_delta( self.Option, vol, spot) synthetic_unit = self.synthetic_option.get_synthetic_unit( self.delta, self.buywrite) # 按照移仓换月日的收盘价计算Delta id_c2 = self.synthetic_option.current_state[Util.ID_INSTRUMENT] open_unit = synthetic_unit self.synthetic_option.synthetic_unit += open_unit - hold_unit close_execution_record, open_execution_record \ = self.synthetic_option.shift_contract_by_VWAP(id_c1=id_future, id_c2=id_c2, hold_unit=hold_unit, open_unit=open_unit, long_short=long_short, slippage=self.slippage, execute_type=ExecuteType.EXECUTE_ALL_UNITS ) self.account.add_record(close_execution_record, self.synthetic_option) self.synthetic_option._id_instrument = id_c2 self.account.add_record(open_execution_record, self.synthetic_option) """ 更新当前持仓头寸 """ """ USE SAME UNIT TO SHIFT CONTRACT AND USE CLOSE PRICE TO REBALANCING DELTA CHANGE. """ print(' Relancing after shift contract, ', self.synthetic_option.eval_date) id_future = id_c2 if self.synthetic_option.eval_datetime.time() == datetime.time( 9, 30, 0): self.next() if self.synthetic_option.eval_date == self.synthetic_option.get_next_state_date( ): if not self.if_hedge('5min'): self.next() continue self.rebalancing() if self.synthetic_option.is_last_minute(): self.account.daily_accounting( self.synthetic_option.eval_date) # 该日的收盘结算 self.add_additional_to_account() self.disp() if self.synthetic_option.eval_date == dt_end and self.synthetic_option.is_last_minute( ): self.close_out() self.next() def if_hedge(self, cd): if cd == '1h': """ 1h """ if self.synthetic_option.eval_datetime.minute == 0: return True else: return False elif cd == '10min': """ 10min """ if self.synthetic_option.eval_datetime.minute % 10 == 0: return True else: return False elif cd == '5min': """ 5min """ if self.synthetic_option.eval_datetime.minute % 5 == 0: return True else: return False elif cd == '1min': return True elif cd == 'half_day': if self.synthetic_option.eval_datetime.time() == datetime.time(11, 29, 00) or \ self.synthetic_option.eval_datetime.time() == datetime.time(14, 59, 00): return True else: return False def rebalancing(self): vol = self.get_vol() self.delta = self.synthetic_option.get_black_delta(self.Option, vol) rebalance_unit = self.synthetic_option.get_rebalancing_unit( self.delta, self.Option, vol, self.synthetic_option.mktprice_close(), DeltaBound.WHALLEY_WILLMOTT, self.buywrite) self.synthetic_option.synthetic_unit += rebalance_unit if rebalance_unit > 0: long_short = LongShort.LONG else: long_short = LongShort.SHORT order = self.account.create_trade_order(self.synthetic_option, long_short, rebalance_unit) execution_record = self.synthetic_option.execute_order( order, slippage=self.slippage, execute_type=ExecuteType.EXECUTE_ALL_UNITS) self.account.add_record(execution_record, self.synthetic_option) def close_out(self): while not self.synthetic_option.is_last_minute(): self.next() close_out_orders = self.account.creat_close_out_order() for order in close_out_orders: execution_record = self.account.dict_holding[ order.id_instrument].execute_order( order, slippage=0, execute_type=ExecuteType.EXECUTE_ALL_UNITS) self.account.add_record( execution_record, self.account.dict_holding[order.id_instrument]) self.account.daily_accounting(self.synthetic_option.eval_date) self.add_additional_to_account() self.disp() self.synthetic_option.sythetic_unit = 0 """ Final NPV check """ self.df_records = pd.DataFrame(self.account.list_records) total_pnl = self.df_records[Util.TRADE_REALIZED_PNL].sum() final_npv = (self.fund + total_pnl) / self.fund print('calculate final npv from adding up realized pnl ; ', final_npv) def get_vol(self): date = self.synthetic_option.eval_date if date in self.df_vol_1m.index: # vol = self.df_vol_1m.loc[date, Util.AMT_HISTVOL] vol = self.df_garman_klass.loc[self.synthetic_option.eval_date, Util.AMT_GARMAN_KLASS] else: dt1 = Util.largest_element_less_than(port.trade_dates, date) vol = self.df_garman_klass.loc[dt1, Util.AMT_GARMAN_KLASS] # vol = self.df_vol_1m.loc[dt1, Util.AMT_HISTVOL] return vol def add_additional_to_account(self): # self.account.account.loc[ # self.synthetic_option.eval_date, 'underlying_npv_1'] = self.invest_underlying_ratio * self.underlying.mktprice_close() / self.init_spot + 1 - self.invest_underlying_ratio self.account.account.loc[ self.synthetic_option.eval_date, 'underlying_npv'] = self.underlying.mktprice_close( ) / self.init_spot self.account.account.loc[ self.synthetic_option.eval_date, 'underlying_price'] = self.underlying.mktprice_close() self.account.account.loc[ self.synthetic_option.eval_date, 'if_c1'] = self.synthetic_option.mktprice_close() self.account.account.loc[self.synthetic_option.eval_date, 'hedge_position'] \ = - self.account.trade_book[self.account.trade_book[Util.TRADE_LONG_SHORT] == LongShort.SHORT][ Util.TRADE_UNIT].sum() self.account.account.loc[self.synthetic_option.eval_date, 'hedge_ratio'] = \ self.account.account.loc[self.synthetic_option.eval_date, Util.PORTFOLIO_SHORT_POSITION_SCALE] / \ self.account.account.loc[self.synthetic_option.eval_date, Util.PORTFOLIO_LONG_POSITION_SCALE] self.account.account.loc[self.synthetic_option.eval_date, 'pct_margin_unrealized_pnl'] = \ self.account.account.loc[ self.synthetic_option.eval_date, Util.MARGIN_UNREALIZED_PNL] / self.account.init_fund self.account.account.loc[self.synthetic_option.eval_date, 'pct_nonmargin_unrealized_pnl'] = \ self.account.account.loc[ self.synthetic_option.eval_date, Util.NONMARGIN_UNREALIZED_PNL] / self.account.init_fund self.account.account.loc[self.synthetic_option.eval_date, 'pct_realized_pnl'] = \ self.account.account.loc[self.synthetic_option.eval_date, Util.TRADE_REALIZED_PNL] / self.account.init_fund self.account.account.loc[self.synthetic_option.eval_date, 'delta'] = self.delta def save_results(self): self.df_records.to_csv('../../data/trade_records.csv') self.account.account.to_csv('../../data/account.csv') # self.df_hedge_info = pd.DataFrame(self.list_hedge_info) # self.df_hedge_info.to_csv('../../data/hedge_info.csv') self.df_analysis.to_csv('../../data/df_analysis.csv') self.account.trade_book_daily.to_csv('../../data/trade_book_daily.csv') def disp(self): if self.synthetic_option.eval_date != self.underlying.eval_date: print('Date miss matched!') try: average_cost = int(self.account.trade_book[self.account.trade_book[ Util.TRADE_LONG_SHORT] == LongShort.SHORT][ Util.AVERAGE_POSITION_COST].values[0]) except: average_cost = 0 pass print( self.synthetic_option.eval_datetime, self.account.account.loc[self.synthetic_option.eval_date, Util.PORTFOLIO_NPV], self.underlying.mktprice_close() / self.init_spot, # self.account.account.loc[self.synthetic_option.eval_date, 'hedge_position'], # self.synthetic_option.synthetic_unit, int(self.Option.strike), int(self.underlying.mktprice_close()), int(self.synthetic_option.mktprice_close()), average_cost, round(self.delta, 2), round( self.account.account.loc[self.synthetic_option.eval_date, 'hedge_ratio'], 2), # self.account.cash, round( 100 * self.account.account.loc[self.synthetic_option.eval_date, 'pct_margin_unrealized_pnl'], 1), '%', round( 100 * self.account.account.loc[self.synthetic_option.eval_date, 'pct_nonmargin_unrealized_pnl'], 1), '%', round( 100 * self.account.account.loc[self.synthetic_option.eval_date, 'pct_realized_pnl'], 1), '%', self.underlying.eval_date, ) def reset_option_maturity(self, dt_maturity=None): if dt_maturity is None: dt_maturity = self.synthetic_option.eval_date + datetime.timedelta( days=self.ttm) self.Option.dt_maturity = dt_maturity def reset_option_strike(self): strike = self.synthetic_option.mktprice_close() self.Option.strike = strike def analysis(self, dt_start, dt_end): """ Replicate Period Result Analysis """ self.df_records = pd.DataFrame(self.account.list_records) analysis = pd.Series() df_hedge_records = self.df_records[ (self.df_records[Util.ID_INSTRUMENT] != 'index_300sh') & (self.df_records[Util.DT_TRADE] >= dt_start) & (self.df_records[Util.DT_TRADE] <= dt_end)] init_stock_value = self.account.account.loc[ dt_start, Util.PORTFOLIO_TRADES_VALUE] init_stock_price = \ self.underlying.df_data[self.underlying.df_data[Util.DT_DATE] == dt_start][Util.AMT_CLOSE].values[0] terminal_stock_price = \ self.underlying.df_data[self.underlying.df_data[Util.DT_DATE] == dt_end][Util.AMT_CLOSE].values[0] replicate_pnl = df_hedge_records[Util.TRADE_REALIZED_PNL].sum() option_payoff = self.synthetic_option.amt_option * max( init_stock_price - terminal_stock_price, 0) replicate_cost = replicate_pnl - option_payoff replicate_cost_future = replicate_pnl - self.synthetic_option.amt_option * max( self.Option.strike - terminal_stock_price, 0) pct_replicate_cost = replicate_cost / init_stock_value pct_replicate_pnl = replicate_pnl / init_stock_value transaction_cost = df_hedge_records[Util.TRANSACTION_COST].sum() init_portfolio_value = self.account.account.loc[dt_start, Util.PORTFOLIO_VALUE] trade_value = np.abs(df_hedge_records[Util.TRADE_BOOK_VALUE]).sum() ratio = trade_value / init_portfolio_value pct_underlying_pnl = (terminal_stock_price - init_stock_price) / init_stock_price analysis['ratio'] = ratio analysis['dt_start'] = dt_start analysis['dt_end'] = dt_end analysis['init_stock_value'] = init_stock_value analysis['replicate_pnl'] = replicate_pnl analysis['option_payoff'] = option_payoff analysis['replicate_cost_spot'] = replicate_cost analysis['replicate_cost_future'] = replicate_cost_future analysis['pct_replicate_cost'] = pct_replicate_cost analysis['pct_replicate_pnl'] = pct_replicate_pnl analysis['pct_underlying_pnl'] = pct_underlying_pnl analysis['transaction_cost'] = transaction_cost analysis['dt_maturity'] = self.Option.dt_maturity self.df_analysis = self.df_analysis.append(analysis, ignore_index=True)
class VolTrading(object): def __init__(self, start_date, end_date, df_metrics, df_vol, df_future_c1_daily, df_futures_all_daily): self.min_holding = 20 self.slippage = 0 self.nbr_maturity = 0 self.moneyness_rank = 0 self.m_notional = 1 self.rf = 0.03 self.n_premium_std = 90 #用于计算权利金溢价1倍标准差的数据期限 self.n_hv = 20 # 用与期权期限相匹配的历史波动率期限 self.n_close_by_maturity = 5 self.min_premium = 2.0 / 100.0 # 对冲成本对应的开平仓最低隐含波动率溢价 self.n_llt = 5 self.cd_option_price = c.CdTradePrice.CLOSE self.cd_future_price = c.CdTradePrice.CLOSE self.cd_price = c.CdPriceType.CLOSE self.start_date = start_date self.end_date = end_date self.df_metrics = df_metrics self.df_vol = df_vol self.df_f_c1 = df_future_c1_daily self.df_f_all = df_futures_all_daily def init(self): df_future_histvol = self.df_f_c1.copy() df_future_histvol['amt_hv'] = histvol.hist_vol( df_future_histvol[c.Util.AMT_CLOSE], n=self.n_hv) self.dt_start = max(self.df_f_c1[c.Util.DT_DATE].values[0], self.df_metrics[c.Util.DT_DATE].values[0]) self.end_date = min(self.df_f_c1[c.Util.DT_DATE].values[-1], self.df_metrics[c.Util.DT_DATE].values[-1]) self.df_metrics = self.df_metrics[ self.df_metrics[c.Util.DT_DATE] >= self.dt_start].reset_index( drop=True) self.df_f_c1 = self.df_f_c1[ self.df_f_c1[c.Util.DT_DATE] >= self.dt_start].reset_index( drop=True) self.df_f_all = self.df_f_all[ self.df_f_all[c.Util.DT_DATE] >= self.dt_start].reset_index( drop=True) self.df_vol = pd.merge(self.df_vol, df_future_histvol[[c.Util.DT_DATE, 'amt_hv']], on=c.Util.DT_DATE).set_index(c.Util.DT_DATE) self.optionset = BaseOptionSet(self.df_metrics) self.optionset.init() self.hedging = SytheticOption(self.df_f_c1, rf=self.rf, df_futures_all_daily=self.df_f_all) self.hedging.init() self.hedging.amt_option = 1 / 1000 # 50ETF与IH点数之比 self.account = BaseAccount(init_fund=c.Util.BILLION, rf=self.rf) self.prepare_timing() def prepare_timing(self): self.timing_hviv() self.timing_llt() def timing_hviv(self): self.df_vol['amt_premium'] = self.df_vol[ c.Util.PCT_IMPLIED_VOL] - self.df_vol['amt_hv'] self.df_vol['amt_1std'] = c.Statistics.standard_deviation( self.df_vol['amt_premium'], n=self.n_premium_std) self.df_vol['amt_2std'] = 2 * c.Statistics.standard_deviation( self.df_vol['amt_premium'], n=self.n_premium_std) self.df_vol['percentile_95'] = c.Statistics.percentile( self.df_vol[c.Util.PCT_IMPLIED_VOL], n=252, percent=0.95) def timing_llt(self): self.df_vol['LLT_' + str(self.n_llt)] = LLT( self.df_vol[c.Util.PCT_IMPLIED_VOL], self.n_llt) self.df_vol['LLT_signal_' + str(self.n_llt)] = self.df_vol['LLT_' + str(self.n_llt)].diff() def open_signal(self): pass def close_signal(self): pass def open_signal_llt(self): dt_date = self.optionset.eval_date if dt_date not in self.df_vol.index: return False if self.df_vol.loc[dt_date, 'LLT_signal_5'] < 0: # 隐含波动率处于下行区间 return True else: return False def close_signal_llt(self): dt_date = self.optionset.eval_date if dt_date not in self.df_vol.index: return False if self.df_vol.loc[dt_date, 'LLT_signal_5'] > 0: # 隐含波动率处于上行区间 return True else: return False def open_signal_ivhv(self): dt_date = self.optionset.eval_date if dt_date not in self.df_vol.index: return False amt_premium = self.df_vol.loc[dt_date, 'amt_premium'] amt_1std = self.df_vol.loc[dt_date, 'amt_1std'] if amt_premium > amt_1std and amt_premium > self.min_premium: # 隐含波动率相比历史波动率具有一定溢价 return True else: return False def close_signal_ivhv(self): dt_date = self.optionset.eval_date amt_premium = self.df_vol.loc[dt_date, 'amt_premium'] if amt_premium <= self.min_premium: return True else: return False def close_signal_maturity(self): dt_maturity = None for option in self.account.dict_holding.values(): if isinstance(option, BaseOption) and option is not None: dt_maturity = option.maturitydt() break if (dt_maturity - self.optionset.eval_date).days <= self.n_close_by_maturity: return True else: return False def strategy(self): return self.short_straddle() def short_straddle(self): maturity = self.optionset.select_maturity_date( nbr_maturity=self.nbr_maturity, min_holding=self.min_holding) list_atm_call, list_atm_put = self.optionset.get_options_list_by_moneyness_mthd1( moneyness_rank=self.moneyness_rank, maturity=maturity, cd_price=self.cd_price) atm_call = self.optionset.select_higher_volume(list_atm_call) atm_put = self.optionset.select_higher_volume(list_atm_put) if atm_call is None or atm_put is None: return else: return [atm_call, atm_put] def excute(self, strategy): if strategy is None: return False else: pv = self.account.portfolio_total_value self.option_holding = {} for option in strategy: unit = np.floor( np.floor(pv / option.strike()) / option.multiplier()) * self.m_notional order = self.account.create_trade_order( option, c.LongShort.SHORT, unit, cd_trade_price=self.cd_option_price) record = option.execute_order(order, slippage_rate=self.slippage) self.account.add_record(record, option) self.option_holding.update({option: unit}) return True def close_out(self): close_out_orders = self.account.creat_close_out_order( cd_trade_price=c.CdTradePrice.CLOSE) for order in close_out_orders: execution_record = self.account.dict_holding[order.id_instrument] \ .execute_order(order, slippage_rate=self.slippage, execute_type=c.ExecuteType.EXECUTE_ALL_UNITS) self.account.add_record( execution_record, self.account.dict_holding[order.id_instrument]) def close_out_1(self): for option in self.account.dict_holding.values(): if isinstance(option, BaseOption): order = self.account.create_close_order( option, cd_trade_price=self.cd_option_price) else: order = self.account.create_close_order( option, cd_trade_price=self.cd_future_price) record = option.execute_order(order, slippage_rate=self.slippage) self.account.add_record(record, option) # TODO: Use Delta Bound Model def delta_hedge(self): option1 = list(self.option_holding.keys())[0] iv_htbr = self.optionset.get_iv_by_otm_iv_curve( dt_maturity=option1.maturitydt(), strike=option1.applicable_strike()) options_delta = 0 for option in self.option_holding.keys(): unit = self.option_holding[option] options_delta += unit * option.get_delta( iv_htbr) * option.multiplier() hedge_unit = self.hedging.get_hedge_rebalancing_unit( options_delta, c.BuyWrite.WRITE) self.hedging.synthetic_unit += -hedge_unit if hedge_unit > 0: long_short = c.LongShort.LONG else: long_short = c.LongShort.SHORT order_u = self.account.create_trade_order( self.hedging, long_short, hedge_unit, cd_trade_price=self.cd_future_price) record_u = self.hedging.execute_order(order_u, slippage_rate=self.slippage) self.account.add_record(record_u, self.hedging) # print('') def back_test(self): empty_position = True while self.optionset.eval_date <= self.end_date: # if self.optionset.eval_date == datetime.date(2017, 1, 19): # print('') if self.optionset.eval_date >= self.end_date: # Final close out all. self.close_out() self.account.daily_accounting(self.optionset.eval_date) print(self.optionset.eval_date, ' close out ') print( self.optionset.eval_date, self.hedging.eval_date, self.account.account.loc[self.optionset.eval_date, c.Util.PORTFOLIO_NPV], int(self.account.cash)) break # 标的移仓换月 if self.hedging.close_old_contract_month( self.account, self.slippage, cd_price=self.cd_future_price): self.hedging.synthetic_unit = 0 # 平仓 if not empty_position: if self.close_signal(): self.close_out_1() self.hedging.synthetic_unit = 0 empty_position = True # 开仓 if empty_position and self.open_signal(): s = self.strategy() empty_position = not self.excute(s) # Delta hedge if not empty_position: self.delta_hedge() self.account.daily_accounting(self.optionset.eval_date) if not self.optionset.has_next(): break self.optionset.next() self.hedging.next() return self.account