期货量化交易FutureQuant开发日志

商业模式,量化交易软件,按时计费给用户用,软件内卖策略,卖解锁功能

一、开发环境

建立工程

1.1 VisualStudio2017

选择vs2017,原因是它支持python。在c++项目中,需要修改头文件库,链接器附加库目录和链接器输入选项。

1.2 winpython安装额外库

python -m pip install -U beautifulsoup4

1.3MFC组织结构

在线文档:https://docs.microsoft.com/zh-cn/cpp/mfc/reference/cwnd-class?view=msvc-150

VC++深入详解

1.4 MYSQ5.6安装选项

安装32位的版本,默认配置文件my-default.ini

# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.6/en/server-configuration-defaults.html
# *** DO NOT EDIT THIS FILE. It's a template which will be copied to the
# *** default location during install, and will be replaced if you
# *** upgrade to a newer version of MySQL.

[mysqld]

# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M

# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin

# These are commonly set, remove the # and set as required.
# basedir = .....
# datadir = .....
# port = .....
# server_id = .....


# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M 

sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES 

MYSQL Server 官方介绍 :https://dev.mysql.com/doc/refman/8.0/en/mysqld-server.html

MYSQL 命令行选项 :5.1.6 Server Command Options

1.5 sqlite安装测试

下载sqlite3.exe并加入环境变量

SQLite 教程
	PyRun_SimpleString("import sqlite3");
	PyRun_SimpleString("conn = sqlite3.connect('test.db')");

SQLite – Python介绍

二、相关函数和问题说明

2.1 CreateWindow函数原型声明:

HWND CreateWindow(
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu or child-window identifier
HANDLE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

CreateWindow 函数的参数x,y,nWidth,nHeight 分别指定窗口左上角的x,y 坐标,窗口的宽度,高度。如果参数x 被设为CW_USEDEFAULT,那么系统为窗口选择默认的左上角坐标并忽略y 参数。如果参数nWidth 被设为CW_USEDEFAULT,那么系统为窗口选择默认的宽度和高度,参数nHeight 被忽略。

2.2 消息映射

参考:MFC消息映射BEGIN_MESSAGE_MAP详解

const AFX_MSGMAP* theClass::GetMessageMap() const 
{ 
	return GetThisMessageMap(); 
} 
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() 
{ 
		typedef theClass ThisClass;						   
		typedef baseClass TheBaseClass;					   
		static const AFX_MSGMAP_ENTRY _messageEntries[] = {/*一系列映射信息。。。。。*/{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } }; 
		static const AFX_MSGMAP messageMap = 
		{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; 
		return &messageMap; 
}	
————————————————
版权声明:本文为CSDN博主「梦之安魂曲」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hello_shadow/article/details/7296593

PASCAL 是约定一种调用方式,这里有必要说明一下函数调用的方式,标准的C语言是调用者处理堆栈,而WINDOWS API函数是被调用的函数负责处理调用堆栈。PASCAL是一种函数调用的方式,确定参数压栈的顺序,还有由谁来恢复堆栈。

2.3 SDI框架

	// 注册应用程序的文档模板。  文档模板
	// 将用作文档、框架窗口和视图之间的连接
	CSingleDocTemplate* pDocTemplate;
	pDocTemplate = new CSingleDocTemplate(
		IDR_MAINFRAME,
		RUNTIME_CLASS(CFutureQuantSingleDoc),
		RUNTIME_CLASS(CMainFrame),       // 主 SDI 框架窗口
		RUNTIME_CLASS(CFutureQuantSingleView));
	if (!pDocTemplate)
		return FALSE;
	AddDocTemplate(pDocTemplate);
//此段代码来源:以FutureQuantSingle为名建立的MFC类型程序,FutureQuantSingle.cpp源文件

想自如地调用函数,首先要知道SDI窗口的基本框架。类向导创建的程序框架中无非也就是帮你建了4个窗口类(应用程序类,Frame框架类,文档类,View视图类),4个对象(应用程序对象,框架窗口类对象,文档对象,视图对象),大致是一个类对应一个实例对象。

编写代码其实就是在设计类(设计成员变量及成员函数),对程序来说相当于指明了规则,程序在运行时先按照我们写的类的框架生成实体化的对象,然后对象再按照我们的规则来运行。

2.4 LPCWSTR

原型

typedef const wchar_t* LPCWSTR;

初始化

LPWSTR lp = TEXT("asdfasgaf");

2.5在MainFrame中显示按钮

在头文件MainFrm.h中,添加:

private:
	CButton m_btn;

也可以通过在“资源视图”当中,通过右键,类向导添加成员变量。

另外在对应的c文件中,加入如下代码:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){
    ……
	LPCWSTR a = TEXT("按钮");
	m_btn.Create(a,WS_CHILD|BS_DEFPUSHBUTTON,CRect(50,50,250,150),this,123);
	m_btn.ShowWindow(SW_SHOWNORMAL);
    ……
}

2.6 c++调用python

请看前文《转载:C++(vs2017)调用python》

安装一个独立的python环境,或者使用WAMP Server类似的集成环境。我这次选择的是winpython。还要注意一点,python和vc++的架构要保持一致,都是x86或者都是x64。

#include <Python.h>	
	if (0 == Py_IsInitialized()) {
		Py_SetPythonHome(L"C:\\WPy-3710\\python-3.7.1");
	}
	Py_Initialize();
	PyRun_SimpleString("import sys,requests,json,pandas");
	PyRun_SimpleString("from bs4 import BeautifulSoup");
	PyRun_SimpleString("data = requests.get('http://stock2.finance.sina.com.cn/futures/api/json.php/IndexService.getInnerFuturesMiniKLine5m?symbol=M1905').content");
	//PyRun_SimpleString("r=requests.get('http://quote.eastmoney.com/qihuo/im.html');");
	//PyRun_SimpleString("print(data)");
	PyRun_SimpleString("fo=open('C://Users//T430//Documents//Visual Studio 2017//self_project//cpptopython//cpptopython//foo.txt', 'w')");
	PyRun_SimpleString("pd=pandas.DataFrame(json.loads(data))");
	PyRun_SimpleString("fo.write(pd.to_string())");
	PyRun_SimpleString("fo.close()");
	Py_Finalize();

使用c++调用python:浅析 C++ 调用 Python 模块c++调用python numpy编程

python官方提供的调用方式介绍:1. Embedding Python in Another Application¶

另外关于引用计数:python 中的引用计数(reference counts)

2.6.1报错:Fatal Python error: initfsencoding: unable to load the file system codec

报此错误即python解释器找不到codec模块。原因大多数是机器上安装了多个python,而运行时的环境却没有指定python的home目录。解决办法即实现上面代码中的

Py_SetPythonHome(L"C:\\WPy-3710\\python-3.7.1");

这一行。

2.7 新浪行情爬取、东方财富行情爬取

参见前文《转载:新浪期货数据接口 API 实时数据/历史数据》

网易:Python爬虫抓取网易股票数据并实现MySQL数据库存储原文链接

东方财富:简单爬虫:东方财富网股票数据爬取(python_017)

关于三个网站的数据总结,网易没有期货数据,pass;东方财富有当日分钟数据,但k线历史数据是一个图片,pass;只有新浪是有k线历史数据,可以用作策略回测。

2.8 行情爬取存入数据库

// cpptopython.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
static int callback(void *data, int argc, char **argv, char **azColName) {
	int i;
	fprintf(stderr, "%s: ", (const char*)data);
	for (i = 0; i < argc; i++) {
		printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
	}
	printf("\n");
	return 0;
}

int main()
{
	
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;
	char *sql;
	const char* data = "Callback function called";


	rc = sqlite3_open("test.db", &db);

	if (rc) {
		fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
		exit(0);
	}
	else {
		fprintf(stderr, "Opened database successfully\n");
	}
	sql = "create table sina (datetime varchar(30),species varchar(8),exchange varchar(8),interval varchar(4), open numeric,high numeric,low numeric, close numeric,volume numeric, constraint sina_key primary key (species,interval,datetime))";
	//sql = "alter table sina primary key(datetime, interval, species)";
	rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
	if (rc) {
		fprintf(stderr, sqlite3_errmsg(db));
		exit(0);
	}
	else {
		fprintf(stderr, "table created\n");
	}
	sqlite3_close(db);
		
	if (0 == Py_IsInitialized()) {
		Py_SetPythonHome(L"C:\\WPy-3710\\python-3.7.1");
	}
	Py_Initialize();
	PyRun_SimpleString("import sys,requests,json,pandas");
	PyRun_SimpleString("from bs4 import BeautifulSoup");
	PyRun_SimpleString("import sqlite3,pymysql");
	PyRun_SimpleString("conn = sqlite3.connect('test.db')");
	PyRun_SimpleString("data = requests.get('http://stock2.finance.sina.com.cn/futures/api/json.php/IndexService.getInnerFuturesMiniKLine5m?symbol=M1905').content");
	PyRun_SimpleString("fo=open('C://Users//T430//Documents//Visual Studio 2017//self_project//cpptopython//cpptopython//foo.txt', 'w')");
	PyRun_SimpleString("pd=pandas.DataFrame(json.loads(data))");
	PyRun_SimpleString("pd.columns=['datetime','open','high','low','close','volume']");
	PyRun_SimpleString("pd['exchange']='DCE'");
	PyRun_SimpleString("pd['interval']='5m'");
	PyRun_SimpleString("pd['species']='M1905'");
	PyRun_SimpleString("pd.to_sql('sina',conn,index=False,if_exists='append')");
	PyRun_SimpleString("fo.close()");
	Py_Finalize();
    std::cout << "Hello World!\n";
	
}

2.9 从sqlite数据库中读取行情数据

使用c++从sqlite数据库中读取数据,主要依赖于一个函数:sqlite3_exec,函数声明为

SQLITE_API int sqlite3_exec(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);

其中第三个参数callback为回调函数,第四个参数为callback函数第一个参数,它是一个指针,可用作数据传出。sqlite3_exec及其callback的声明和注释在文件sqlite3.h中。

callback函数的声明如下:

typedef int (*sqlite3_callback)(void*,int,char**, char**);

通过构造如下的类,实现从sqlite数据库中读取数据

class StructuredData {
public: 
	vector<double> Close;
	vector<double>* pClose = &Close;
	void getclose(sqlite3 *db); 
};

void StructuredData::getclose(sqlite3 *db) {
	char *sql;
	char *zErrMsg = 0;
	int rc;
	//sql = "select * from sina";
	sql = "SELECT * from sina where interval='5m' and datetime='2019-04-26 10:00:00'";
	printf("%s\n", sql);
	rc = sqlite3_exec(db, sql, get_close_callback, (void*)pClose, &zErrMsg);
	printf("last back %f\n",pClose->back());
}

回调函数get_close_callback为

int get_close_callback(void* data, int argc, char **argv, char **azColName) {
	int i;
	vector<double>* pClose = (vector<double>*)data;
	printf("get_close_callback called\n");
	for (i = 0; i < argc; i++) {
		printf("%s\n", azColName[i]);
		if (strcmp("close", azColName[i]) == 0) {
			double close_double = std::stod(argv[i]);
			pClose->push_back(close_double);
			printf("pClose->back() %f\n", pClose->back());
			//printf("pClose->end() %f\n", pClose->end());
			printf("content: %s = %f\n", azColName[i], close_double);
		}
	}
	return 0;
}

2.9.1 获取收盘价close升级为获取open,close,high,low四项

首先将这几项封装起来,形成一个class

class bardata {
public:
	double open;
	double close;
	double high;
	double low;
};
int get_bar_callback(void* data, int argc, char **argv, char **azColName) {
	int i;
	bardata bar;
	vector<bardata>* pBar = (vector<bardata>*)data;
	printf("get_bar_callback called\n");
	for (i = 0; i < argc; i++) {
		printf("%s\n", azColName[i]);
		if (strcmp("open", azColName[i]) == 0) {
			bar.open = std::stod(argv[i]);
		}
		else if(strcmp("close", azColName[i]) == 0)
		{
			bar.close = std::stod(argv[i]);
		}
		else if (strcmp("high", azColName[i]) == 0)
		{
			bar.high = std::stod(argv[i]);
		}
		else if (strcmp("low", azColName[i]) == 0) {
			bar.low = std::stod(argv[i]);
		}
	}
	pBar->push_back(bar);
	bardata Bar = bar;
	printf("pClose->back(),open: %f,close: %f,high %f,low: %f\n", Bar.open,Bar.close,Bar.high,Bar.low);
	printf("总共数目:%d\n",pBar->size());
	return 0;
}
class StructuredData {
public: 
	vector<double> Bar;
	vector<double>* pBar = &Bar;
	void getbar(sqlite3 *db); 
};
void StructuredData::getbar(sqlite3 *db) {
	char *sql;
	char *zErrMsg = 0;
	int rc;
	//sql = "select * from sina";
	//sql = "SELECT * from sina where interval='5m' and datetime='2019-04-26 10:00:00'";
	sql = "SELECT * from sina where interval='5m'";
	printf("%s\n", sql);
	rc = sqlite3_exec(db, sql, get_bar_callback, (void*)pBar, &zErrMsg);
	printf("last back %f\n",pBar->back());
}

2.10 获取系统时间

#include <windows.h>
#include <stdio.h>

void main()
{
    SYSTEMTIME st, lt;
    
    GetSystemTime(&st);
    GetLocalTime(&lt);
    
    printf("The system time is: %02d:%02d\n", st.wHour, st.wMinute);
    printf(" The local time is: %02d:%02d\n", lt.wHour, lt.wMinute);
}

2.11 行情接口的初始化

2.11.0 摘录自文档《CTP客户端开发指南》的3.2节行情初始化方法

行1&2

使用函数CreateFtdcMdApi 创建CThostFtdcMdApi 的实例。其中第一个参数是本地流文件生成的目录。流文件是行情接口或交易接口在本地生成的流文件,后缀名为.con。流文件中记录着客户端收到的所有的数据流的数量。第二个参数描述是否使用UDP 传输模式,true 表示使用UDP 模式,false 表示使用TCP 模式。如果开发者要使用组播行情,则需要使用该函数的第三个参数bIsMulticast。在第二个参数和第三个参数赋值均为true 时,行情将以组播的形式传输。

注意:组播行情只能在内网中使用。
流文件的详细介绍可参见“客户端开发注意事项”章节。

行3

然后创建SPI 实例。QCTPMdSpi 是笔者自己创建的实体类,继承了CThostFtdcMdSpi。

行4

向API 实例注册SPI 实例。

行5

向API 实例注册前置地址。前置地址的格式为:tcp://127.0.0.1:17001。tcp 字段是开始字符串(不是通讯模式,
不可更改),127.0.0.1 是托管服务器的行情前置地址,17001 是该行情前置的端口号。
注意: 综合交易平台的接口可以通过三种方式传输行情:TCP,UDP 和组播(Multicast)。同一个前置地
址可以使用三种传输模式中的任一种,而不需要为每个前置地址指定一个传输地址。

行6&7

初始化行情接口的工作线程。初始化之后,线程自动启动,并使用上一步中注册的地址向服务端请求建立连
接。
综合交易平台接口都有独立的工作线程。如果开发者在进行可视化程序的开发,请务必注意线程冲突的问题。

来源:CTP客户端开发指南(pdf)

2.11.1 编程错误: 无法解析的外部符号 “public: static class CThostFtdcMdApi *的解决办法。

遇到了这个问题,检查了链接库,资源文件中确实包含了thostmduserapi_se.dll和.lib等文件,还是出现这个错误。这时候需要检查一下编译器x86是否与库的位数一致。经检查不一致,即从官方包中重新取出x86版本,加入工程中,问题得到解决。

2.11.2 编程错误:RuntimeError:can not open CFlow file in line 256 of file ../../source/userapi/ThostFtdcUserApiImplBase.cpp的解决

大多数是路径没写对造成的,经过修改,变成如下设置即可

	CThostFtdcMdApi  *pUserMdApi = CThostFtdcMdApi::CreateFtdcMdApi("C://Users//T430//Documents//Visual Studio 2017//self_project//cpptopython//cpptopython//Release//flow//");

2.11.3 自己实现的行情初始化方法

单独跑一个线程,直接操作CThostFtdcMdApi类和CThostFtdcMdSpi类。

	CThostFtdcMdApi  *pMdApi = CThostFtdcMdApi::CreateFtdcMdApi("C://Users//T430//Documents//Visual Studio 2017//self_project//cpptopython//cpptopython//Release//mdflow//");
	CThostFtdcMdSpi *pMdSpi = new CThostFtdcMdSpi();
	pMdApi->RegisterSpi(pMdSpi);
	pMdApi->RegisterFront("tcp://180.168.146.187:10212");
	pMdApi->Init();

通过直接修改ThostFtdcMdApi.h文件中CThostFtdcMdSpi的OnFrontConnected方法,简单验证前置连接请求的发送和接收成功。

virtual void OnFrontConnected() { printf("ok"); };

修改OnFrontConnected的时候,把virtual关键字去掉了,导致运行时错误。可以推测,lib和dll导出的CThostFtdcMdSpi中已经有符号表,不容随意修改。

2.12 登录系统

初始化之后,行情工作线程就会自动使用注册好的前置地址向服务端请求建立无身份验证的连接。
连接建立后函数OnFrontConnected 即会被调用。如果连接不能建立,或程序运行过程中该连接断开,则函数OnFrontDisconnected 会被调用。

连接建立之后,即可使用函数ReqUserLogin 请求登录系统,核心的数据结构是CThostFtdcReqUserLoginField。

如果只登陆行情服务器,是不需要用户名,密码的,不写也能登陆成功。

class UserMdSpi :
	public CThostFtdcMdSpi
{
public:
	CThostFtdcMdApi * pMdApi;
	int requestID;
	UserMdSpi(CThostFtdcMdApi *pMdApi) { this->pMdApi = pMdApi; requestID = 0; };

	virtual void OnFrontConnected() {
		printf("front connected\n");
		CThostFtdcReqUserLoginField req;
		memset(&req, 0, sizeof(req));
		strcpy_s(req.BrokerID, "9999");
		strcpy_s(req.UserID, "你的用户id");
		strcpy_s(req.Password, "你的密码");
		int ret = this->pMdApi->ReqUserLogin(&req, ++requestID);
		switch (ret)
		{
		case 0:
			printf("发送成功\n"); break;
		case 1:
			printf("网络连接失败\n"); break;
		case 2:
			printf("处理请求超过许可数\n"); break;
		case 3:
			printf("每秒发送请求数超过许可数\n"); break;
		default:
			break;
		}
	}
	virtual void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {
		if (0 == pRspInfo->ErrorID) {
			printf("登录成功\n");
			printf("交易日日期:%s, session id = %d,system name = %s\n",pRspUserLogin->TradingDay,pRspUserLogin->SessionID,pRspUserLogin->SystemName);
			printf("Front ID:%d, MaxOrderRef = %s,User ID = %s\n", pRspUserLogin->FrontID, pRspUserLogin->MaxOrderRef, pRspUserLogin->UserID);
		}
		else {
			printf("登录失败,错误消息:%s\n", pRspInfo->ErrorMsg);
		}
	}
};

BrokerID 是期货公司的会员号。
UserID 是投资者在该期货公司的客户号。
Password 是该投资者密码。

该函数将会返回一个整数值(代码片段中的ret),标志该请求是否被成功发送出去,而不代表该请求是否会被服务端处理。

返回值取值
0: 发送成功
1: 因网络原因发送失败
2: 未处理请求队列总数量超限。
3: 每秒发送请求数量超限。
综合交易平台接口中的大部分请求操作的返回值都如上述描述一样。

服务器端成功接收该用户登录请求,而且对期货公司会员号,投资者客户号,投资者登录密码检查无误后,通过函数OnRspUserLogin 发送服务端对该登录请求操作作出的响应。

开发者可以通过第二个参数pRspInfo 中的ErrorID 判断登录是否已经成功。如果ErrorID 为0,则登录成功,否则登录失败。

一般密码错误时会返回“不合法登录”的错误信息。

登录成功后,第一个参数pRspUserLogin 中包含了服务器端返回的一些基础数据,如SessionID,FrontID,MaxOrderRef 以及各交易所服务器上的时间。

2.13订阅行情

登录成功后,才可以进行行情的订阅。
客户端使用函数SubscribeMarketData 进行行情订阅。第一个参数是一个包含所有要订阅的合约的数组,第二个参数是该数组的长度。

2.14 显示k线

2.14.1 显示最简易版k线

首先借助2.9节的从sqlite数据库中读取数据的技术,提取数据

	StructuredData *sdata = new StructuredData();
	sqlite3 *db;
	bool rc = sqlite3_open("test.db", &db);

	if (rc) {
		fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
		exit(0);
	}
	else {
		fprintf(stderr, "Opened database successfully\n");
	}
	sdata->getbar(db);

接下来做一个与view相关的画笔

	CClientDC dc(this);
	CPen cyanPen(PS_SOLID, 1, RGB(0, 0, 240));//创建一个宽度为1的实线画笔
	dc.SelectObject(&cyanPen);

获取绘图区域矩形,并获取上下左右坐标

	CRect rect;
	GetClientRect(&rect);
	int left = rect.left;
	int right = rect.right;
	int top = rect.top;
	int bottom = rect.bottom;

总共k线数量

	int div = sdata->pBar->size();

每根k线宽度

	int width = (right - left) / div;

遍历所有k线,获取k线最高点和最低点

vector<bardata>::iterator i;  //定义正向迭代器
double highest = 0 ;
double lowest = 1000000000;
for (i = sdata->pBar->begin(); i != sdata->pBar->end(); ++i) {  //用迭代器遍历容器
    bardata bdata = *i;
        if(bdata.high>highest)
        {
            highest = bdata.high;
        }
        if (bdata.low < lowest) {
            lowest = bdata.low;
        }
}

计算坐标并画图

	int j = 0;
	for (i = sdata->pBar->begin(); i != sdata->pBar->end(); ++i) {  //用迭代器遍历容器
		bardata bdata = *i;
		//int left = rect.left;
		//int right = rect.right;
		int left = rect.left + j * width; j++;
		int right = left+width;
		//int HIGH = rect.bottom + int((rect.top - rect.bottom) / (highest - lowest)*(bdata.high - lowest));
		//int LOW = rect.bottom + int((rect.top - rect.bottom) / (highest - lowest)*(bdata.low - lowest));
		int top = rect.bottom + int((max(bdata.open, bdata.close)-lowest)*(rect.top - rect.bottom) / (highest - lowest));
		int bottom = rect.bottom + int((min(bdata.open, bdata.close)-lowest)*(rect.top - rect.bottom) / (highest - lowest));
		//int top = rect.top;
		//int bottom = rect.bottom;
		POINT topleft = { left ,top};
		POINT bottomright = { right,bottom};
		CRect rect1(topleft, bottomright);
		dc.Rectangle(&rect1);
请忽略这个按钮

2.15 安排Eigen库

找到visual studio的安装目录D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\,新建文件夹D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\site-packages\并将Eigen库下载解压后的文件夹\eigen-3.2.10\拷贝入上面新建文件夹中,形成D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\site-packages\eigen-3.2.10\。

c文件中include即可使用

#include <Eigen/Dense>

2.16 选取局部高点或低点

	int size = sdata->pBar->size();
	printf("size是%d\n", size);
	int critical[300];
	double phigh[300];
	double plow[300];
	printf("phigh的长度%d\n", sizeof(phigh));
	memset(critical,0,sizeof(critical));
	memset(phigh,0,sizeof(phigh));
	memset(plow, 0, sizeof(plow));
	
	vector<bardata>::iterator it;  //定义正向迭代器
	int k = 0;
	
	for (it = sdata->pBar->begin(); it != sdata->pBar->end(); ++it) {  //用迭代器遍历容器
		bardata bdata = *it;
		phigh[k] = bdata.high;
		plow[k] = bdata.low;
		k++;
	}

	for (k = 0; k < size; k++) {
		int j = 0;
		int ultrahigh = 0;
		while (k - j - 1 >= 0 && k + j + 1 < size) {
			if (phigh[k] > phigh[k - j - 1] && phigh[k] > phigh[k + j + 1]) { ultrahigh++; } else{break;}
			j++;
		}
		j = 0; 
		int ultralow = 0;
		while (k - j - 1 >= 0 && k + j + 1 < size) {
			if (plow[k] < plow[k - j - 1] && plow[k] < plow[k + j + 1]) { ultralow++; }else { break; }
			j++;
		}
		critical[k] = max(ultrahigh,ultralow);
	}

2.17 用c++进行多项式拟合

*要用Eigen

	//最近的两个特殊点之间拟合
	for (k = 0; k < size; k++) {
		if (critical[k] > 3) {
			int h;
			for ( h = k + 1; h < size; h++) {//h从k往后数,查到第一个极值点就break
				if (critical[h] > 3) {
					printf("确定%d,%d区间,进行拟合\n",k,h);
					int r=1; //拟合阶次,r=4表示四次多项式
					MatrixXd mat_u(h-k+1, r+1);
					MatrixXd mat_y(h-k+1, 1);
					for (int p = 0; p < h-k+1; p++)
					{
						for (int q = 0; q < r + 1; q++) {
							mat_u(p, q) = pow(k+p, q);
						}
						mat_y(p, 0) = pclose[k+p];
					}
					MatrixXd mat_k(r + 1, 1);
					mat_k = (mat_u.transpose()*mat_u).inverse()*mat_u.transpose()*mat_y;
					for (int s = 0; s < r+1; s++)
					{
						printf("拟合系数s %6.3f\n",mat_k(s,0));
					}
					break;
				}
			}
		}

参考了文章:转载:C++实现多项式曲线拟合–polyfit

对线性回归来说,快速求出系数的公式是这样的:

2.18 画三连线

void SelectPolyfit(sqlite3 *db, CClientDC *dc,double highest,double lowest,double width, CRect rect) {
	StructuredData * sdata = new StructuredData();
	sdata->getbar(db);
	int size = sdata->pBar->size();
	printf("size是%d\n", size);
	//int sizeofn = 300;
	int *critical = new int[size];
	//int critical[300];
	double *phigh = new double[size];
	double *plow = new double[size];
	double *pclose = new double[size];
	printf("phigh的长度%d\n", sizeof(phigh));
	memset(critical, 0, sizeof(critical));
	memset(phigh, 0, sizeof(phigh));
	memset(plow, 0, sizeof(plow));
	memset(plow, 0, sizeof(plow));
	vector<bardata>::iterator it;  //定义正向迭代器
	int k = 0;

	for (it = sdata->pBar->begin(); it != sdata->pBar->end(); ++it) {  //用迭代器遍历容器
		bardata bdata = *it;
		phigh[k] = bdata.high;
		plow[k] = bdata.low;
		pclose[k] = bdata.close; //此处不用,后面拟合用
		k++;
	}

	for (k = 0; k < size; k++) {
		int j = 0;
		int ultrahigh = 0;
		while (k - j - 1 >= 0 && k + j + 1 < size) {
			if (phigh[k] > phigh[k - j - 1] && phigh[k] > phigh[k + j + 1]) { ultrahigh++; }
			else { break; }
			j++;
		}
		j = 0;
		int ultralow = 0;
		while (k - j - 1 >= 0 && k + j + 1 < size) {
			if (plow[k] < plow[k - j - 1] && plow[k] < plow[k + j + 1]) { ultralow++; }
			else { break; }
			j++;
		}
		critical[k] = max(ultrahigh, ultralow);
	}
	printf("数组critical的值为,\n");
	for (k = 0; k < size; k++) { printf("%d ", critical[k]); }
	printf("\n");
	//最近的两个特殊点之间拟合
	for (k = 0; k < size; k++) {
		if (critical[k] > 3) {
			int h;
			for (h = k + 1; h < size; h++) {//h从k往后数,查到第一个极值点就break
				if (critical[h] > 3) {
					printf("确定%d,%d区间,进行拟合\n", k, h);
					int r = 4; //拟合阶次,r=4表示四次多项式
					MatrixXd mat_u(h - k + 1, r + 1);
					MatrixXd mat_y(h - k + 1, 1);
					for (int p = 0; p < h - k + 1; p++)
					{
						for (int q = 0; q < r + 1; q++) {
							mat_u(p, q) = pow(p, q);
						}
						mat_y(p, 0) = pclose[p];
					}
					MatrixXd mat_k(r + 1, 1);
					mat_k = (mat_u.transpose()*mat_u).inverse()*mat_u.transpose()*mat_y;
					for (int s = 0; s < r + 1; s++)
					{
						//printf("拟合系数s %6.3f\n",mat_k(s,0));
					}
					break;
				}
			}
		}
		set<bardata> vbset[1000];
		int i = 0;
		//set<bardata> **bset;
		for (int j = 0; j < size; j++) {
			if (critical[j] > 3) {
				for (int k = j + 1; k < size; k++) {
					if (critical[k] > 3) {
						//if (j,k都在一个)
						bool newset = true;
						for (int s = 0; s < i; s++) { if (vbset[i].count(sdata->pBar->at(j)) > 0 && (vbset[i].count(sdata->pBar->at(k)) > 0)) { newset = false; break; } }
						if (newset == false) break;
						set<bardata>* SET = new set<bardata>;
						for (int h = k + 1; h < size; h++) {
							if (critical[h] > 3) {
								//判断三点围成的面积较小http://tatetian.blogspot.com/2009/02/blog-post.html
								bardata jbar = sdata->pBar->at(j);
								bardata kbar = sdata->pBar->at(k);
								bardata hbar = sdata->pBar->at(h);
								if ((j - h)*(kbar.close - hbar.close) - (k - h)*(jbar.close - hbar.close) < (jbar.close + kbar.close + hbar.close) / 300) {
									SET->insert(jbar);
									SET->insert(kbar);
									SET->insert(hbar);
									//printf("三点共线形成%d,%d,%d\n",j,k,h);
									int x1 = int(rect.left+j * width+ width / 2);
									int x2 = int(rect.left + k * width + width / 2); 
									int y1 = int(rect.bottom + int((jbar.close - lowest)*(rect.top - rect.bottom) / (highest - lowest)));
									int y2 = int(rect.bottom + int((kbar.close - lowest)*(rect.top - rect.bottom) / (highest - lowest)));
									CPen redPen(PS_SOLID, 1, RGB(255, 0, 0));//创建一个宽度为1的实线画笔
									dc->SelectObject(&redPen);
									dc->MoveTo(x1, y1);
									dc->LineTo(x2, y2);
								}
							}
						}
						//printf("bset->size() %d\n",SET->size());
						if (SET->size() != 0)
						{
							vbset[i++] = *SET;
							//printf("\n");
						}
					}
				}
			}
		}
	}
}

源码在 FutureQuantSingle-v0.1.zip

三、参考文献

VC++深入详解 孙鑫

综合交易系统 第一次运行

simnow官网

CTP二代行情api _6.3.15_demo VS2015版本2019.11.15更新,main.cpp

综合交易平台交易 API 特别说明

量化交易系统的关键部件(k线绘画器,tick数据处理器,交易api)

K-Means聚类算法原理

转载:C++实现多项式曲线拟合–polyfit

四、测试环境

BrokerID统一为:9999

第一套(支持上期所期权):

        第一组:Trade Front:180.168.146.187:10201,Market Front:180.168.146.187:10211;【电信】(看穿式前置,使用监控中心生产秘钥)

        第二组:Trade Front:180.168.146.187:10202,Market Front:180.168.146.187:10212;【电信】(看穿式前置,使用监控中心生产秘钥)

        第三组:Trade Front:218.202.237.33:10203,Market Front:218.202.237.33:10213;【移动】(看穿式前置,使用监控中心生产秘钥)

        用户注册后,默认的APPID为simnow_client_test,认证码为0000000000000000(16个0),默认不开终端认证,程序化用户可以选择不开终端认证接入。

        交易品种:五所所有期货品种以及上期所所有期权品种。

        账户资金:初始资金两千万,支持入金,每日最多三次。

        交易阶段(服务时间):与实际生产环境保持一致。

        客户端软件下载:点击下载客户端

建议下载客户端后使用测速功能检测畅通的交易前置。

第二套:

    交易前置:180.168.146.187:10130,行情前置:180.168.146.187:10131;【7×24】(看穿式前置,使用监控中心生产秘钥)

    第二套环境仅服务于CTP API开发爱好者,仅为用户提供CTP API测试需求,不提供结算等其它服务。

    新注册用户,需要等到第三个交易日才能使用第二套环境。

    账户、钱、仓跟第一套环境上一个交易日保持一致。

    交易阶段(服务时间):交易日,16:00~次日09:00;非交易日,16:00~次日15:00。

    用户通过SimNow的账户(上一个交易日之前注册的账户都有效)接入环境,建议通过商业终端进行模拟交易的用户使用第一套环境。

五、算法大纲

一、对日线进行聚类,将k线划分为由一次函数和二次函数构成的连续段。判定趋势。

二、对各段的特殊点(通道角点、段交点)进行统计和连线,确定支撑和阻力隔离带。

三、对分钟k线进行拟合,隔离带内再次精细划分。

四、tick级别结合量价进行下单。

5.1 算法亮点

1.选取极值点,对相邻极值点间的k线进行拟合形成驱动线;对不相邻的极值点(同极大或同极小)连线并作延长线做主力支撑,判据形成阻力线。阻力强度正相关与线长与线上特殊点数量。

2. 从特殊点形成阻力线的过程:首先对特殊点进行三点共线筛选,形成若干集合,集合的元素包括三点,以及线的斜率和截距。对形成的集合进行扩充。

六、学习资料下载(书籍PDF和项目工程)地址:

[wshop_downloads]

发表评论