- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
原文链接: 简单的股票行情演示(一) - 实时标的数据 。
很长一段时间都有一个想法,使用QCP去做一个行情展示小事例,一直没有着手开发的原因主要是行情数据源的问题,毕竟稳定的数据才是核心,加上今年5月份有了小宝宝也一直比较忙.
最近得空研究了下用C++实现股票行情展示相关内容,主要策略是通过拉取网上一些免费的开源接口数据,然后存储到本地,在通过代码读取需要的日期数据进行展示。互联网拉取行情数据方法网上随手百度后会发现有一大堆,调取个别接口进行获取数据也是很方便的,比如通过新浪开源获取A股股票接口获取实时行情数据就很简单,浏览器url输入框中输入 http://hq.sinajs.cn/list=sz002208,sh601318 这段测试连接,按下回车,就会拿到list指定的两支股票数据,效果如下图所示.
需要特别注意:该接口拉取频繁后,会被后台403,所以本地需要做一些策略,尽可能减少无效拉取 。
尝到了简单的甜头之后,接下来就是疯狂百度、google,尽可能全面的整理开源的行情数据源,网上虽然文章很多,但是重复的内容特别多,讲的比较好的文章有 新浪股票 api 、 股票数据 API 接口合集 、 实时行情API ,通过看这几篇文章能大概了解到一些皮毛,简单使用不成问题。总的来说提供了一个可操作的入口, 数据源的问题算是暂时得到一部分解决,至于其他更完善的数据后续文章会有介绍,是由开源软件提供,而且文档比较详细,之后更多的数据将会使用开源程序进行获取 .
实时行情数据有了之后,接下来就是C++侧代码实现,主要分为异步数据拉取、数据写入本地文件、数据层读取,回调给UI展示,本篇文章接下来的主要内容将会讲解怎么拉取数据、回调给UI等流程.
如下效果图所示,是一个简单的多窗口程序,支持同时拉取多支股票实时行情数据并回调给UI。拿其中一个行情数据展示窗口为例来说明,数据源是来自新浪行情API接口,测试程序UI展示总共分上中下三段,上半部分主要是股票盘口数据,展示开收盘价格、实时成交量等,中段是股票买卖五档数据,最底下白色框中是数据源内容,也就是从互联网接口拉取后的数据.
正常情况下测试程序只会跑一个窗口,图示中多个窗口主要是为了观察方便.
要想实现数据复用,并减少Server压力,数据中心是必不可少的模块,举一个简单例子,当UI界面上展示的两支股票相同时,那么数据中心只会维护一支股票数据,并实时更新然后同步给两份UI界面.
如下代码所示,为行情中心接口类,其中展示了如何去订阅股票详情数据和取消订阅, IQuoteCall 这是订阅者唯一标识,每一个想要获取数据的对象都应该是一个 IQuoteCall 、或者持有一个 IQuoteCall .
struct QUOTECENTER_EXPORT IQuoteCenter
{
public:
virtual ~IQuoteCenter(){}
public:
//订阅detail
virtual void SubscribeDetail(IQuoteCall * observer, const SecurityInfo & security) = 0;
virtual void UnSubscribeDetail(IQuoteCall * observer) = 0;
.
.
.
//取消所有数据订阅
virtual void UnSubscribe(IQuoteCall * observer) = 0;
.
.
.
};
IQuoteCall 接口类中有一个 UpdateDetail 接口,通过重写该接口即可获取订阅的标的行情数据,切换标的时从新订阅即可,之前订阅的标的会被自动取消.
股票实时行情数据需要启动一个轮训任务,每隔3秒去请求一次当前订阅的所有标的数据,有了时间服务后,我们只需要抛一个任务对象和时间间隔,之后的定时触发操作则会自动被执行.
QuoteCenter::QuoteCenter()
{
qRegisterMetaType<DetailCNItem>("DetailCNItem");
qRegisterMetaType<QList<DetailCNItem>>("QList<DetailCNItem>");
m_strTaskID = Services::TimerServiceInstance()->AddTask(&DoRequests, 3000);
.
.
.
}
DoRequests 函数比较重要,可谓之承上启下,关键桥梁作用,因此这里单独做下说明.
首先 DoRequests 是一个C函数,被行情模块注册到时间管理器中,该函数会在指定时间间隔后触发一次,每次任务触发我们都需要在主线程中构造一个任务请求工作者,并把任务执行完成后的触发信号与行情对象的接收槽函数绑定,之后把任务对象抛给网络请求服务即可。任务对象后续还会有更加详细的说明,具体参看对detail请求对象说明.
void DoRequests(long long mseconds)
{
.
.
.
DetailWorker * detail = new DetailWorker(securitys);
QObject::connect(detail, &DetailWorker::Response
, static_cast<QuoteCenter *>(Quote::QuoteCenterInstance()), &QuoteCenter::OnDetailResponse);
RLNet::NetworkInstance()->AddTask(detail);
}
本地数据的唯一来源就是从网络拉取,为了程序运行流程起见,必须要运行在工作线程中,防止阻塞UI,我们开发此演示程序是基于Qt开发框架下,所以线程创建、线程交互将会变的很简单,具体细节接下来一步一步讲解.
线程池 。
既然用到Qt,那么线程池肯定也要用Qt的,这样我们开发起来会省很多力气,如下代码所示,简单的几行代码我们就搞出来一个线程池,我们只管往池子里丢任务,当池子中有空闲线程时就会帮我们处理任务,是不是非常nice.
void RLNetwork::AddTask(CommonWorker * task)
{
// 添加任务
QThreadPool::globalInstance()->start(task);
}
RLNetwork::RLNetwork()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
// 线程池初始化,设置最大线程池数
QThreadPool::globalInstance()->setMaxThreadCount(8);
}
RLNetwork::~RLNetwork()
{
curl_global_cleanup();
}
任务对象 。
有了线程池后,我们只管创建需要的task,然后丢到池子中,任务的触发时机将交给Qt线程池进行管理,我们只需要关心任务中要干什么、任务结束后怎么通知给外部即可.
任务基类 。
为了减少大量重复代码,这里我们定义一个任务基类,基类中完成每个请求任务都需要操作的内容,然后把请求体和写入内容封装成接口,供子类重写.
抽象内容包括:
class RLNETWORK_EXPORT CommonWorker : public QObject, public QRunnable
{
public:
CommonWorker();
~CommonWorker();
public:
virtual void run() override;
virtual void Write(char * data, std::size_t len) = 0;
virtual void DoRequest(CURL * curl) = 0;
static size_t CurlWriteCb(char *ptr, size_t size, size_t nmemb, void *userdata);
private:
};
Detail请求 。
说了这么多,终于到了最关键的detail请求环节,如下 StockListWorker 代码所示,当Detail请求完成后,通过Response信号通知外部任务已完成,标的detail存放在了filePath指定的文件中.
对于 StockListWorker 对象有以下几点需要注意:
class RLNETWORK_EXPORT StockListWorker : public CommonWorker
{
Q_OBJECT
public:
StockListWorker(const QString & filePath);
~StockListWorker();
signals:
void Response(const QString & filePath);
public:
virtual void Write(char * data, std::size_t len) override;
virtual void DoRequest(CURL * curl) override;
private:
QString m_filePath;
QFile m_file;
};
DoRequest 函数是基类提供给我们重写发送请求使用,如下代码所示,展示了请求detail数据的过程, 网络请求我们统一使用libcurl进行完成,不使用Qt网络库主要是觉着不好用.
void StockListWorker::DoRequest(CURL * curl)
{
curl_easy_setopt(curl, CURLOPT_URL, StockListUrl);//准备发送request的url
CURLcode res = curl_easy_perform(curl);
if (res == CURLE_OK)
{
curl_off_t val = -1;
curl_easy_getinfo(curl, CURLINFO_NAMELOOKUP_TIME_T, &val);
emit Response(m_filePath);
}
else
{
std::cout << "curl_easy_perform() failed: " << curl_easy_strerror(res);
}
}
市场服务 。
市场服务主要提供市场相关接口,如下代码所示, IsTradingStatus 接口获取当前标的所属市场是否属于交易状态,根据市场状态我们可以过滤一些无效操作,比如A股不开盘时,不需要请求detail等.
struct BASICSERVICES_EXPORT IMarketService
{
virtual ~IMarketService(){}
virtual bool IsTradingStatus(const SecurityInfo & security, long long mseconds) const = 0;
virtual bool IsTradingStatus(const QList<SecurityInfo> & securitys, long long mseconds) const = 0;
·
·
·
};
时间服务 。
时间管理器对于数据中心是相当重要的,因为有了时间维度后,我们才能去定制一批时间相关的任务,比如轮训任务、获取当前时间等.
本文中的股票实时detail数据就需要添加了一个轮训任务,因为没有长连接的加持,很多数据都需要我们自己去跟服务器要,虽然这样会增大服务器的压力,但是目前除过长连接外没有其他更好的方式去完成这件事.
struct BASICSERVICES_EXPORT ITimerService
{
virtual ~ITimerService(){}
virtual QString AddTask(const std::function<void(long long)> & fun, long internal = 3000) = 0;
virtual bool HasTask(const QString &) = 0;
virtual void RemoveTask(const QString &) = 0;
virtual void ImmediatelyTask(const QString &) = 0;
virtual long long GetCurrentStamp() const = 0;
·
·
·
};
订阅股票detail 。
如下代码所示,通过行情中心我们可以很简单的去订阅标的数据,之后通过重写 UpdateDetail 接口拿数据就行,其他的我们统一不用操心.
void HqSimple::on_pushButton_pull_clicked()
{
const QString & name = ui.comboBox->currentText();
const QString & id = ui.comboBox->currentData().toString();
const QStringList & items = id.split('_');
SecurityInfo info;
info.market = items.at(0);
info.symbol = items.at(1);
info.secType = "STK";
Quote::QuoteCenterInstance()->SubscribeDetail(this, info);
}
每一个需要订阅行情数据的对象目前都是继承自 IQuoteCall ,或者持有一个 IQuoteCall ,本篇文章包括后续系列文章都会采用第一种方案来实现数据订阅,关于继承和包含的优缺点及使用场景问题大家可以自行斟酌,本篇文章所讲述案列数据类型较少,使用继承足以完成目标.
struct QUOTECENTER_EXPORT IQuoteCall
{
virtual ~IQuoteCall(){}
virtual void UpdateDetail(const DetailCNItem &) = 0;
.
.
.
};
A股Detail数据定义 。
/*
0: 通用股份 // 名字;
1 : 5.050 // 今日开盘价
2 : 5.060 // 昨日收盘价
3 : 5.090 // 当前价格
4 : 5.110 // 今日最高价
5 : 5.030 // 今日最低价
6 : 5.090 // 竞买价,即 “买一” 报价;
7 : 5.100 // 竞卖价,即 “卖一” 报价;
8 : 3963000 // 成交的股票数,转手乘 100
9 : 20106078.000// 成交金额 (元),转万除 10000
10 : 52800 //“买一” 申请 52800 股
11 : 5.090 //“买一” 报价;
12 : 90600 //“买二” 申请 90600 股
13 : 5.080 //“买二” 报价;
14 : 98500 //..
15 : 5.070 //..
16 : 105200 //..
17 : 5.060 //..
18 : 127900 //..
19 : 5.050 //..
20 : 104400 //“卖一” 申报 104400 股
21 : 5.100 //“卖一” 报价;
22 : 99700 //“卖二” 申报 99700 股
23 : 5.110 //“卖二” 报价;
24 : 111800 //..
25 : 5.120 //..
26 : 87500 //..
27 : 5.130 //..
28 : 73300 //..
29 : 5.140 //..
30 : 2022 - 02 - 14 // 日期
31 : 11 : 18 : 56 // 时间
*/
struct STOCKDATA_EXPORT DetailCNItem : public SecurityInfo
{
//0-9
QString name; //名字
double open; //今日开盘价
double preClose; // 昨日收盘价
double lastprice; // 当前价格
double high; // 今日最高价
double low; // 今日最低价
double bid; // 竞买价,即 “买一” 报价;
double ask; // 竞卖价,即 “卖一” 报价;
double volumn; // 成交的股票数,转手乘 100
double amount; // 成交金额 (元),转万除 10000
struct AskBid
{
double price;//买/卖价
double volumn;//买/卖量
};
QVector<AskBid> asks;//卖五档 //10-19
QVector<AskBid> bids;//买五档 //20-29
//30-31
QString date; // 日期
QString time; // 时间
QString source; //原始数据
DetailCNItem();
DetailCNItem(const QString & str);
DetailCNItem(DetailCNItem && other);
void Clear();
};
刷新数据 。
UI数据刷新这里就比较简单了,文章最开始已经描述过UI数据分为上中下三部分,上部和下部就是简单文案设置,然后通过qss加了一些涨跌色配置,这里就简单展示下部分代码.
void HqSimple::UpdateDetail(const DetailCNItem & data)
{
ui.label_open->setText(QString::number(data.open, 'f', 2));
.
.
.
ui.label_amount->setText(QString::number(data.amount, 'f', 2));
m_pListmodel->SetAskBid(data);
ui.textEdit->setText(QStringLiteral("原始数据:") + data.source);
}
盘口数据分为6列:买档、买价格、买数量、卖数量、卖价格和卖档。实现起来也比较简单,标准MVC即可搞定,代码中表现为 QAbstractListModel + QListView + QStyledItemDelegate ,其中M和V都比较简单,简单的进行绑定之后就可以,这里主要说下绘制界面用的 QStyledItemDelegate ,其中最为关键的就是 paint 函数,相信用过Qt一年半载的同学都比较熟悉,代码如下所示,绘制代码比较简单就不做说明了,不明白的同学进行留言或者私聊即可.
void AskBidDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
const DetailCNItem & detail = index.model()->data(index).value<DetailCNItem>();
//left
int y = 15;
int lwidth = option.rect.width() / 2;
const QPoint & gPos = QCursor::pos();
const QPoint & lPos = option.widget->mapFromGlobal(gPos);
int t = 0;
if (lPos.x() >= 0 && lPos.x() <= lwidth)
{
t = 1;
}
else if (lPos.x() >= lwidth && lPos.x() <= lwidth * 2)
{
t = 2;
}
{
painter->fillRect(option.rect.adjusted(0, 0, lwidth, 0)
, t == 1 && option.state.testFlag(QStyle::State_MouseOver) ? QColor(28, 109, 83) : QColor(39, 67, 62));
const DetailCNItem::AskBid & bid = detail.bids.at(index.row());
const QString & bidName = QStringLiteral("买%1").arg(index.row() + 1);
painter->setPen(QColor(Qt::white));
painter->drawText(10, option.rect.top() + y, bidName);
painter->setPen(QColor(Qt::red));
painter->drawText(60, option.rect.top() + y, PriceText(bid.price));
const QString & volumnName = PriceText(bid.volumn);
int volumW = painter->fontMetrics().width(volumnName);
painter->setPen(QColor(Qt::white));
painter->drawText(lwidth - volumW, option.rect.top() + y, volumnName);
}
//right
{
painter->fillRect(option.rect.adjusted(option.rect.width() / 2, 0, 0, 0)
, t == 2 && option.state.testFlag(QStyle::State_MouseOver) ? QColor(28, 109, 83) : QColor(68, 48, 58));
const DetailCNItem::AskBid & ask = detail.asks.at(index.row());
const QString & volumnName = PriceText(ask.volumn);
painter->setPen(QColor(Qt::white));
painter->drawText(lwidth + 10, option.rect.top() + y, volumnName);
painter->setPen(QColor(Qt::green));
painter->drawText(option.rect.width() - 98, option.rect.top() + y, PriceText(ask.price));
const QString & askName = QStringLiteral("卖%1").arg(index.row() + 1);
painter->setPen(QColor(Qt::white));
painter->drawText(option.rect.width() - 28, option.rect.top() + y, askName);
}
}
讲到这里,股票行情展示程序也差不都完成了,从数据订阅、数据求情、数据缓存、数据回调和数据刷新大致都说了一遍,最后贴上项目工程截图,大家可以参考.
此篇文章算是给股票行情演示系列文章开了一个头,后续还会有更多文章出来,比如K线展示、分时图展示等,敬请期待。。.
值得一看的优秀文章:
很重要--转载声明 。
本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者: 朝十晚八 or Twowords 。
如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的.
最后此篇关于简单的股票行情展示演示(一)-实时标的数据的文章就讲到这里了,如果你想了解更多关于简单的股票行情展示演示(一)-实时标的数据的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我创建了一个邻接链表来显示城市之间的航类。该程序从 2 个文件中读取,一个包含城市名称,另一个包含不同航类的来源和目的地。我为所服务的城市创建了一个数组,并为数组的每个城市索引创建了一个连接城市的链接
我正在寻找可以让我更好地了解着色器在游戏中的用途、它们可以做什么,甚至更重要的是,它们不能做什么的资源。我了解图形管道的工作原理以及所有这些,并且我在 GLSL 中制作了一些非常基本的着色器(主要是为
我正在尝试根据单选按钮的选择制作显示两个 div 之一的东西。就单选按钮而言,我有这段代码: 然后是我的DIV: Purchase and Sale Purchase or Sale 我
谁能告诉我应该使用哪些 GitHub API 来检索 GitHub 展示?谢谢。 为了获取GitHub的列表showcases ,我应该使用什么确切的 GitHub API? 选择主题后,要检索该主题
我添加了 showcaseview jar 到我的项目中并像这样使用它 ShowcaseView.ConfigOptions co = new ShowcaseView.ConfigOptio
如果这不可能,那么在使用应用程序 3 分钟后我该如何做?这将用于“给我们评分”提醒,但我希望用户在要求他们评分之前有一些时间实际使用该应用程序。 最佳答案 - (BOOL)application:(U
除了选择 0-4 之间的随机数,如果它的 3 显示广告,是否有更好的方式来展示插页式广告? 此方法有其缺点,因为有时广告不会展示一段时间,有时它们会连续展示 3 次,这非常烦人。任何帮助将不胜感激!
我们正在尝试在我们的 Android 应用程序中监控 Firebase 云消息传递功能的状态。我们经常收到用户的反馈,他们说他们没有。 通过检查 Google Firebase 报告工具,我们发现在过
我制作了一个加载苹果测试广告的测试应用程序。我想知道如何加载实时广告而不是苹果测试广告。加载实时 iAd 的机制是什么?任何人都可以在这方面帮助我吗? 问候阿卜杜勒·萨马德 最佳答案 作为docume
我正在尝试让我的应用程序在屏幕上休眠。 我研究过 pmset 但没有成功,我什至尝试过 IOHIDPostEvent。我可以让 IOHIDPostEvent 按下 Eject 键,但设置 Shift
有没有办法使用 manim 显示 latex 表并为其设置动画? 例如 \begin{table}[] \centering \begin{tabular}{lllll} & & \multico
有没有办法使用 manim 显示 latex 表并为其设置动画? 例如 \begin{table}[] \centering \begin{tabular}{lllll} & & \multico
此代码调用新数据并将其放入 div,但不会替换旧的 htm。它只是增加了它。代码中有错误,但我找不到它们。如何获取它来替换 htm 文件而不仅仅是添加到数据中? .click(function ()
我想检测网络状态,当网络状态发生变化时,在当前 Controller 中显示错误 View 。但是使用协议(protocol)有一个问题。这是代码: private func networkingDe
我被要求创建一个小环境,展示使用 NoSQL - SQL 混合数据库相对于仅使用 SQL 数据库的优势。由于我的背景主要是管理/DevOps,所以我对数据库有基本的了解,但我从未做过这样的事情。 我想
我有一个使用模式的表单。似乎在任何浏览器上,当我单击图标(上传链接)时,模式弹出,然后页面向左缩小一点。 此处为实例:http://dev.handyvet.org/VetProsDevSite/_M
我正在准备一个大师类,向工作中的一组技术美 worker 员展示。小组中的每个人以前都使用 C/C++/MEL/MAXScript/Python 进行过编程。该类(class)的目的是共同将每个人的技
几天前,我将我的 Beta (公开 Beta 测试) 应用程序提升到生产环境。一切顺利。我可以在 Playstore 中搜索我的应用程序。但是有一个问题,Playstore 没有删除我的应用程序名称旁
使用 Bootstrap 3,我想要一个方形的平铺菜单,看起来像 Bootstrap 的缩略图 (http://getbootstrap.com/components/#thumbnails)。获得平
我想显示 admob Ad通过mopub sdk在我的一个 iPhone 应用程序中,但我找不到 API Password和API Key通过 Ad Mob。 谁能帮我将 Admob 网络集成到 mo
我是一名优秀的程序员,十分优秀!