KTL 用C++14写公式的K线工具 - 0.9.3版
K,K线,Candle蜡烛图。
T,技术分析,工具平台
L,公式Language语言使用c++14,Lite小巧简易。
项目仓库:https://github.com/bbqz007/KTL
国内仓库:https://gitee.com/bbqz007/KTL
当前0.9.3。修正代码编辑器不能调整窗口大小的问题。
新功能以patch.cpprest.7z方式提供,包含源代码。需要手动解压。
关键字:pplx, cpprest, sina finance, baidu gushitong.
新功能如下:
这个版本,加入pplx,cpprest编程模块。使用者可以在本工具使用pplx更简单地写异步代码, 使用cpprest更方便地写异步http,包括http请求数据还有http服务提供数据。
cpprest使用boost178,包含最小的asio头文件依赖。
然后提供应用工具,请求新浪财经数据,使用百度股市通opendata的api请求数据。
先来简单介绍pplx。是一个简化的PPL,相似地为java或javascript的Promise。用于简单方便地写异步应用。
将一个完整流程逻辑,拆分成一段一段的contination。每个contination借助pplx;:task投放到线程池。你可以wait()在当前控制的线程手动同步,或者then()让它在线程池事件循环异步等待结果。
auto task = create_task([]{ return 1; }) .then([](int){ return 1.; }) .then([](double){ return std::string("end"); });
这里面安排了三个task,它们构成一个简单的filter-pipeline,前者的输出结果作为后者的输入参数。并且三个task在线程池自动完成分派。
下面是map-reduce形式
std::vector<task<int> > maps = { create_task([]{ return 1; }), create_task([]{ return 12; }), create_task([]{ return 13; }), create_task([]{ return 14; }), }; auto reduce = when_all(maps.begin(), maps.end()) .then([=](auto){ int sum = 0; for_each(maps.begin(), maps.end(), [&](auto& task){ sum += task.get(); }); return sum; }); reduce.get();
产生4个map任务并发, 然后reduce所有结果,一共5个task在线程池自动完成。
为了有更好的控制,这里有一个可等待事件,task_completion_event。我们可以用它作为一个事件源,借助一个task用它作为参数就可以进行同步或异步等待了,类似于future。
我们可以将这个future用作输入源,让安排好异步任务链等待输入才开始执行。
task_completion_event<int> future; task<int> start(future); auto pipeline = start.then([](int i){ return i; }) .then([](int i){ return i; }) .then([](double){ return std::string("end"); }); future.set(0); pipeline.wait();
或者利用这个future,让pplx线程池外的线程也参与到pplx异步任务链。让pplx异步任务等待外部线程的结果。
再次指出,then()异步等待,并不阻塞任何线程,它是由线程池的事件循环完成异步等待的。
本工具提供的pplx,其底层基于asio。所有可以用asio的timer进行async_wait。结合task_completion_event,可以实现delay。
pplx::task<void> delay_post(std::chrono::milliseconds delay) { boost::asio::io_service& io_service = pplx::threadpool::shared_instance().io_service(); pplx::task_completion_event<void> tce; auto timer = std::make_shared<boost::asio::steady_timer>(io_service); timer->expires_after(delay); timer->async_wait([tce, timer](const boost::system::error_code& ec) mutable { if (!ec) { tce.set(); } else { tce.set_exception(std::make_exception_ptr(std::runtime_error("Timer error: " + ec.message()))); } }); return pplx::create_task(tce); }了
了解更多pplx,我写了另一篇《浅析pplx库的设计与实现。》
再来介绍cpprest。
cpprest就是cpp实现的rest开发库。线程模型使用pplx,http使用asio的实现。全程支持异步。
对于http client。主要两步,异步等待response,然后异步读取content。当http_client接收读取出status跟header fields后,response完成等待,然后就可以对streambuf进行异步读取content数据。
对于http server。借助于pplx异步模型,灵活性大。一个request不再由一个线程从开头处理到结束。而是把处理逻辑拆分成异步片段。在线程池进行异步任务链处理。这样就可以将http请求实现成poll。
下面就用BlackJack例子说明。
客户端状态机如下:
0,准备开局,接受投注。
>0, 开局中。
1, 需要轮询,等待下一步。
2, 进行处理环节,可以操作。
0, 结束,得出结果。
开始时,大家都在状态0,并进行投注,服务器原子等待所有人都投注后才能开局。
服务器会阻塞所有投注请求,直到人齐数了,才会开局并发出response。
服务器不会将线程阻塞来等待其它投注进来,而是将request作为异步上下文缓存起来。当人数齐后,进行开局逻辑处理,并发出response通知开局信息,包含状态切换。
收到1的玩家,必须调用接口refresh阻塞轮询。收到2的玩家进入服务状态,进行牌局交互操作。结束后,服务器通知其切换状态1进入轮询等待。并选出一下玩家通知它的轮询切换状态2。
服务器通过缓存起全部轮询请求,以确保一桌人数同时在线。
全部玩家相继完成服务环节后,服务器向每人的轮询请求发出结果。并知会状态切换0。
BlackJack例子的实现就是利用pplx的异步优势。
本工具提供的BlackJack例子完全使用cpprest的BlackJack例程,逻辑没有任何修改。原例程应用于控制台标准输入输出,为了让其应用在界面,我只是将标准输入替换成QTextStream再指向pipe,标准输出替换成stringstream。
这样一来,原例程就可以原原本本地作为台后部分运行,界面作为前台部分运行。前后台之间用两条流进行数据交互。
要将cpprest线程池的数据更新到ui线程,我们需要将数据投递到ui线程执行。在win32界面我们可以PostMessage到线程队列,虽然qt没有线程队列,但是基于qt事件循环的线程,每个qt对象都可充当线程队列角色,我说的是signal队列。为方便,我们只需要借用任意一个qt对象的signal就可以了,不必新写一个qt对象只为了定义一个signal。在此,我就借用QFutureWatcher的signal finished(),用作投递方法。
struct MyQFutureWatcher : public QFutureWatcher<int> { ~MyQFutureWatcher() { OutputDebugStringA("~MyQFutureWatcher"); } QMutex mtx; QList<std::function<void()> > queue; MyQFutureWatcher(QObject *parent = nullptr) : QFutureWatcher(parent) { QObject::connect(this, &QFutureWatcher<int>::finished, [=](){ _sched(); }); } void sched(std::function<void()>& functor) { mtx.lock(); queue.push_back(functor); mtx.unlock(); /// Z#20241210, bug, Q_EMIT dispatch in caller's thread //Q_EMIT finished(); // to sched QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); } private: void _sched() { QList<std::function<void()> > coll; mtx.lock(); coll = std::move(queue); mtx.unlock(); while (!coll.empty()) { coll.front()(); coll.pop_front(); } } };
于是有界面例程:
编写服务器也简单方便,虚函数handle_XXX对应于http请求方法的入口,只要override这些虚函数,再根据uri进行不同服务接口处理。
然后就是本工具的新增的主要功能。
通过两个例子,介绍如何使用cpprest写代码,请求新浪财经的数据,使用百度股市通的opendata接口请求数据。
对于新浪财务数据,就是页面的表格。我们可以用QTextEdit转换成MarkDown。
下面就是百度的opendata数据接口,它们在webpack://finance-pc/src/api/路径下api_prefix.js跟detail.js,有详细的注释说明。数据涵盖多个市场,A股,港股,美股,货币,有色金属等。
本功能使用了两个接口,第一个是码表搜索。第二个是基本行情。所有接口皆用JSON承载数据。
本功能帮助大家调试分析这些接口的数据。每次请求结果会在下方树型表缓冲。右键树型节点打开不同功能。根节点预览并转换格式保存文件。对于JSON的对象节点,右键打开属性路径搜索。叶子节点则是复制值。
除了上面两个固定网站的接口应用例子,还可以使用通用请求。
现就用通用请求跟BlackJack例子一同测试一下。
本功能的源代码, 已经完整展示了如何使用cpprest调用opendata接口,如何使用cpprest写界面交互。使用者可以根据需要修改裁剪扩展,满足自己需要的功能。 使用cpprest从其它股票相关网站获取数据也是十分方便。有cpprest借助pplx加承,不必费心多线程跟io并发,轻轻松松写异步http请求。
还有一点注意, cpprest默认使用unicode,qt字符串底层也是使用unicode。其它json11,pugihtml需要utf8。
本篇结束。