Rust: CTP的rust版本如何手工封装
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Rust: CTP的rust版本如何手工封装相关的知识,希望对你有一定的参考价值。
https://blog.csdn.net/wowotuo/article/details/86669758
这里指的手工封装,是指不用外部类似swig专用的库。
一、库、配置
1、DLL 交互的库
(1)libloading
https://github.com/nagisa/rust_libloading
(2)libc
Raw FFI bindings to platform libraries like libc.
https://github.com/rust-lang/libc
2、类型转换库
Rust封装CTP,涉及两个方面类型,一个是rust转c++; 另一个是c++转rust.
cbindgen: 可以根据此库,把rust的rs文件生成相应的头文件,提高效率,减少手工封装过程。
https://crates.io/crates/cbindgen
rust-bindgen: 可以根据此库将C/C++的头文件,自动生成Rust 的C绑定文件。
https://github.com/rust-lang/rust-bindgen
二、CTP DLL资料
http://www.sfit.com.cn/5_2_DocumentDown.htm
Api有3种通讯模式:
• 对话通讯模式:由客户端主动发起请求。Thost收到请求、处理请求后,返回1条或者多条响应纪录。例如登入、各项查询、报单、撤单等操作。
• 私有通讯模式:由Thost主动向客户端发出的相关信息。例如委托回报、成交回报、错单回报等
• 广播通讯模式:由Thost主动向所有客户端发出的公共信息,例如行情等。
文件名 文件描述
FtdcTraderApi.h 交易接口头文件
ThostFtdcMdApi.h 行情接口头文件
FtdcUserApiStruct.h 定义了一系列业务相关的数据结构头文件
FtdcUserApiDataType.h 定义了 API 所需的一系列数据类型头文件
thosttraderapi.dll,thostmduserapi.dll 动态链接库二进制文件
thostraderapi.lib,thostmduserapi.lib 导入库文件
CTP相关知识、封装具体的介绍可以参考:
https://zhuanlan.zhihu.com/p/20031646
Python量化交易平台开发教程系列1-类CTP交易API的工作原理
https://zhuanlan.zhihu.com/p/20031660
Python量化交易平台开发教程系列2-类CTP交易API的Python封装设计
关于测试环境SIMNOW:
CTP开发中使用的模拟账号密码,要到SIMNOW上注册。BrokerID为9999,账号即investorId,密码为SIMNOW的登陆密码。
三、CTP中 C++ .h文件的改写
在ctp文件中,有一类是数据结构.h;一类是类和虚方法接口的.h文件;量大,需要写一个自动转换的程序。
1、ThostFtdcUserApiDataType.h和ThostFtdcUserApiStruct.h
(1)ThostFtdcUserApiDataType.h头文件中的类型
比如:
/
///TFtdcTraderIDType是一个交易所交易员代码类型
/
typedef char TThostFtdcTraderIDType[21];
/
///TFtdcInvestorIDType是一个投资者代码类型
/
typedef char TThostFtdcInvestorIDType[13];
/
///TFtdcBrokerIDType是一个经纪公司代码类型
/
typedef char TThostFtdcBrokerIDType[11];
/
///TFtdcBrokerAbbrType是一个经纪公司简称类型
/
typedef char TThostFtdcBrokerAbbrType[9];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(2)ThostFtdcUserApiStruct.h中结构体
struct CThostFtdcDisseminationField
TThostFtdcSequenceSeriesType SequenceSeries;
TThostFtdcSequenceNoType SequenceNo;
;
1
2
3
4
5
要转换成rust 中的结构体:[名字和类型可以沿用原来的,只是换个方式],比如:
pub struct CThostFtdcDisseminationField
pub SequenceSeries : i16 ,
pub SequenceNo : i32 ,
pub struct CThostFtdcReqUserLoginField
pub TradingDay : & \'static str ,
pub BrokerID : & \'static str ,
pub UserID : & \'static str ,
pub Password : & \'static str ,
pub UserProductInfo : & \'static str ,
pub InterfaceProductInfo : & \'static str ,
pub ProtocolInfo : & \'static str ,
pub MacAddress : & \'static str ,
pub OneTimePassword : & \'static str ,
pub ClientIPAddress : & \'static str ,
pub LoginRemark : & \'static str ,
pub struct CThostFtdcRspUserLoginField
pub TradingDay : & \'static str ,
pub LoginTime : & \'static str ,
pub BrokerID : & \'static str ,
pub UserID : & \'static str ,
pub SystemName : & \'static str ,
pub FrontID : i32 ,
pub SessionID : i32 ,
pub MaxOrderRef : & \'static str ,
pub SHFETime : & \'static str ,
pub DCETime : & \'static str ,
pub CZCETime : & \'static str ,
pub FFEXTime : & \'static str ,
pub INETime : & \'static str ,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
转换成rust中这个类型后,rust才能使用。
细节:这里暂时没有考虑int->c_int之类。只是提供一个方法。
2、tradeApi.h 和mdApi.h的改写
(1)主要是把类改成struct;
tradeapi中:
class CThostFtdcTraderSpi
class TRADER_API_EXPORT CThostFtdcTraderApi
1
2
mdapi中:
class CThostFtdcMdSpi
class MD_API_EXPORT CThostFtdcMdApi
1
2
(2)还有虚方法改成接口 trait;比如:
virtual void OnFrontConnected();
///当客户端与交易后台通信连接断开时,该方法被调用。当发生这个情况后,API会自动重新连接,客户端可不做处理。
///@param nReason 错误原因
/// 0x1001 网络读失败
/// 0x1002 网络写失败
/// 0x2001 接收心跳超时
/// 0x2002 发送心跳失败
/// 0x2003 收到错误报文
virtual void OnFrontDisconnected(int nReason);
1
2
3
4
5
6
7
8
9
10
四、封装:Rust与C++交互的核心技术
封装的目标是什么,简单地说,需要根据http://www.sfit.com.cn/5_2_DocumentDown.htm提供的4个头文件,两个C++原生的dll,以及lib文件,生成一个(或两个)封装的dll:
使之,
能够接受原生dll回调信息,与c++交互;
能够接受rust中参数,与原生dll交互;
(一)关于相关库
相关的库还是不少的,下面只是一些例子。
cc-rs:
https://github.com/alexcrichton/cc-rs
A library to compile C/C++/assembly into a Rust library/application.
libloading
https://github.com/nagisa/rust_libloading
A memory-safer wrapper around system dynamic library loading primitives. The most important safety guarantee by this library is prevention of dangling-Symbols that may occur after a Library is unloaded.
bindgen
bindgen automatically generates Rust FFI bindings to C (and some C++) libraries.
https://github.com/rust-lang/rust-bindgen
(二)关于封装
1、封装Rust 调用原生dll部分
比如Req开头的:
extern crate libloading as lib;
//const
#[cfg(target_arch = "x86")]
fn load_ordinal_lib() -> Library
Library::new("D:\\\\ctp-api\\\\tradeapi\\\\thostmduserapi.dll").expect("thostmduserapi.dll")
#[cfg(target_arch = "x86")]
//virtual void RegisterFront(char *pszFrontAddress) = 0;
fn RegisterFront(pszFrontAddress: String) -> lib::Result<()>
let lib = load_ordinal_lib();
unsafe
let func: lib::Symbol<unsafe extern "C" fn(String) -> ()> = lib.get(b"RegisterFront")?;
Ok(func(pszFrontAddress))
//virtual void RegisterNameServer(char *pszNsAddress) = 0;
//#[cfg(target_arch = "x86")]
fn RegisterNameServer(pszNsAddress: String) -> lib::Result<()>
let lib = lib::Library::new("D:\\\\ctp-api\\\\tradeapi\\\\thostmduserapi.dll")?;
unsafe
let func: lib::Symbol<unsafe extern "C" fn(String) -> ()> =
lib.get(b"RegisterNameServer")?;
Ok(func(pszNsAddress))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2、封装原生dll回传给rust的部分
比如:On打开头的。
这个应是在C++中写的。方向与上面是相反的。
// Rust: 供c++中调用
pub extern "C" fn onRspUserLogin(
pRspUserLogin: CThostFtdcRspUserLoginField,
pRspInfo: CThostFtdcRspInfoField,
nRequestID: i32,
bIsLast: bool,
)
1
2
3
4
5
6
7
C++中:(大致的写法,相当于伪代码吧)
virtual void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
this->onRspUserLogin(pRspUserLogin, pRspInfo, nRequestID, bIsLast) //调用rust中的函数
1
2
3
4
这样,就把c++中的代码和rust函数之间的消息传递建立起来了。
下面文章值得参考一下:
https://blog.csdn.net/guiqulaxi920/article/details/78653054
Rust与C交互(FFI)中复杂类型的处理
五、与CTP 相关的一些具体技术问题
建议可以参考一下:
https://github.com/tashaxing/CTPtest
其中的难点在于:
int main()
// 账号密码
cout << "请输入账号: ";
scanf("%s", gInvesterID);
cout << "请输入密码: ";
scanf("%s", gInvesterPassword);
// 初始化行情线程
cout << "初始化行情..." << endl;
g_pMdUserApi = CThostFtdcMdApi::CreateFtdcMdApi(); // 创建行情实例
CThostFtdcMdSpi *pMdUserSpi = new CustomMdSpi; // 创建行情回调实例
g_pMdUserApi->RegisterSpi(pMdUserSpi); // 注册事件类
g_pMdUserApi->RegisterFront(gMdFrontAddr); // 设置行情前置地址
g_pMdUserApi->Init(); // 连接运行
// 初始化交易线程
cout << "初始化交易..." << endl;
g_pTradeUserApi = CThostFtdcTraderApi::CreateFtdcTraderApi(); // 创建交易实例
//CThostFtdcTraderSpi *pTradeSpi = new CustomTradeSpi;
CustomTradeSpi *pTradeSpi = new CustomTradeSpi; // 创建交易回调实例
g_pTradeUserApi->RegisterSpi(pTradeSpi); // 注册事件类
g_pTradeUserApi->SubscribePublicTopic(THOST_TERT_RESTART); // 订阅公共流
g_pTradeUserApi->SubscribePrivateTopic(THOST_TERT_RESTART); // 订阅私有流
g_pTradeUserApi->RegisterFront(gTradeFrontAddr); // 设置交易前置地址
g_pTradeUserApi->Init(); // 连接运行
// 等到线程退出
g_pMdUserApi->Join();
delete pMdUserSpi;
g_pMdUserApi->Release();
g_pTradeUserApi->Join();
delete pTradeSpi;
g_pTradeUserApi->Release();
// 转换本地k线数据
//TickToKlineHelper tickToKlineHelper;
//tickToKlineHelper.KLineFromLocalData("market_data.csv", "K_line_data.csv");
getchar();
return 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
其中,
(1) 初始化行情线程和初始化交易线程中难点;
(2) 这里c++的类在rust中只能用struct对应;
(3) 注册、回调和订阅在rust 中如何实现?
待续…
————————————————
版权声明:本文为CSDN博主「songroom」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wowotuo/article/details/86669758
如何创建具有计时时区的通用 Rust 结构?
【中文标题】如何创建具有计时时区的通用 Rust 结构?【英文标题】:How do I create a generic Rust struct with a chrono time zone? 【发布时间】:2021-10-24 08:47:15 【问题描述】:免责声明:我是 Rust 的新手(以前的经验是 Python、TypeScript 和 Go,按顺序排列),我完全有可能遗漏了一些非常明显的东西。
我正在尝试构建一个 Rust 时钟接口。我的基本目标是我有一个报告实际时间的小时钟结构,以及一个报告伪造版本以供测试的存根版本。请注意,这些是历史测试而不是单元测试:我的目标是重放历史数据。我认为部分问题也可能是我对chrono
的理解不够好。这显然是一个很棒的库,但我在 chrono
和 chrono_tz
中的类型与实例关系方面遇到了问题。
无论如何,这就是我所拥有的:
use chrono::DateTime, TimeZone, Utc;
/// A trait representing the internal clock for timekeeping requirements.
/// Note that for some testing environments, clocks may be stubs.
pub trait Clock<Tz: TimeZone>
fn now() -> DateTime<Tz>;
我的最终目标是让其他结构在特定时区拥有dyn Clock
。该时钟可能是系统时钟(具有适当的时区转换),也可能是某种存根。
这是我对系统时钟 shim 的尝试,但一切都出现了可怕的错误:
/// A clock that reliably reports system time in the requested time zone.
struct SystemClock<Tz: TimeZone>
time_zone: std::marker::PhantomData<*const Tz>,
impl<Tz: TimeZone> Clock<Tz> for SystemClock<Tz>
/// Return the current time.
fn now() -> DateTime<Tz>
Utc::now().with_timezone(&Tz)
关键问题是Utc::now().with_timezone(&Tz)
。编译器需要一个值,而不是类型。很公平,除了chrono
和chrono_tz
似乎没有时区值。我一直在寻找合适的东西放在这里,但似乎没有什么是正确的答案。
【问题讨论】:
【参考方案1】:问题是时区 type 不足以实现指定的now()
。大多数时区没有实现为单独的类型,Utc
在这方面实际上是特殊的(Local
也是如此)。正常时区被实现为更一般的时区类型的值,例如FixedOffset
或chrono_tz::Tz
。这些类型在运行时存储时区偏移量,因此有效的FixedOffset
s 包括FixedOffset::east(1)
(CET)、FixedOffset::west(5)
(EST),甚至FixedOffset::east(0)
(GMT、UTC)。这就是为什么DateTime::with_timezone()
需要一个具体的时区值,而不仅仅是它的类型。
解决此问题的最简单方法是修改 now()
以接受时区值:
pub trait Clock<Tz: TimeZone>
fn now(tz: Tz) -> DateTime<Tz>;
struct SystemClock<Tz: TimeZone>
time_zone: std::marker::PhantomData<*const Tz>,
impl<Tz: TimeZone> Clock<Tz> for SystemClock<Tz>
fn now(tz: Tz) -> DateTime<Tz>
Utc::now().with_timezone(&tz)
用法如下所示:
fn main()
// now in Utc
println!(":?", SystemClock::now(Utc));
// now in GMT+1
println!(":?", SystemClock::now(FixedOffset::east(1)));
// now in Copenhagen time
println!(
":?",
SystemClock::now("Europe/Copenhagen".parse::<chrono_tz::Tz>().unwrap())
);
特别注意第二个和最后一个示例,其中时区是在运行时选择的,显然没有被时区类型捕获。
如果您发现在 now()
等 trait 方法中指定时区值是多余的,您可以让这些方法访问 self
并将时区值保留在 SystemClock
的字段中(这也将很好地消除PhantomData
):
pub trait Clock<Tz: TimeZone>
fn now(&self) -> DateTime<Tz>;
struct SystemClock<Tz: TimeZone>
time_zone: Tz,
impl SystemClock<Utc>
fn new_utc() -> SystemClock<Utc>
SystemClock time_zone: Utc
impl<Tz: TimeZone> SystemClock<Tz>
fn new_with_time_zone(tz: Tz) -> SystemClock<Tz>
SystemClock time_zone: tz
impl<Tz: TimeZone> Clock<Tz> for SystemClock<Tz>
fn now(&self) -> DateTime<Tz>
Utc::now().with_timezone(&self.time_zone)
fn main()
println!(":?", SystemClock::new_utc().now());
println!(":?", SystemClock::new_with_time_zone(FixedOffset::east(1)).now());
// ...
Playground
对于在编译时已知偏移量的时区,例如Utc
和Local
,时区字段将不占用空间,SystemClock
的大小为零,就像您原来的一样设计。对于在运行时选择偏移量的时区,SystemClock
会将该信息存储在结构中。
最后,随着 const 泛型的出现,可以想象FixedOffset
的一种变体,它在编译时将偏移量存储为 const 泛型。此类类型由 chrono-simpletz
crate 提供,您可以使用它来创建您最初想要的 Clock
特征。由于它的类型是在编译时完全指定的,它们实现了Default
,因此您可以使用Tz::default()
轻松获得时区值。结果(遗憾的是再次需要PhantomData
)可能如下所示:
use std::marker::PhantomData;
use chrono::DateTime, TimeZone, Utc;
use chrono_simpletz::UtcZst, known_timezones::UtcP1;
type UtcP0 = UtcZst<0, 0>; // chrono_simpletz doesn't provide this
pub trait Clock<Tz: TimeZone + Default>
fn now() -> DateTime<Tz>;
struct SystemClock<Tz: TimeZone>
time_zone: PhantomData<fn() -> Tz>,
impl<Tz: TimeZone + Default> Clock<Tz> for SystemClock<Tz>
fn now() -> DateTime<Tz>
Utc::now().with_timezone(&Tz::default())
fn main()
println!(":?", SystemClock::<UtcP0>::now());
println!(":?", SystemClock::<UtcP1>::now());
如果在生产中选择哪个选项不明显,我推荐第二个,即带有游乐场链接的那个。
【讨论】:
不是OP,但答案很明确,谢谢。每当我需要使用日期时间时,我都会花很长时间来了解它们,所以它会被添加到书签中。 感谢您提供如此详细的答案。我将在今晚尝试实施,并在我这样做后正式接受。谢谢!以上是关于Rust: CTP的rust版本如何手工封装的主要内容,如果未能解决你的问题,请参考以下文章