Esempio n. 1
0
class ParityArbitrage(object):
    def __init__(self,name_code, df_option, df_etf=None, df_future_all=None,df_index=None):
        self.name_code = name_code
        self.df_option = df_option
        self.df_etf = df_etf
        self.df_future_all = df_future_all
        self.df_index = df_index
        self.rf = 0.03
        self.m = 0.9
        self.account = BaseAccount(c.Util.BILLION / 10,rf=self.rf)
        self.unit = 50
        self.min_holding = 6 # 50ETF与IH到期日相差5天
        self.nbr_maturity = 0
        self.rank = 3
        self.slippage = 0
        self.aggregate_costs = 0.5/100.0
        self.cd_price = c.CdTradePrice.CLOSE
        self.df_arbitrage_window = pd.DataFrame()

    def init(self):
        self.underlying = None
        self.futureset = None
        self.baseindex = None
        self.optionset = BaseOptionSet(self.df_option)
        self.optionset.init()
        if self.name_code == c.Util.STR_50ETF:
            if self.df_etf is not None:
                self.underlying = BaseInstrument(self.df_etf) # 50ETF
                self.underlying.init()
            if self.df_future_all is not None:
                self.futureset = BaseFutureSet(self.df_future_all) # IH
                self.futureset.init()
                self.future_unit_ratio = 1/1000.0
            if self.df_index is not None:
                self.baseindex = BaseInstrument(self.df_index) # SH50
                self.baseindex.init()
        else: # 商品期权
            if self.df_future_all is not None:
                self.futureset = BaseFutureSet(self.df_future_all) # IH
                self.futureset.init()
                self.future_unit_ratio = 1.0
            # self.optionset = BaseOptionSet(self.df_option)
            # self.optionset.init()

    def update_sythetics(self):
        if self.name_code == c.Util.STR_50ETF:
            dt_maturity = self.optionset.select_maturity_date(nbr_maturity=self.nbr_maturity, min_holding=self.min_holding)
            contract_month = self.optionset.get_dict_options_by_maturities()[dt_maturity][0].contract_month()

            self.t_quote = self.optionset.get_T_quotes(dt_maturity, self.cd_price)
            self.t_quote.loc[:, 'diff'] = abs(
                self.t_quote.loc[:, c.Util.AMT_APPLICABLE_STRIKE] - self.t_quote.loc[:, c.Util.AMT_UNDERLYING_CLOSE])
            self.t_quote.loc[:, 'rank'] = self.t_quote.index - self.t_quote['diff'].idxmin()
            discount = c.PricingUtil.get_discount(self.optionset.eval_date, dt_maturity, self.rf)
            self.t_quote.loc[:, 'sythetic_underlying'] = self.t_quote.loc[:, c.Util.AMT_CALL_QUOTE] \
                                                    - self.t_quote.loc[:,c.Util.AMT_PUT_QUOTE] \
                                                    + self.t_quote.loc[:,c.Util.AMT_APPLICABLE_STRIKE] * discount
            df_window = self.t_quote[(self.t_quote['rank']<=self.rank)&(self.t_quote['rank']>=-self.rank)] # 只考虑rank以内期权
            self.row_max_sythetic = df_window.loc[df_window['sythetic_underlying'].idxmax()]
            self.row_min_sythetic = df_window.loc[df_window['sythetic_underlying'].idxmin()]
            self.df_arbitrage_window.loc[self.optionset.eval_date,'50etf'] = self.underlying.mktprice_close()
            self.df_arbitrage_window.loc[self.optionset.eval_date,'sythetic_underlying_max'] = self.row_max_sythetic['sythetic_underlying']
            self.df_arbitrage_window.loc[self.optionset.eval_date,'sythetic_underlying_min'] = self.row_min_sythetic['sythetic_underlying']
            if self.futureset is not None:
                future = self.futureset.select_future_by_contract_month(contract_month)
                self.row_max_sythetic['future'] = future
                if future is None:
                    return
                self.basis_to_etf = future.mktprice_close() - self.underlying.mktprice_close()/self.future_unit_ratio
                self.df_arbitrage_window.loc[self.optionset.eval_date, 'basis_to_etf'] = self.basis_to_etf
                self.df_arbitrage_window.loc[self.optionset.eval_date, 'ih'] = future.mktprice_close()
                if self.baseindex is not None:
                    self.basis_to_index = future.mktprice_close() - self.baseindex.mktprice_close()
                    self.tracking_error = self.underlying.mktprice_close()/self.future_unit_ratio - self.baseindex.mktprice_close()
                    self.df_arbitrage_window.loc[self.optionset.eval_date, 'basis_to_index'] = self.basis_to_index
                    self.df_arbitrage_window.loc[self.optionset.eval_date, 'tracking_error'] = self.tracking_error
                    self.df_arbitrage_window.loc[self.optionset.eval_date, 'index_50'] = self.baseindex.mktprice_close()
        else:
            dt_maturity = self.optionset.select_maturity_date(nbr_maturity=self.nbr_maturity, min_holding=self.min_holding)
            contract_month = self.optionset.get_dict_options_by_maturities()[dt_maturity][0].contract_month()
            self.t_quote = self.optionset.get_T_quotes(dt_maturity, self.cd_price)
            self.t_quote.loc[:, 'diff'] = abs(
                self.t_quote.loc[:, c.Util.AMT_APPLICABLE_STRIKE] - self.t_quote.loc[:, c.Util.AMT_UNDERLYING_CLOSE])
            self.t_quote.loc[:, 'rank'] = self.t_quote.index - self.t_quote['diff'].idxmin()
            discount = c.PricingUtil.get_discount(self.optionset.eval_date, dt_maturity, self.rf)
            self.t_quote.loc[:, 'sythetic_underlying'] = self.t_quote.loc[:, c.Util.AMT_CALL_QUOTE] \
                                                    - self.t_quote.loc[:,c.Util.AMT_PUT_QUOTE] \
                                                    + self.t_quote.loc[:,c.Util.AMT_APPLICABLE_STRIKE] * discount
            df_window = self.t_quote[(self.t_quote['rank']<=self.rank)&(self.t_quote['rank']>=-self.rank)] # 只考虑rank以内期权
            self.row_max_sythetic = df_window.loc[df_window['sythetic_underlying'].idxmax()]
            self.row_min_sythetic = df_window.loc[df_window['sythetic_underlying'].idxmin()]
            future = self.futureset.select_future_by_contract_month(contract_month)
            self.underlying = future
            self.df_arbitrage_window.loc[self.optionset.eval_date, 'underlying'] = self.underlying.mktprice_close()
            self.df_arbitrage_window.loc[self.optionset.eval_date, 'sythetic_underlying_max'] = self.row_max_sythetic[
                'sythetic_underlying']
            self.df_arbitrage_window.loc[self.optionset.eval_date, 'sythetic_underlying_min'] = self.row_min_sythetic[
                'sythetic_underlying']


    def open_signal(self,cd_strategy):
        if cd_strategy == 'box':
            if (self.row_max_sythetic['sythetic_underlying'] - self.row_min_sythetic['sythetic_underlying'])/self.underlying.mktprice_close() > self.aggregate_costs:
                df = pd.DataFrame(columns=['dt_date','id_instrument','base_instrument','long_short'])
                df = self.short_sythetic(df)
                df = self.long_sythetic(df)
                return df
            else:
                return None
        elif cd_strategy == 'conversion': # Converion : Short Sythetic, Long ETF
            if (self.row_max_sythetic['sythetic_underlying'] - self.underlying.mktprice_close())/self.underlying.mktprice_close() > self.aggregate_costs:
                df = pd.DataFrame(columns=['dt_date','id_instrument','base_instrument','long_short'])
                df = self.short_sythetic(df)
                df = self.long_etf(df)
                return df
            else:
                return None
        elif cd_strategy == 'conversion_ih': # Converion : Short Sythetic, Long IH # 主要布局IH负基差套利
            if self.optionset.eval_date.month ==5: return None #5月由于股票集中现金分红不做空Synthetic
            future = self.row_max_sythetic['future']
            if future is None: return None
            self.future = future
            if (self.row_max_sythetic['sythetic_underlying']/self.future_unit_ratio - future.mktprice_close()-
                    self.df_arbitrage_window.loc[self.optionset.eval_date,'tracking_error'])/future.mktprice_close() > self.aggregate_costs:
                df = pd.DataFrame(columns=['dt_date', 'id_instrument', 'base_instrument', 'long_short'])
                df = self.short_sythetic(df)
                df = self.long_ih(df,future)
                return df
            else:
                return None
        elif cd_strategy == 'ih_basis_arbitrage':
            future = self.row_max_sythetic['future']
            if future is None: return None
            self.future = future
            if self.df_arbitrage_window.loc[self.optionset.eval_date, 'basis_to_index'] < -self.aggregate_costs:  # 期货贴水
                df = pd.DataFrame(columns=['dt_date', 'id_instrument', 'base_instrument', 'long_short'])
                df = self.short_sythetic(df)
                df = self.long_ih(df,future)
                return df
            else:
                return None
        elif cd_strategy == 'may_effect': # Reverse: Long Sythetic, Short IH # 5-9月分红期
            if self.optionset.eval_date.month ==5: #5月由于股票集中现金分红不做空Synthetic
                df = pd.DataFrame(columns=['dt_date', 'id_instrument', 'base_instrument', 'long_short'])
                df = self.long_sythetic(df)
                df = self.short_ih(df)
                return df
            else:
                return None

    def close_signal(self,cd_strategy,df_position):
        if cd_strategy == 'box':
            if self.reverse_call.maturitydt() == self.optionset.eval_date or self.conversion_call.maturitydt() == self.optionset.eval_date :
                return True
            discount_r = c.PricingUtil.get_discount(self.optionset.eval_date, self.reverse_put.maturitydt(), self.rf)
            discount_c = c.PricingUtil.get_discount(self.optionset.eval_date, self.conversion_put.maturitydt(), self.rf)
            reverse_sythetic = self.reverse_call.mktprice_close()-self.reverse_put.mktprice_close()+self.reverse_put.applicable_strike()*discount_r # Longed
            conversion_sythetic = self.conversion_call.mktprice_close()-self.conversion_put.mktprice_close()+self.conversion_put.applicable_strike()*discount_c # shorted
            if conversion_sythetic <= reverse_sythetic:
                return True
            else:
                return False
        elif cd_strategy == 'conversion': # Short Sythetic, Long Underlying # 主要布局IH负基差套利
            if self.conversion_call.maturitydt() == self.optionset.eval_date:
                return True
            discount_c = c.PricingUtil.get_discount(self.optionset.eval_date, self.conversion_put.maturitydt(), self.rf)
            conversion_sythetic = self.conversion_call.mktprice_close() - self.conversion_put.mktprice_close() + self.conversion_put.applicable_strike() * discount_c  # shorted
            if conversion_sythetic/self.future_unit_ratio <= self.underlying.mktprice_close():
                return True
            else:
                return False
        elif cd_strategy == 'conversion_ih': # Short Sythetic, Long IH # 主要布局IH负基差套利
            if self.conversion_call.maturitydt() == self.optionset.eval_date or self.future.maturitydt() == self.optionset.eval_date:
                return True
            discount_c = c.PricingUtil.get_discount(self.optionset.eval_date, self.conversion_put.maturitydt(), self.rf)
            conversion_sythetic = self.conversion_call.mktprice_close() - self.conversion_put.mktprice_close() + self.conversion_put.applicable_strike() * discount_c  # shorted
            if conversion_sythetic/self.future_unit_ratio <= self.future.mktprice_close():
                return True
            else:
                return False
        elif cd_strategy == 'ih_basis_arbitrage':
            if self.df_arbitrage_window.loc[self.optionset.eval_date, 'basis_to_index'] >= 0:
                return True
            else:
                return False
        elif cd_strategy == 'may_effect': # Reverse: Long Sythetic, Short IH # 5-9月分红期
            if self.optionset.eval_date.month !=5: #5月由于股票集中现金分红不做空Synthetic
                return True
            else:
                return False

    def open_excute(self,open_signal):
        if open_signal is None:
            return False
        else:
            fund_per_unit = open_signal['fund_requirement'].sum()
            unit = np.floor(self.account.cash*self.m/fund_per_unit)
            for (idx,row) in open_signal.iterrows():
                option = row['base_instrument']
                order = self.account.create_trade_order(option, row['long_short'], unit*row['unit_ratio'],
                                                        cd_trade_price=self.cd_price)
                record = option.execute_order(order, slippage=self.slippage)
                self.account.add_record(record, option)
            print(self.optionset.eval_date, ' open position')
            return True


    def close_excute(self):
        self.close_out()
        print(self.optionset.eval_date, ' close position')
        return True
        # else:
        #     for (idx,row) in close_signal.iterrows():
        #         option = row['base_instrument']
        #         order = self.account.create_trade_order(option, row['long_short'], row['unit'],
        #                                                 cd_trade_price=self.cd_price)
        #         record = option.execute_order(order, slippage=self.slippage)
        #         self.account.add_record(record, option)
        #     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=self.slippage, execute_type=c.ExecuteType.EXECUTE_ALL_UNITS)
            self.account.add_record(execution_record, self.account.dict_holding[order.id_instrument])


    def short_sythetic(self,df):
        # Short Sythetic
        self.conversion_call = self.optionset.get_baseoption_by_id(self.row_max_sythetic[c.Util.ID_CALL])
        fund_requirement = self.conversion_call.get_fund_required(c.LongShort.SHORT)
        df = df.append({'dt_date': self.optionset.eval_date,
                        'cd_posiiton': 'C_call',
                        'id_instrument': self.row_max_sythetic[c.Util.ID_CALL],
                        'base_instrument': self.conversion_call,
                        'long_short': c.LongShort.SHORT,
                        'fund_requirement': fund_requirement,
                        'cashflow_t0': self.conversion_call.mktprice_close() * self.conversion_call.multiplier(),
                        'unit_ratio' : 1},
                       ignore_index=True)
        self.conversion_put = self.optionset.get_baseoption_by_id(self.row_max_sythetic[c.Util.ID_PUT])
        fund_requirement = self.conversion_put.get_fund_required(c.LongShort.LONG)
        df = df.append({'dt_date': self.optionset.eval_date,
                        'cd_posiiton': 'C_put',
                        'id_instrument': self.row_max_sythetic[c.Util.ID_PUT],
                        'base_instrument': self.conversion_put,
                        'long_short': c.LongShort.LONG,
                        'fund_requirement': fund_requirement,
                        'cashflow_t0': -self.conversion_put.mktprice_close() * self.conversion_put.multiplier(),
                        'unit_ratio' : 1},
                       ignore_index=True)
        return df

    def long_sythetic(self,df):
        # Reverse : Long Sythetic
        self.reverse_call = self.optionset.get_baseoption_by_id(self.row_min_sythetic[c.Util.ID_CALL])
        fund_requirement = self.reverse_call.get_fund_required(c.LongShort.LONG)
        df = df.append({'dt_date': self.optionset.eval_date,
                        'cd_posiiton': 'R_call',
                        'id_instrument': self.row_min_sythetic[c.Util.ID_CALL],
                        'base_instrument': self.reverse_call,
                        'long_short': c.LongShort.LONG,
                        'fund_requirement': fund_requirement,
                        'cashflow_t0': -self.reverse_call.mktprice_close() * self.reverse_call.multiplier()},
                       ignore_index=True)
        self.reverse_put = self.optionset.get_baseoption_by_id(self.row_min_sythetic[c.Util.ID_PUT])
        fund_requirement = self.reverse_put.get_fund_required(c.LongShort.SHORT)
        df = df.append({'dt_date': self.optionset.eval_date,
                        'cd_posiiton': 'R_put',
                        'id_instrument': self.row_min_sythetic[c.Util.ID_PUT],
                        'base_instrument': self.reverse_put,
                        'long_short': c.LongShort.SHORT,
                        'fund_requirement': fund_requirement,
                        'cashflow_t0': self.reverse_put.mktprice_close() * self.reverse_put.multiplier()},
                       ignore_index=True)
        return df

    def long_etf(self,df):
        unit_ratio = self.conversion_put.multiplier()
        fund_requirement = self.conversion_put.multiplier() * self.underlying.mktprice_close()
        df = df.append({'dt_date': self.optionset.eval_date,
                        'cd_posiiton': 'underlying',
                        'id_instrument': self.underlying.id_instrument(),
                        'base_instrument': self.underlying,
                        'long_short': c.LongShort.LONG,
                        'fund_requirement': fund_requirement,
                        'cashflow_t0': -self.underlying.mktprice_close() * self.underlying.multiplier()*unit_ratio,
                        'unit_ratio' : unit_ratio},
                       ignore_index=True)
        return df

    def long_ih(self,df,future):
        unit_ratio = self.conversion_put.multiplier()/future.multiplier()/1000.0
        fund_requirement = future.mktprice_close()*future.multiplier()*unit_ratio
        df = df.append({'dt_date': self.optionset.eval_date,
                        'cd_posiiton': 'underlying',
                        'id_instrument': future.id_instrument(),
                        'base_instrument': future,
                        'long_short': c.LongShort.LONG,
                        'fund_requirement': fund_requirement,
                        'cashflow_t0': -future.mktprice_close() * future.multiplier()*unit_ratio,
                        'unit_ratio' : unit_ratio},
                       ignore_index=True)
        return df

    def short_ih(self,df):
        unit_ratio = self.reverse_put.multiplier()/self.future.multiplier()/1000.0
        fund_requirement = self.future.mktprice_close()*self.future.multiplier()*unit_ratio
        df = df.append({'dt_date': self.optionset.eval_date,
                        'cd_posiiton': 'underlying',
                        'id_instrument': self.future.id_instrument(),
                        'base_instrument': self.future,
                        'long_short': c.LongShort.SHORT,
                        'fund_requirement': fund_requirement,
                        'cashflow_t0': -self.future.mktprice_close() * self.future.multiplier()*unit_ratio,
                        'unit_ratio' : unit_ratio},
                       ignore_index=True)
        return df


    def back_test(self, cd_strategy):
        # TODO: DIVIDEND
        empty_position = True
        df_position = None
        while self.optionset.has_next():
            # if self.optionset.eval_date == datetime.date(2015,5,18):
            #     print('')
            self.update_sythetics()
            if empty_position:
                df_position = self.open_signal(cd_strategy)
                empty_position = not self.open_excute(df_position)
            elif self.close_signal(cd_strategy,df_position):
                empty_position = self.close_excute()
            # if isinstance(self.underlying,BaseFutureCoutinuous):
            #     self.underlying.shift_contract_month(self.account,self.slippage)
            self.account.daily_accounting(self.optionset.eval_date)
            self.optionset.next()
            self.underlying.next()
            if self.futureset is not None: self.futureset.next()
            if self.baseindex is not None: self.baseindex.next()
            # print(self.optionset.eval_date)
        return self.account

    def back_test_comdty(self, cd_strategy):
        empty_position = True
        df_position = None
        while self.optionset.has_next():
            # if self.optionset.eval_date == datetime.date(2015,5,18):
            #     print('')
            self.update_sythetics()
            if empty_position:
                df_position = self.open_signal(cd_strategy)
                empty_position = not self.open_excute(df_position)
            elif self.close_signal(cd_strategy,df_position):
                empty_position = self.close_excute()
            # if isinstance(self.underlying,BaseFutureCoutinuous):
            #     self.underlying.shift_contract_month(self.account,self.slippage)
            self.account.daily_accounting(self.optionset.eval_date)
            self.optionset.next()
            self.futureset.next()
            # if self.baseindex is not None: self.baseindex.next()
            # print(self.optionset.eval_date)
        return self.account
Esempio n. 2
0
df_metrics = get_data.get_comoption_mktdata(start_date, end_date, name_code)
""" T-quote IV """
optionset = BaseOptionSet(df_metrics, rf=0)
optionset.init()
optionset.go_to(end_date)
dt_maturity = optionset.select_maturity_date(0, min_holding)
call_list, put_list = optionset.get_options_list_by_moneyness_mthd1(
    0, dt_maturity)
atm_call = optionset.select_higher_volume(call_list)
atm_put = optionset.select_higher_volume(put_list)
print('atm call iv: ', atm_call.get_implied_vol())
print('atm put iv: ', atm_put.get_implied_vol())
print('atm strike: ', atm_put.strike())

t_quote = optionset.get_T_quotes(dt_maturity)
ivs_c1 = optionset.get_call_implied_vol_curve(dt_maturity)
ivs_p1 = optionset.get_put_implied_vol_curve(dt_maturity)
ivs_c2 = optionset.get_call_implied_vol_curve_htbr(dt_maturity)
ivs_p2 = optionset.get_put_implied_vol_curve_htbr(dt_maturity)

iv_curve = optionset.get_implied_vol_curves(dt_maturity)
iv_curve_htbr = optionset.get_implied_vol_curves_htbr(dt_maturity)
iv_volume_weighted = optionset.get_volume_weighted_iv(dt_maturity)
iv_volume_weighted_htbr = optionset.get_volume_weighted_iv_htbr(dt_maturity)
htbr = optionset.get_htb_rate(dt_maturity)
print(iv_volume_weighted)
print(iv_volume_weighted_htbr)
print(htbr)
print(iv_curve_htbr)
""" Quantlib """