数据是量化交易的源头,如何高效地管理大量数据是非常关键的环节,数据库是最佳的解决方案之一,现如今数据库的应用已经是各类日内交易、高频交易等策略的量化标准配置。本篇我们来研究一下优宽量化(youquant.com)内置的数据库,内容包括:如何创建数据表、存储数据、修改数据、删除数据、引用数据以及如何应用于实战。
熟悉优宽量化平台的应该知道,在这之前要想把数据保存到本地复用,只能用_G()函数,每次停止策略时,_G()函数会自动保存需要的信息。但如果想要保存更多更复杂的格式化数据,_G()函数显然就不太适用了,于是很多人想到了自建数据库来解决这个问题。
提到自建数据库,想必大家能想到Oracle、MySQL、KDB、OneTick、NoSQL…这些都是非常优秀的企业级别的应用,不管是功能还是性能都非常强大。但也面临几个问题:上手难度大,配置繁琐维护起来麻烦,这对于量化交易散户来说,有点用大炮打苍蝇的感觉,即使上手也只是用到很少一部分功能。
接下来让我们认识一下优宽量化内置的轻型数据库,DBExec是优宽量化内置的一个关系型数据管理系统接口,基于SQLite开发,其本身是用C写的,不但体积小巧,占用资源低,而且处理速度快,非常适合用于金融量化分析爱好者在本地实现数据管理,因为可以将不同的“对象”(例如交易所,数据源,价格)分成不同的表,并在表之间定义关系。此外用户无需单独安装和配置,只要调用DBExec()函数就可以直接使用!
另外,SQLite语言的学习成本很低,在数据库上执行的大部分工作都由SQLite语句完成。熟悉基本语法就能满足大部分需求,以下是SQLite的基础语法。
SQLite的语法是不区分大小写的,不过有一些命令对大小写敏感,如GLOB和glob代表不同的含义。SQLite语句可以以任何关键字开始,如SELECT、INSERT、UPDATE、DELETE、ALTER、DROP等,它们分别表示:提取数据、插入数据、更新数据、删除数据、修改数据库、删除数据表。所有的语句以英文分号结束。下面是一个简单的数据库创建、增、删、改、查等操作:
function main() {
// 创建:如果“users”表不存在就创建一个,“id”是整数且自动增加,“name”是文本形式且不为空
Log(DBExec('CREATE TABLE IF NOT EXISTS "users" (id INTEGER PRIMARY KEY AUTOINCREMENT, name text not NULL);'));
// 增加:
Log(DBExec("INSERT INTO users(name) values('张三')"));
Log(DBExec("INSERT INTO users(name) values('李四')"));
// 删除:
Log(DBExec("DELETE FROM users WHERE id=1;"));
// 修改
Log(DBExec("UPDATE users SET name='王五' WHERE id=2"));
// 查询
Log(DBExec('select 2, ?, ?, ?, ?', 'ok', true,9.8,null));
Log(DBExec('select * from kvdb'));
Log(DBExec('select * from cfg'));
Log(DBExec('select * from log'));
Log(DBExec('select * from profit'));
Log(DBExec('select * from chart'));
Log(DBExec("selEct * from users"));
}
一个数据库通常包含一个或多个表,每个表都有一个名字标识,需要注意的是系统保留表分别: kvdb, cfg, log, profit, chart。也就是说在创建表时,应该避开系统保留的名字。让我们运行上面的代码,输出以下内容:
了解了SQLite的基础语法,我们趁热打铁使用优宽量化内置的数据库,创建一个收集和使用Tick数据的实例。
第一步:更新托管者 首先确保您使用的是最新版本的托管者,如果之前下载使用过托管者,需要先删除,并在 https://www.youquant.com/m/add-node 页面重新下载和部署。
第二步:创建策略
function main() {
// 订阅合约
_C(exchange.SetContractType, 'swap');
// 创建数据表
DBExec('CREATE TABLE IF NOT EXISTS "tick" (id INTEGER PRIMARY KEY AUTOINCREMENT,'.concat(
'High FLOAT not NULL,',
'Low FLOAT not NULL,',
'Sell FLOAT not NULL,',
'Buy FLOAT not NULL,',
'Last FLOAT not NULL,',
'Volume INTEGER not NULL,',
'Time INTEGER not NULL);'
));
// 获取10个tick数据
while (true) {
let tick = exchange.GetTicker();
// 在tick表中增加数据
DBExec(`INSERT INTO tick(High, Low, Sell, Buy, Last, Volume, Time) values(${tick.High}, ${tick.Low}, ${tick.Sell}, ${tick.Buy}, ${tick.Last}, ${tick.Volume}, ${tick.Time})`);
// 查询所有数据
let allDate = DBExec('select * from tick');
if (allDate.values.length > 10) {
break;
}
Sleep(1000);
}
// 查询所有数据
Log(DBExec('select * from tick'));
// 查询第一个数据
Log(DBExec('select * from tick limit 1'));
// 查询前两个数据
Log(DBExec('select * from tick limit 0,2'));
// 删除第一个数据
Log(DBExec('DELETE FROM tick WHERE id=1;'));
// 修改第二个数据
Log(DBExec('UPDATE tick SET High=10000 WHERE id=2'));
// 查询所有数据
let allDate = DBExec('select * from tick')
Log(allDate);
}
第三步:运行策略 以Windows为例,运行策略之后,会在托管者目录的“\logs\storage”目录中生成一个以机器人编号命名的文件夹,打开该文件夹,里面有一个以“.db3”作为后缀的文件,这个文件就是优宽量化内置数据库的文件。以下图所示: 上面的代码首先创建了一个以“tick”命名的数据表,然后给该表添加tick数据字段,接着在循环中从交易所获取tick数据,并把这些数据插入到“tick”数据表中,同时判断该数据表中的数据量超过10个就跳出循环。最后分别用5个SQLite命令查询、删除、修改数据表中的数据。并在日志中打印出来,以下图所示: 第四步:创建状态栏 最后我们增加一些代码,通过获取优宽量化数据库中的数据,给策略创建一个状态栏,把数据更直观的展示出来,新增代码如下:
// 创建状态栏
let table = {
type: 'table',
title: '币安Tick数据',
cols: allDate.columns,
rows: allDate.values
}
LogStatus('`' + JSON.stringify(table) + '`');
上面的代码通过数据库中的数据,创建了一个“币安Tick数据”表。其中数据库中的“columns”字段代表状态栏中的“行”,“values”字段代表状态栏中的“列”。如下图所示:
数据库不仅可以承载海量数据,更能承载众多量化交易爱好者的宽客梦想。对于数据库的使用绝非仅限于本文的例子,更多使用方法可以参考SQLite教程,以及优宽量化后续推出的系列文章。
/*backtest start: 2020-07-19 00:00:00 end: 2020-08-17 23:59:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}] */ function main() { // 订阅合约 _C(exchange.SetContractType, 'rb2201'); // 创建数据表 DBExec('CREATE TABLE IF NOT EXISTS "tick" (id INTEGER PRIMARY KEY AUTOINCREMENT,'.concat( 'High FLOAT not NULL,', 'Low FLOAT not NULL,', 'Sell FLOAT not NULL,', 'Buy FLOAT not NULL,', 'Last FLOAT not NULL,', 'Volume INTEGER not NULL,', 'Time INTEGER not NULL);' )); // 获取10个tick数据 while (true) { let tick = exchange.GetTicker(); if (!tick) continue // 在tick表中增加数据 DBExec(`INSERT INTO tick(High, Low, Sell, Buy, Last, Volume, Time) values(${tick.High}, ${tick.Low}, ${tick.Sell}, ${tick.Buy}, ${tick.Last}, ${tick.Volume}, ${tick.Time})`); // 查询所有数据 let allDate = DBExec('select * from tick'); if (allDate.values.length > 10) { break; } Sleep(1000); } // 查询所有数据 Log(DBExec('select * from tick')); // 查询第一个数据 Log(DBExec('select * from tick limit 1')); // 查询前两个数据 Log(DBExec('select * from tick limit 0,2')); // 删除第一个数据 Log(DBExec('DELETE FROM tick WHERE id=1;')); // 修改第二个数据 Log(DBExec('UPDATE tick SET High=10000 WHERE id=2')); // 查询所有数据 let allDate = DBExec('select * from tick') Log(allDate); // 创建状态栏 let table = { type: 'table', title: '螺纹钢Tick数据', cols: allDate.columns, rows: allDate.values } LogStatus('`' + JSON.stringify(table) + '`'); }