【问题标题】:How to improve performance in calculating greeks in DolphinDB?如何提高在 DolphinDB 中计算希腊语的性能?
【发布时间】:2020-05-31 14:28:07
【问题描述】:

我用DolphinDB计算Greeks,我用向量化的方式写的,性能还不错。但是我不能以向量化的方式实现隐含波动率,这使得性能很差。如何提高以下实现的性能?

def GBlackScholes(future_price, strike, input_ttm, risk_rate, b_rate, input_vol, is_call) {
  ttm = input_ttm + 0.000000000000001;
  vol = input_vol + 0.000000000000001;

  d1 = (log(future_price/strike) + (b_rate + vol*vol/2) * ttm) / (vol * sqrt(ttm));
  d2 = d1 - vol * sqrt(ttm);

  if (is_call) {
    return future_price * exp((b_rate - risk_rate) * ttm) * cdfNormal(0, 1, d1) - strike * exp(-risk_rate*ttm) * cdfNormal(0, 1, d2);
  } else {
    return strike * exp(-risk_rate*ttm) * cdfNormal(0, 1, -d2) - future_price * exp((b_rate - risk_rate) * ttm) * cdfNormal(0, 1, -d1);
  }
}

def ImpliedVolatility(future_price, strike, ttm, risk_rate, b_rate, option_price, is_call) {
  high=5.0;
  low = 0.0;

  do {
    if (GBlackScholes(future_price, strike, ttm, risk_rate, b_rate, (high+low)/2, is_call) > option_price) {
      high = (high+low)/2;
    } else {
      low = (high + low) /2;
    }
  } while ((high-low) > 0.00001);

  return (high + low) /2;
}

【问题讨论】:

    标签: performance quantitative-finance dolphindb


    【解决方案1】:

    如何提高以下实现的性能?

    矢量化处理?

    这部分有点神秘 - 请原谅一个老 quant 没有足够好地阅读这部分 - 关于哪些参数不打算成为标量的信息不存在,因此分析是基于明确存在的信息。

    免责声明
    虽然我知道 DolphinDB 尚未发布三元运算符 (...)?(...):(...) 以在公共 API 中可用,但我觉得下面表达的想法是清晰而合理的。

    战斗表现?

    如果认真谈论性能,首先让我们回顾一下并主要避免任何重复的重新计算,这发生在上面提议的代码中:

    def GBlackScholes( future_price,
                       strike,
                       input_ttm,
                       risk_rate,
                       b_rate,
                       input_vol,               // <------------[VAR]:: <-- ( high+low )/2
                       is_call                 //
                       ) {                    //
        ttm = input_ttm + 0.000000000000001; //-do-while-(CONST)
        vol = input_vol + 0.000000000000001;//--do-while--------[VAR]
                                           //
        d1  = ( log( future_price )       //----do-while-(CONST)
              - log( strike )            //-----do-while-(CONST)
              + b_rate                  //------do-while-(CONST)
              + vol*vol/2              //-------do-while--------[VAR]
              * ttm                   //--------do-while-(CONST)
                ) / ( vol            //---------do-while--------[VAR]
                    * sqrt( ttm )   //----------do-while-(CONST)
                      );           //
        d2  = ( d1                //------------do-while--------[VAR]
              - vol              //-------------do-while--------[VAR]
              * sqrt( ttm )     //--------------do-while-(CONST)
                );             //              ++---------------[VAR]
                              //               ||     .________________________________________________.
     // -----------[VAR]-?-( cdfNormal(--------vv-) * [                                                ]--do-while-(CONST)
        return ( is_call ? ( cdfNormal( 0, 1,  d1 ) * future_price * exp( ( b_rate - risk_rate ) * ttm )
                           - cdfNormal( 0, 1,  d2 ) * strike       * exp(           -risk_rate   * ttm )
                             )
                         : ( cdfNormal( 0, 1, -d2 ) * strike       * exp(           -risk_rate   * ttm )
                           - cdfNormal( 0, 1, -d1 ) * future_price * exp( ( b_rate - risk_rate ) * ttm )
                             )
                 );
    }
    

    一个稍微更好的 GBlackScholes_WHILEd() 函数 - 这可以节省 ~ 22x float-OP(其中一些相当昂贵)每个 do{}while-LOOP :

    def GBlackScholes_WHILEd( vol_,         // <--------------------------[VAR]:: ( high+low )/2 + 0.000000000000001;
                              V1,           // <--------------------------[VAR]:: vol_ * C3
                              is_call,
                              ttm_,         // do-while-(CONST)
                              C1, C2, C3,   // do-while-(CONST)
                              R1, R2        // do-while-(CONST)
                              ) {
      d1  = ( C1
            + C2
            * vol_*vol_ //--------------------do-while--------[VAR]
              )
            / V1;       //--------------------do-while--------[VAR]
    
      d2  = ( d1 //---------------------------do-while--------[VAR]
            - V1 //---------------------------do-while--------[VAR]
              );
    
      //    --[VAR]--- ? ( cdfNormal(------[VAR]) * <________________________________________________>--do-while-(CONST)
      return ( is_call ? ( cdfNormal( 0, 1,  d1 ) * R1
                         - cdfNormal( 0, 1,  d2 ) * R2
                           )
                       : ( cdfNormal( 0, 1, -d2 ) * R2
                         - cdfNormal( 0, 1, -d1 ) * R1
                           )
               );
    }
    

    最后:

    最有效的 ImpliedVolatility() 函数甚至完全避免了所有的调用签名每循环处理,做了一些代数并保持在可实现的性能优势:

    def ImpliedVolatility( future_price,
                           strike,
                           ttm,
                           risk_rate,
                           b_rate,
                           option_price,
                           is_call
                           ) {
        high = 5.0;                                                 // IS THIS A UNIVERSALLY SAFE & TRUE SUPREME - i.e. SAFELY ABOVE ALL POSSIBLE OPTIONS ?
        low  = 0.0;
    
        ttm_ = ttm + 0.000000000000001;                             // do-while-(CONST)     1x fADD
        C1   = log(future_price ) - log( strike ) + b_rate;         // do-while-(CONST)     1x fADD  1x fDIV  1x fLOG  1x fNEG
        C2   =     ( ttm_ ) / 2;                                    // do-while-(CONST)              1x fDIV
        C3   = sqrt( ttm_ );                                        // do-while-(CONST)                       1x fSQRT
        R1   = future_price * exp( ( b_rate - risk_rate ) * ttm_ ); // do-while-(CONST)     1x fADD  2x fMUL  1x fEXP  1x fNEG
        R2   = strike       * exp(           -risk_rate   * ttm_ ); // do-while-(CONST)              2x fMUL  1x fEXP  1x fNEG
        U4   = C2   - ttm_;                                         // do-while-(CONST)     1x fADD                    1x fNEG
        U5   = ttm_ - C2;                                           // do-while-(CONST)     1x fADD                    1x fNEG
        U3inv= 1./ C3;                                              // do-while-(CONST)              1x fDIV
    
     // ------------------------------------------------------------// -----------------------------------------------------------------------------------------------
        if ( is_call ) {                                            // do-while-RE-TESTING: AVOIDED REPETITIVE per-loop COSTS of TESTING THE VERY THE SAME
                                                                    // -----------------------------------------------------------------------------------------------
                  do {
                        mid   = ( high + low ) / 2;       // cheapest  do-while per-loop-[VAR]-update
                        vol_  = mid + 0.000000000000001; //  cheapest  do-while per-loop-[VAR]-update
                        vol_2 = vol_ * vol_;            //   cheapest  do-while per-loop-[VAR]-update
    
                     /* ---------------------------------------------------------------------------------------------------------------------------------------------
                        HAS EVOLVED FROM THE ORIGINAL FORMULATION + AVOIDED REPETITIVE per-loop COSTS of 20+ expensive float OPs fully wasted,all in do-while-(CONST)
                                                                  + AVOIDED REPETITIVE per-loop COSTS of all the CALL fun() STACK MANIPULATIONS AND RELATED OVERHEADS
                        ---------------------------------------------------------------------------------------------------------------------------------------------
                     */ 
                     // V4d1  =         ( C1 +   C2               * vol_2      ) / vol_ / C3;                            // [VAR]-dependent updates per loop
                     // V5d2  =         ( C1 + ( C2 - ttm_ )      * vol_2      ) / vol_ / C3;                            // [VAR]-dependent updates per loop
                     // Vmd2  =         (           ( ttm_ - C2 ) * vol_2 - C1 ) / vol_ / C3;                            // [VAR]-dependent updates per loop
                     // 
                     // V4d1  = U3inv * ( C1 +                 C2 * vol_2      ) / vol_;                                 // fMUL faster than fDIV + a few more fUtilityCONSTs
                     // V5d2  = U3inv * ( C1 +                 U4 * vol_2      ) / vol_;                                 // fMUL faster than fDIV + a few more fUtilityCONSTs
                     // Vmd2  = U3inv * (                      U5 * vol_2 - C1 ) / vol_;                                 // fMUL faster than fDIV + a few more fUtilityCONSTs
                     // 
                     // ---------------------------------------------------------------------------------------------------
                     // THIS AVOIDS RE-CALCULATION OF ALL do-while-(CONST)s BY THEIR RE-USE :
                     //
                     // if ( option_price < GBlackScholes_WHILEd( vol_,       // <----------[VAR] input_vol,             // GBlackScholes( future_price,
                     //                                           vol_ * C3,  // <----------[VAR] V1,                    //                strike,
                     //                                           is_call,    //                  is_call,               //                ttm,
                     //                                           ttm_,       // do-while-(CONST) ttm_,                  //                risk_rate,
                     //                                           C1, C2, C3, // do-while-(CONST) C1, C2, C3,            //                b_rate,
                     //                                           R1, R2      // do-while-(CONST) R1, R2                 //                mid,         // == (high+low)/2,
                     //                                           )           //                                         //                is_call
                     //      ) ...                                            //                                         //                )
                     //
                     // -------------------------------------------------------------------------------------------------- 
                     // EVEN BETTER :
                     //
                     // if ( option_price < ( is_call ? ( R1 * cdfNormal( 0, 1,  U3inv * ( C1 + C2 * vol_2      ) / vol_ )
                     //                                 - R2 * cdfNormal( 0, 1,  U3inv * ( C1 + U4 * vol_2      ) / vol_ )
                     //                                   )
                     //                               : ( R2 * cdfNormal( 0, 1,  U3inv * (      U5 * vol_2 - C1 ) / vol_ )
                     //                                 - R1 * cdfNormal( 0, 1, -U3inv * ( C1 + C2 * vol_2      ) / vol_ )
                     //                                   )
                     //                       )
                     // 
                     //     ) ...
                     // ________________________________( CALL-OPTIONs )__________________________________________________
    
                        if ( option_price < (           ( R1 * cdfNormal( 0, 1,  U3inv * ( C1 + C2 * vol_2      ) / vol_ )
                                                        - R2 * cdfNormal( 0, 1,  U3inv * ( C1 + U4 * vol_2      ) / vol_ )
                                                          )
                                              )
                             ) {  high = mid;                             // == (high+low)/2;  // LOWER    HI-SIDE BRACKET
                        } else {   low = mid;                             // == (high+low)/2;  // HEIGHTEN LO-SIDE BRACKET
                        }
                  } while ( ( high - low ) > 0.00001 );
                  return    ( high + low ) / 2; // ________________________________________________________________ JIT/RET
        } else {  
                  do {  mid   = ( high + low ) / 2;       // cheapest  do-while per-loop-[VAR]-update
                        vol_  = mid + 0.000000000000001; //  cheapest  do-while per-loop-[VAR]-update
                        vol_2 = vol_ * vol_;            //   cheapest  do-while per-loop-[VAR]-update
    
                     // ________________________________( PUT-OPTIONs )___________________________________________________
    
                        if ( option_price < (           ( R2 * cdfNormal( 0, 1,  U3inv * (      U5 * vol_2 - C1 ) / vol_ )
                                                        - R1 * cdfNormal( 0, 1, -U3inv * ( C1 + C2 * vol_2      ) / vol_ )
                                                          )
                                              )
                             ) {  high = mid;                             // == (high+low)/2;  // LOWER    HI-SIDE BRACKET
                        } else {   low = mid;                             // == (high+low)/2;  // HEIGHTEN LO-SIDE BRACKET
                        }
                  } while ( ( high - low ) > 0.00001 );
                  return    ( high + low ) / 2; // ________________________________________________________________ JIT/RET
        }
    }
    

    【讨论】:

      【解决方案2】:

      DolphinDB 1.01 引入了即时编译 (JIT)。 JIT 非常适合非向量化操作,例如隐含波动率的二分搜索。

      要使用 JIT,只需在用户定义的函数之前添加一个符号 @jit。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-28
        • 2011-08-06
        相关资源
        最近更新 更多