第六节 代码的编写(1)
从这一节开始,进入到代码的编写,有了上面的基础,我们只需要一个个的把里面的按钮双击,进入到对应的代码编写区域,把相应功能的代码写上去就可以了。
还是按上面的顺序,首先,我们来编写登入、退出 部分的代码。需要查看原文件的读者,欢迎前往 :https://ipacel.cc/ctp
一、上期模拟、上期7X24:这两个Radio Button 用来切换两个CTP服务器的地址,为了方便,读者也可以把账号密码写进去,程序调试好了,记得把账号密码不要显示出来。双击这两个按钮,把以下代码分别复制到相应的响应事件中去:
- void CMyCtpDlg::OnBnClickedR1() //上期模拟
- {
- // TODO: 在此添加控件通知处理程序代码
- GetDlgItem(IDC_EDIT_MDadd)->SetWindowTextW(_T("tcp://218.202.237.33:10213"));
- GetDlgItem(IDC_EDIT_TRadd)->SetWindowTextW(_T("tcp://218.202.237.33:10203"));
- GetDlgItem(IDC_EDIT_BrID)->SetWindowTextW(_T("9999"));
- GetDlgItem(IDC_EDIT_INVid)->SetWindowTextW(_T(""));
- GetDlgItem(IDC_EDIT_Pass)->SetWindowTextW(_T(""));
- GetDlgItem(IDC_EDIT_appid)->SetWindowTextW(_T("simnow_client_test"));
- GetDlgItem(IDC_EDIT_auID)->SetWindowTextW(_T("0000000000000000"));
- }
- void CMyCtpDlg::OnBnClickedR2() //上期7X24
- {
- // TODO: 在此添加控件通知处理程序代码
- GetDlgItem(IDC_EDIT_MDadd)->SetWindowTextW(_T("tcp://180.168.146.187:10131"));
- GetDlgItem(IDC_EDIT_TRadd)->SetWindowTextW(_T("tcp://180.168.146.187:10130"));
- GetDlgItem(IDC_EDIT_BrID)->SetWindowTextW(_T("9999"));
- GetDlgItem(IDC_EDIT_INVid)->SetWindowTextW(_T(""));
- GetDlgItem(IDC_EDIT_Pass)->SetWindowTextW(_T(""));
- GetDlgItem(IDC_EDIT_appid)->SetWindowTextW(_T("simnow_client_test"));
- GetDlgItem(IDC_EDIT_auID)->SetWindowTextW(_T("0000000000000000"));
- }
- 关闭:我们先把简单的讲了,双击这个按钮控件,代码如下:
void CMyCtpDlg::OnBnClickedCancel() // 关闭
{
if (MessageBox(TEXT(" 确定要退出吗?"), TEXT("退出提示"), MB_YESNO | MB_DEFBUTTON1) == IDNO)
{
return;
}
CMyCtpDlg::OnBnClickedButton11();
PostQuitMessage(0);//最常用
}
- 登入:
代码如下:
void CMyCtpDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
///取得登陆信息
CString STRTEMP = _T("");
GetDlgItem(IDC_EDIT_MDadd)->GetWindowTextW(STRTEMP);
STRTEMP.Trim(); ///trimleft
if (STRTEMP != "")
{
CStringToChar(STRTEMP, FRONT_MD_ADDR); // 前置地址
}
else
{
return;
}
STRTEMP = _T("");
GetDlgItem(IDC_EDIT_TRadd)->GetWindowTextW(STRTEMP);
STRTEMP.Trim(); ///trimleft
if (STRTEMP != "")
{
CStringToChar(STRTEMP, FRONT_TR_ADDR); // 前置地址
}
else
{
return;
}
STRTEMP = _T("");
GetDlgItem(IDC_EDIT_BrID)->GetWindowTextW(STRTEMP);
STRTEMP.Trim(); ///trimleft
if (STRTEMP != "")
{
CStringToChar(STRTEMP, BROKER_ID);
}
else
{
return;
}
STRTEMP = _T("");
GetDlgItem(IDC_EDIT_INVid)->GetWindowTextW(STRTEMP);
STRTEMP.Trim(); ///trimleft
if (STRTEMP != "")
{
CStringToChar(STRTEMP, INVESTOR_ID); // 投资者代码
}
else
{
return;
}
STRTEMP = _T("");
GetDlgItem(IDC_EDIT_Pass)->GetWindowTextW(STRTEMP);
/// STRTEMP.Trim(); ///trimleft ///密码前后可以有空格,不能删除这些信息
if (STRTEMP != _T(""))
{
CStringToChar(STRTEMP, PASSWORD); ///TThostFtdcPasswordType PASSWORD = ""; // 用户密码
}
else
{
return;
}
STRTEMP = _T("");
GetDlgItem(IDC_EDIT_appid)->GetWindowTextW(STRTEMP);
STRTEMP.Trim();
if (STRTEMP != _T(""))
{
CStringToChar(STRTEMP, APPID); /// APPID不能为 空
}
else
{
return;
}
STRTEMP = _T("");
GetDlgItem(IDC_EDIT_auID)->GetWindowTextW(STRTEMP);
STRTEMP.Trim();
if (STRTEMP != _T(""))
{
CStringToChar(STRTEMP, AuthCode); /// 认证码不能为 空
}
else
{
return;
}
将以上登陆信息写入到相应的结构中去///
memset(&reqUserInFo, 0, sizeof(reqUserInFo));
strcpy_s(reqUserInFo.BrokerID, BROKER_ID);
strcpy_s(reqUserInFo.UserID, INVESTOR_ID);
strcpy_s(reqUserInFo.Password, PASSWORD);
//CThostFtdcReqAuthenticateField Auth_login_field; ///穿透式认证信息
memset(&Auth_login_field, 0, sizeof(Auth_login_field));
strcpy_s(Auth_login_field.BrokerID, BROKER_ID);
strcpy_s(Auth_login_field.UserID, INVESTOR_ID);
strcpy_s(Auth_login_field.AppID, APPID);
strcpy_s(Auth_login_field.AuthCode, AuthCode);
/// //
if (!LOGIN_MD_FLAG)
{
CMyCtpDlg::MsgDis(888, _T("正在连接行情服务器,请稍等..."));
//判断保存本地数据的目录是否存在,如果不存在,建一个 (".\\flow\\")
STRTEMP = _T(".\\flow\\");
if (!PathIsDirectory(STRTEMP))
{
BOOL dirok = CreateDirectory(STRTEMP, NULL);
}
CThostFtdcMdSpi* pUserMdSpi = new MdSpi; //创建spi对象
pUserMdApi = CThostFtdcMdApi::CreateFtdcMdApi(".\\flow\\", true); //创建API对象 //使用udp的通讯模式接收行情 true
//pUserMdApi = CThostFtdcMdApi::CreateFtdcMdApi(".\\flow\\", false, false);
//其中第一个参数是本地流文件生成的目录。流文
//件是行情接口或交易接口在本地生成的流文件,后缀名为.con。流文件中记录着客户端收到的所有的数据流
//的数量。第二个参数描述是否使用 UDP 传输模式,true 表示使用 UDP 模式,false 表示使用 TCP 模式。
pUserMdApi->RegisterSpi(pUserMdSpi); 将spi注册给api,注册回调函数
pUserMdApi->RegisterFront(FRONT_MD_ADDR); 注册前置服务器地址 行情地址
pUserMdApi->Init(); //自动创建线程,创建后调用spi中的OnFrontConnect函数 pUserTrApi
if (WAIT_TIMEOUT == WaitForSingleObject(MdXc, 5000)) { MsgDis(888, _T("连接行情服务器失败,连接超时")); }
}
}
//
这段代码比较长,需要说明一下:
这段代码分成三部分
- 把参数从各个控件中取出来,再存到相应的数据变量中去。比如,行情前置地址,交易前置地址等,这里只有账号和密码这两个控件在运行时是可以看见的,其它的控件在运行时是看不到,这些看不到的控件,是由旁边两个切换CTP服务器地址等的 Radio Buuton 在点击的时候赋了值的,我们在操作中,只需要手工把账号密码填写进去就可以了。
- 在正确获取到参数后,开始创建CTP的行情SPI对象:
CThostFtdcMdSpi* pUserMdSpi = new MdSpi; //创建spi对象
pUserMdApi = CThostFtdcMdApi::CreateFtdcMdApi(".\\flow\\", true); //创建API对象
就是这两句,这个是个重点内容,CTP的API接口除了分行情、交易两个线程外,还分请求与响应函数。行情有请求和响应,交易也有自己的请求和响应。所以,下面就有这一句,将SPI注册回调函数:
pUserMdApi->RegisterSpi(pUserMdSpi); 将spi注册给api,注册回调函数
创建这两个对象的时候,有一些参数,请参看源码部分给出的说明,源码上写得比较清楚,有不明白的地方,可以与作者联系沟通交流。
- 初始化:
pUserMdApi->Init(); //自动创建线程,创建后调用spi中的OnFrontConnect函数 pUserTrApi
通过这个 Init() 函数,程序正确运行时,就会有不同的信息通过回调函数返回相应的信息。
到止,行情的代码就在CTP接口里的回调函数里去写了,我们先不去研究那部分,先把主窗口界面的代码讲解完,回过来再讲解行情,交易API接口提供的回调函数。