如何提高在 DolphinDB 中计算希腊语的性能?

Posted

技术标签:

【中文标题】如何提高在 DolphinDB 中计算希腊语的性能?【英文标题】:How to improve performance in calculating greeks in 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;

【问题讨论】:

【参考方案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(其中一些相当昂贵)每个 dowhile-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。

【讨论】:

以上是关于如何提高在 DolphinDB 中计算希腊语的性能?的主要内容,如果未能解决你的问题,请参考以下文章

用 DolphinDB 和 Python Celery 搭建一个高性能因子计算平台

更强大更灵活更全面丨一文搞懂DolphinDB窗口计算

干货丨Orca对DolphinDB分布式表的操作

如何对窗口中的某个字段进行排序以获得前 N 个值,并对 DolphinDB 中的相应字段进行聚合计算?

如何将输出时间点替换为 DolphinDB 中窗口的结束点?

干货附代码|大数据分析语言DolphinDB脚本语言概述