如何提高在 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 搭建一个高性能因子计算平台
如何对窗口中的某个字段进行排序以获得前 N 个值,并对 DolphinDB 中的相应字段进行聚合计算?