标星★置顶公众号 爱你们
作者:Yi Dong 编译:1+1=6
1
前言
在金融领域,计算效率有时可以直接转化为交易利润。量化分析师面临着在研究效率和计算效率之间进行权衡的挑战。使用Python可以生成简洁的研究代码,从而提高了研究效率。但是,一般的Python代码速度很慢,不适合用于生产环境。在这篇文章中,我们将探索如何使用Python的GPU库来高性能实现奇异期权定价领域遇到的问题。
2
定价计算概述
Black-Scholes模型可以有效地用欧洲行权规则为plain vanilla定价。像障碍(Barrier)期权和篮子(Basket )期权这样的期权具有复杂的结构。蒙特卡罗模拟是一种有效的定价方法。为了得到一个精确的价格和一个小的变动,你需要许多模拟路径,计算十分密集。
幸运的是,每个模拟路径都是独立的,大家可以利用多核NVIDIA GPU在一个节点内加速计算,甚至在必要时将其扩展到多个服务器。由于独立路径的并行化,使用GPU可以将计算速度提高几个数量级。
传统上,对GPU的蒙特卡罗仿真是在CUDA C/ C++代码中实现的。大家必须明确地管理内存并编写大量样板代码,这对代码维护和生产效率提出了挑战。
最近,Deep Learning Derivatives(Ryan et al,2018)的论文被引入到使用深度神经网络来近似期权定价模型。该方法利用计算时间与推理时间进行定价训练,与GPU上的蒙特卡罗模拟相比,它实现了额外的数量级加速,这使得在生产环境中的实时奇异期权定价成为一个现实目标。
在这篇文章中介绍的方法对奇异期权类型没有任何限制。它适用于任何可以用蒙特卡罗方法模拟的期权定价模型。
在不失一般性的情况下,大家可以使用亚式障碍期权作为一个示例。亚式障碍期权是亚式期权和障碍期权的混合。衍生品价格取决于标的资产价格S、执行价格K和障碍价格B的平均值。以上下看涨期权离散化亚洲障碍期权为例。
- 如果标的资产的平均价格低于这一水平,则该期权无效。
- 资产现货价格S通常在建模中被认为是属于几何布朗运动,它有三个参数:现货价格、波动率和漂移率。
- 期权的价格是到期时的预期利润相对于当前价值的折现。
- 期权的路径依赖性使得对期权价格的解析解成为不可能。
这是使用蒙特卡罗模拟定价的一个很好的示例。你需要一个至少16GB的GPU来复现这个结果。
3
第1部分:使用GPU Python库进行蒙特卡洛定价
NVIDIA GPU被设计用来使用大量线程进行并行计算。蒙特卡罗仿真是在GPU中可以很好加速的算法之一。在下面的小节中,大家将看到在传统的CUDA代码中使用蒙特卡罗模拟,然后在Python中使用不同的库实现相同的算法。
CUDA方法
传统上,蒙特卡罗期权定价是在CUDA C/ C++中实现的。下面的CUDA C/ C++代码示例使用蒙特卡罗方法计算期权价格:
[code]#include
#include
#include
#include
#include
#include
#include
#define CHECKCURAND(expression) \
{ \
curandStatus_t status = (expression); \
if (status != CURAND_STATUS_SUCCESS) { \
std::cerr >=1)
{
__syncthreads();
if (tid < s) smdata[tid] += smdata[tid + s];
}
if (tid == 0)
{
atomicAdd(mysum, smdata[0]);
}
}
__global__ void barrier_option(
float *d_s,
const float T,
const float K,
const float B,
const float S0,
const float sigma,
const float mu,
const float r,
const float * d_normals,
const long N_STEPS,
const long N_PATHS)
{
unsigned idx = threadIdx.x + blockIdx.x * blockDim.x;
unsigned stride = blockDim.x * gridDim.x;
const float tmp1 = mu*T/N_STEPS;
const float tmp2 = exp(-r*T);
const float tmp3 = sqrt(T/N_STEPS);
double running_average = 0.0;
for (unsigned i = idx; i= 2) N_PATHS = atoi(argv[1]);
if (argc >= 3) N_STEPS = atoi(argv[2]);
const float T = 1.0f;
const float K = 110.0f;
const float B = 100.0f;
const float S0 = 120.0f;
const float sigma = 0.35f;
const float mu = 0.1f;
const float r = 0.05f;
double gpu_sum{0.0};
int devID{0};
cudaDeviceProp deviceProps;
checkCudaErrors(cudaGetDeviceProperties(&deviceProps, devID));
printf("CUDA device [%s]\n", deviceProps.name);
printf("GPU Device %d: \"%s\" with compute capability %d.%d\n\n", devID, deviceProps.name, deviceProps.major, deviceProps.minor);
// Generate random numbers on the device
curandGenerator_t curandGenerator;
CHECKCURAND(curandCreateGenerator(&curandGenerator, CURAND_RNG_PSEUDO_MTGP32));
CHECKCURAND(curandSetPseudoRandomGeneratorSeed(curandGenerator, 1234ULL)) ;
const size_t N_NORMALS = (size_t)N_STEPS * N_PATHS;
float *d_normals;
checkCudaErrors(cudaMalloc(&d_normals, N_NORMALS * sizeof(float)));
CHECKCURAND(curandGenerateNormal(curandGenerator, d_normals, N_NORMALS, 0.0f, 1.0f));
cudaDeviceSynchronize();
// before kernel launch, check the max potential blockSize
int BLOCK_SIZE, GRID_SIZE;
checkCudaErrors(cudaOccupancyMaxPotentialBlockSize(&GRID_SIZE,
&BLOCK_SIZE,
barrier_option,
0, N_PATHS));
std::cout |
|