基于强化学习的期权量化交易回测系统3(转载)

在本篇博文中,我们将获取50EFT期权的日行情数据和50ETF的日行情数据,作为环境的状态数据,可以在强化学习环境SopEnv中逐日显示出来。

数据集对象定义
我们定义50ETF日行情数据集类D50etfDataset,在其中统一管理50ETF行情和50ETF期权行情数据以及Greeks信息、VIX恐慌指数等信息。D50etfDataset是PyTorch中Dataset的子类,该类中有两个我们必须提供实现代码的方法,分别为__len__获取数据集中样本数量,__getitem__获取指定索引的样本数据,大家看到这两个方法都是双下划线开头,表明我们通常不使用这两个方法,而是通过后面要介绍的DataLoader来获取数据集中的数据。我们这里先给出一个这个类的实现框架:

import numpy as np
import torch
import torch.utils.data.dataset as Dataset

class D50etfDataset(Dataset.Dataset):
def init(self):
self.X, self.y = self._load_dataset()

def __len__(self):
    return self.X.shape[0]

def __getitem__(self, index):
    return self.X[index], self.y[index]

def _load_dataset(self):
    X = np.array([
        [1.1, 1.2, 1.3, 1.4, 1.5],
        [2.1, 2.2, 2.3, 2.4, 2.5],
        [3.1, 3.2, 3.3, 3.4, 3.5],
        [4.1, 4.2, 4.3, 4.4, 4.5],
        [5.1, 5.2, 5.3, 5.4, 5.5],
        [6.1, 6.2, 6.3, 6.4, 6.5],
        [7.1, 7.2, 7.3, 7.4, 7.5],
        [8.1, 8.2, 8.3, 8.4, 8.5],
        [9.1, 9.2, 9.3, 9.4, 9.5]
    ])
    y = np.array([1, 1, 1, 0, 0, 0, 1, 1, 1])
    return torch.from_numpy(X), torch.from_numpy(y)

在这里我们在_load_dataset方法中,本来是要从DataSource类中从取出日行情数据来进行初始化,但是这里用写死的简单数据来进行试验。
我们编写一个单元测试用例,来测试一下这个数据集类:

import unittest
import torch.utils.data.dataloader as DataLoader
from apps.sop.d_50etf_dataset import D50etfDataset

class TD50etfDataset(unittest.TestCase):
@classmethod
def setUp(cls):
pass

@classmethod
def tearDown(cls):
    pass

def test_getitem(self):
    ds = D50etfDataset()
    dataloader = DataLoader.DataLoader(ds, batch_size= 2, 
                shuffle = True, num_workers= 4)
    for idx, (X, y) in enumerate(dataloader):
        print('{0}: {1} => {2}; {3};'.format(idx, X, y, type(y)))
    print('数量:{0};'.format(ds.__len__()))
    X, y = ds.__getitem__(3)
    print('样本:{0} => {1};'.format(X, y))

运行结果如下所示:

test_getitem (uts.apps.sop.t_d_50etf_dataset.TD50etfDataset) … 0: tensor([[9.1000, 9.2000, 9.3000, 9.4000, 9.5000],
[3.1000, 3.2000, 3.3000, 3.4000, 3.5000]], dtype=torch.float64) => tensor([1, 1], dtype=torch.int32); <class ‘torch.Tensor’>;
1: tensor([[5.1000, 5.2000, 5.3000, 5.4000, 5.5000],
[4.1000, 4.2000, 4.3000, 4.4000, 4.5000]], dtype=torch.float64) => tensor([0, 0], dtype=torch.int32); <class ‘torch.Tensor’>;
2: tensor([[7.1000, 7.2000, 7.3000, 7.4000, 7.5000],
[1.1000, 1.2000, 1.3000, 1.4000, 1.5000]], dtype=torch.float64) => tensor([1, 1], dtype=torch.int32); <class ‘torch.Tensor’>;
3: tensor([[8.1000, 8.2000, 8.3000, 8.4000, 8.5000],
[6.1000, 6.2000, 6.3000, 6.4000, 6.5000]], dtype=torch.float64) => tensor([1, 0], dtype=torch.int32); <class ‘torch.Tensor’>;
4: tensor([[2.1000, 2.2000, 2.3000, 2.4000, 2.5000]], dtype=torch.float64) => tensor([1], dtype=torch.int32); <class ‘torch.Tensor’>;
数量:9;
样本:tensor([4.1000, 4.2000, 4.3000, 4.4000, 4.5000], dtype=torch.float64) => 0;
ok


Ran 1 test in 1.436s

OK

获取50ETF期权行情数据
我们要交易50ETF期权,就需要获取50ETF日行情数据,我们定义D50etfOptionDataSource类:

import numpy as np
import akshare as ak

class Sh50etfOptionDataSource(object):
CALL_OPTION_IDX = 0
PUT_OPTION_IDX = 1
CALL_OPTION = 101 # 认购期权
PUT_OPTION = 102 # 认沽期权

def __init__(self):
    self.refl = ''
    self.symbol = '50ETF'
    self.underlying = '510050'

def get_data(self):
    print('获取50ETF期权日行情数据')
    option_dict = {}
    expire_months = self.get_expire_months()
    option_codes = self.get_option_codes(expire_months[1])
    dates_set = set()
    for option_code in option_codes[Sh50etfOptionDataSource.\
                    CALL_OPTION_IDX]:
        option_dict[option_code] = self.get_option_daily_quotation(
            option_code, Sh50etfOptionDataSource.CALL_OPTION
        )
    for option_code in option_codes[Sh50etfOptionDataSource.\
                    PUT_OPTION_IDX]:
        option_dict[option_code] = self.get_option_daily_quotation(
            option_code, Sh50etfOptionDataSource.PUT_OPTION
        )
    return option_dict

def get_expire_months(self):
    ''' 获取合约到期月份 '''
    return ak.option_sina_sse_list(
                symbol=self.symbol, exchange="null")

def get_option_codes(self, trade_date):
    '''
    获取指定月份的期权合约列表
    '''
    return ak.option_sina_sse_codes(trade_date=trade_date,
                 underlying=self.underlying)

def get_option_daily_quotation(self, option_code, option_type):
    df = ak.option_sina_sse_daily(code=option_code)
    X = []
    dates = df['日期']
    opens = df['开盘']
    highs = df['最高']
    lows = df['最低']
    closes = df['收盘']
    volumes = df['成交']
    for i in range(len(dates)):
        if Sh50etfOptionDataSource.CALL_OPTION == option_type:
            X.append([
                dates[i], 0.0, 0.0, 0.0,
                opens[i], highs[i], 
                lows[i], closes[i], volumes[i]
            ])
        elif Sh50etfOptionDataSource.CALL_OPTION == option_type:
            X.append([
                dates[i], 1.0, 0.0, 0.0,
                opens[i], highs[i], 
                lows[i], closes[i], volumes[i]
            ])
    return np.array(X)

第17行:定义一个字典用来存50ETF行情数据,键值为合约编号,值为numpy的数组,每一行代表该天所有合约的行情数据;
第18行:获取合约到期月份列表,例如在8月调用时,会返回8月、9月、12月、次年3月;
第19行:由于次月到期的期权合约交易最活跃,因此我们只获取次月到期的期权合约编号列表;
第20行:我们的主循环是按日期进行循环的,我们将所有日期加入到date_set中,然后进行排序,作为系统日历的日期;
第21~25行:循环认购期权合约编号,以其为键,值为每一行的期权合约行情数据,其中第1列为日期,第2到4列为期权类型,目前0,0,0代表认购期权,1,0,0代表认沽期权,剩余列分别为:开盘、最高、最低、收盘、交易量数据;
第26~30行:同理处理认沽期权;
测试用例如下所示:
import unittest
from apps.sop.sh50etf_option_data_source import Sh50etfOptionDataSource

class TSh50etfOptionDataSource(unittest.TestCase):
@classmethod
def setUp(cls):
pass

@classmethod
def tearDown(cls):
    pass

def test_get_expire_months(self):
    ds = Sh50etfOptionDataSource()
    expire_months = ds.get_expire_months()
    print(expire_months)

def test_get_option_codes(self):
    ds = Sh50etfOptionDataSource()
    trade_date = '202009'
    option_contracts = ds.get_option_codes(trade_date)
    print(option_contracts)

def test_get_option_daily_quotation(self):
    ds = Sh50etfOptionDataSource()
    option_code = '10002423'
    X = ds.get_option_daily_quotation(option_code)
    print('X: {0};'.format(X.shape))
    print(X)

def test_get_data(self):
    ds = Sh50etfOptionDataSource()
    option_dict = ds.get_data()
    for key in option_dict.keys():
        ocs = option_dict[key]
        print(ocs)
        break

下面我们将所获取到数据转化为数据集形式,同时从其中找出日期列表,我们来定义Sh50etfDataset类:

class Sh50etfDataset(Dataset.Dataset):
def init(self):
self.X, self.y, self.r = self._load_dataset()

def __len__(self):
    return self.X.shape[0]

def __getitem__(self, index):
    return self.X[index], self.y[index], self.r[index]

def _load_dataset(self):
    d_50etf = Sh50etfOptionDataSource()
    option_dict = d_50etf.get_data()
    # 获取日期列表
    date_set = set()
    self.key_list = []
    for key in option_dict.keys():
        self.key_list.append(key)
        for oc in option_dict[key]:
            date_set.add(oc[0])
    self.dates = list(date_set)
    list.sort(self.dates, reverse=False)
    list.sort(self.key_list, reverse=False)
    raw_X = []
    for idx in range(len(self.dates)):
        date_row = []
        for key in self.key_list:
            oc = option_dict[key]
            if len(oc) > idx:
                date_row += [float(oc[idx][1]), float(oc[idx][2]), 
                            float(oc[idx][3]), float(oc[idx][4]), 
                            float(oc[idx][5]), float(oc[idx][5]),
                            float(oc[idx][6]), float(oc[idx][7])]
            else:
                date_row += [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
        raw_X.append(date_row)
    X = np.array(raw_X, dtype=np.float32)
    y = np.zeros((len(self.dates),))
    r = np.zeros((len(self.dates),))
    return torch.from_numpy(X), torch.from_numpy(y), torch.from_numpy(r)

第2~9行:继承自pytorch的Dataset基类,并提供了__len__和__getitem__方法实现;
第11行:定义调用Sh50etfOptionDataSource和Sh50etfIndexDataSource来获取数据,形成可以用于强化学习的数据集。我们在这里先介绍调用Sh50etfOptionDataSource类来获取期权数据;
第12、13行:调用Sh50etfOptionDataSource来获取如下格式的期权日行情数据:
{
期权合约编号1: [
‘2020-06-01’, 0.0, 0.0, 0.0, 1.1, 2.1, 3.1, 4.1, 888,
‘2020-06-02’, 0.0, 0.0,0.0, 1.1. 2.1, 3.1, 4.1, 888,

],

}

其为一个字典,键为期权合约编号,值为其日行情列表,每一行为一个日行情数据,其中第1列为日期,第2至4列代表是认购还是认沽合约,后面列依次为开盘、最高、最低、收盘、交易量,此处为原始数据,并没有进行归一化;

第15~23行:求出日期列表和合约列表,都进行从小到大排序;
第25~36行:将当天的所有期权合约行情数据,按照合约编号从小到大依次排列,作为一个数据集的样本;
第37行:将样本设计矩阵(Design Matrix)变为numpy数组;
第38行:y为监督学习的标签,在强化学习中代表所采取的行动;
第39行:对于强化学习,r代表在上一时间点采取行动后,环境返回给Agent的奖励信号;
第40行:以Tensor对象形式返回样本集、标签集(行动集)和奖励集;
测试程序如下所示:

class TSh50etfDataset(unittest.TestCase):
@classmethod
def setUp(cls):
pass

@classmethod
def tearDown(cls):
    pass

def test_getitem(self):
    ds = Sh50etfDataset()
    dataloader = DataLoader.DataLoader(ds, batch_size= 2, 
                shuffle = True, num_workers= 4)
    for idx, (X, y) in enumerate(dataloader):
        print('{0}: {1} => {2}; {3};'.format(idx, X, y, type(y)))
    print('数量:{0};'.format(ds.__len__()))
    X, y = ds.__getitem__(3)
    print('样本:{0} => {1};'.format(X, y))

def test__load_dataset(self):
    ds = Sh50etfDataset()
    print('X: {0};'.format(ds.X.shape))
    print('y: {0};'.format(ds.y.shape))

运行命令

python -m unittest uts.apps.sop.t_sh50etf_dataset.TSh50etfDataset.test__load_data -v

运行结果如下所示:

test__load_dataset (uts.apps.sop.t_sh50etf_dataset.TSh50etfDataset) … 获取50ETF期权日行情数据
X: torch.Size([141, 368]);
y: torch.Size([141]);
ok

这表示我们共有141个日期,每个日期共有368个期权合约的行情数据。

更新环境主循环
我们现在终于有真实的行情数据,我们现在来修改一下主循环的逻辑,依次显示各个交易日,如下所示:

class SopEnv(gym.Env):
def startup(self, args={}):
self.ds = Sh50etfDataset()
self.reset()
obs, reward, done, info = self._next_observation(), 0, False, {}
for dt in self.ds.dates:
print(’{0}: ‘.format(dt))
action = {}
obs, reward, done, info = self.step(action)
X = obs[‘X’].cpu().numpy()
y = obs[‘y’].cpu().numpy()
r = obs[‘r’].cpu().numpy()
print(’ X:{0}; y:{1}; r:{2}’.format(X.shape,
y, r
))
self.tick += 1

def _next_observation(self):
    X, y, r = self.ds.__getitem__(self.tick)
    return {'X': X, 'y': y, 'r': r}

运行程序可以得到如下结果:

重置环境到初始状态
2020-01-23:
X:(368,); y:0.0; r:0.0
2020-02-03:
X:(368,); y:0.0; r:0.0

我们目前获取了期权合约的行情数据,在实际应用中,我们需要获取期权合约的Black-Scholes估计价格、恐慌指数VIX和希腊字母Greeks信息。由于Black-Scholes和VIX需要额外计算,但是可以直接获取到Greeks信息,因此我们可以把Greeks加入到每个合约的行情数据后面。这个可以作为系统未来的一个可以扩充的功能点,我们在这里就先不实现了。
在下一篇博文中,我们将获取50ETF期权的标的物50ETF指数日行情数据,并且将其加入到合约日行情数据中。

原文链接:https://blog.youkuaiyun.com/Yt7589/article/details/108140652?utm_medium=distribute.pc_feed.none-task-blog-personrec_tag-10.nonecase&depth_1-utm_source=distribute.pc_feed.none-task-blog-personrec_tag-10.nonecase&request_id=5f4726f2cea070620e94000f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值