Files
QTradeProgram/QMainwindow/QMainwindow.cpp

502 lines
13 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma execution_character_set("utf-8")
#include "QMainwindow.h"
#include <QSettings>
#include <QFile>
#include <QTextStream>
#include <QTimer>
//#include <QMessageBox>
#include <QDebug>
QMainwindow::QMainwindow(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
// 初始化日志管理器
QLogManager::Instance()->setLogLevel(QLogManager::SQ_INFO);;
m_nReplyCount = 0;
m_dataAcquisition = new QDataAcquisition;
m_netCheckTimer = new QTimer(this);
m_netCheckTimer->start(1000); // 启动定时器间隔1000毫秒1秒
connect(m_netCheckTimer, &QTimer::timeout, this, [=]() {
if (m_dataAcquisition->m_InterfaceFutu->bConnected)
ui.lblNetStatu->setStyleSheet("background-color: green;");
else
ui.lblNetStatu->setStyleSheet("background-color: red;");
});
initWidget();
initReplyManage();
// 日志查看器标签页
logViewer = new QLogViewer;
//tabWidget->addTab(logViewer, "System Logs");
ui.verticalLayout_9->addWidget(logViewer);
LOG_INFO("Start Programm!");
// 创建查看器
QBigOrderViewer* viewer = new QBigOrderViewer();
ui.layoutBigorderViewer->addWidget(viewer);
QObject::connect(
viewer,
&QBigOrderViewer::sendStockCodeToMainForm,
this,
[this](const QString& code) {
int nIdex = 0;
for (const auto& item : m_replyCodes) {
if (item->stockCode == code)
break;
nIdex++;
}
if (nIdex < 0 || nIdex >= m_model->rowCount()) return;
// 清除当前选择
ui.tableReplylist->clearSelection();
// 创建选择范围(整行)
QModelIndex topLeft = m_model->index(nIdex, 0);
QModelIndex bottomRight = m_model->index(nIdex, m_model->columnCount() - 1);
QItemSelection selection(topLeft, bottomRight);
// 应用选择
ui.tableReplylist->selectionModel()->select(selection, QItemSelectionModel::Select);
// 滚动到选中行
ui.tableReplylist->scrollTo(m_model->index(nIdex, 0), QAbstractItemView::PositionAtTop);
//ui.tableReplylist->scrollTo(topLeft, QAbstractItemView::PositionAtCenter);
ui.tableReplylist->update();
}, Qt::QueuedConnection // 必须使用跨线程连接
);
auto manager = QBigOrderManager::instance();
QObject::connect(
manager,
&QBigOrderManager::markBigOrderSignal,
this,
[this]() {
m_lightWidget->triggerSignal();
}, Qt::QueuedConnection // 必须使用跨线程连接
);
// 检查对象有效性
if (!m_dataAcquisition || !m_dataAcquisition->m_InterfaceFutu) {
qCritical() << "Invalid data acquisition or interface";
return;
}
// 获取处理器指针
auto* processor = &m_dataAcquisition->m_InterfaceFutu->processor;
// 连接 maxAskOrderReady 信号
bool askConnected = QObject::connect(
processor,
&QOrderProcessor::maxOrderReady,
this,
[this](BigOrderInfo bigOrderInfo) {
if (auto manager = QBigOrderManager::instance()) {
if (!manager->addBigOrder(bigOrderInfo))
;// LOG_WARNING("Failed to add order");
}
},Qt::QueuedConnection // 必须使用跨线程连接
);
//bool bidConnected = QObject::connect(
// processor,
// &QOrderProcessor::maxBidOrderReady,
// this,
// [this](BigOrderInfo bigOrderInfo) {
// BigOrderItem order;
// order.stockCode = bigOrderInfo.code;
// order.stockName = bigOrderInfo.name;
// order.orderPrice = bigOrderInfo.price;
// order.stockQuantity = bigOrderInfo.volume;
// order.orderCount = 1;
// order.orderType = "BID";
// order.orderTime = QDateTime::currentDateTime(); // 这里时间还不准确,应该是接收到的系统时间
// if (auto manager = QBigOrderManager::instance()) {
// if (!manager->addBigOrder(order))
// LOG_WARNING("Failed to add order");
// }
// }, Qt::QueuedConnection // 必须使用跨线程连接
//);
/*
执行订阅
- 发送订阅信号
*/
QObject::connect(ui.btnDoReply, &QPushButton::clicked, [this]() {
m_nReplyCount = m_setReplayCode.size();
LOG_INFO("Start Reply!");
ui.btnDoReply->setText("正在检测 ...");
// 后面封装起来,通过信号的方式进行交互
// 筛选参数,这个放到外面去,修改之后及时更新
m_dataAcquisition->m_InterfaceFutu->processor.setreplyCodeQuantity(m_replyCodeQuantity);
m_dataAcquisition->m_InterfaceFutu->subStock();
});
/*
手动连接 OpenD
*/
QObject::connect(ui.btnConnOpend, &QPushButton::clicked, [this]() {
if (!m_dataAcquisition->m_InterfaceFutu->bConnected)
m_dataAcquisition->m_InterfaceFutu->InitConn();
});
}
QMainwindow::~QMainwindow()
{
if (m_dataAcquisition)
{
delete m_dataAcquisition;
m_dataAcquisition = NULL;
}
}
void QMainwindow::initWidget()
{
m_model = new QStandardItemModel(0, 2, this);
//m_model->setEditable(true);
m_proxyModel = new QSortFilterProxyModel(this);
// 表格视图配置
m_proxyModel->setSourceModel(m_model);
ui.tableReplylist->setModel(m_proxyModel);
//ui.tableReplylist->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui.tableReplylist->setSelectionBehavior(QAbstractItemView::SelectRows);
ui.tableReplylist->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
//ui.tableReplylist->setEditTriggers(QAbstractItemView::EditTriggers);
// 设置表头
m_model->setHorizontalHeaderLabels(
QStringList() << "股票代码" << "检测阈值");
/*
用户更改数量阈值时,同时修改列表
*/
QObject::connect(m_model, &QStandardItemModel::dataChanged,
[&](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) {
// 遍历变化区域
for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
for (int col = topLeft.column(); col <= bottomRight.column(); ++col) {
QModelIndex index = m_model->index(row, col);
QString newData = m_model->data(index, Qt::DisplayRole).toString();
m_replyCodes.at(row)->stockQuantityTh = newData.mid(0, newData.size() - 2).toFloat() * 1000;
}
}
});
QObject::connect(ui.tableReplylist->selectionModel(), &QItemSelectionModel::selectionChanged,
[&](const QItemSelection &selected, const QItemSelection &deselected) {
Q_UNUSED(deselected); // 未使用deselected避免警告
// 获取当前选中的所有行
QModelIndexList selectedIndexes = ui.tableReplylist->selectionModel()->selectedIndexes();
// 如果没有选中任何项
if (selectedIndexes.isEmpty()) {
return;
}
// 提取选中的行号(去重)
int selectedRow = selectedIndexes.at(0).row();
ui.editInputCode->setText(m_replyCodes.at(selectedRow)->stockCode);
ui.editQuantityTh->setText(QString::number(m_replyCodes.at(selectedRow)->stockQuantityTh / 1000,'f',1));
});
//ui.listAsk->setAlternatingRowColors(true);
//ui.listAsk->setStyleSheet(
// "QListWidget::item[latest=true] {"
// " background-color: #ffcccc;" // 最新行的颜色
// "}"
// "QListWidget::item {"
// " background-color: white;" // 默认行的颜色
// "}"
//);
//ui.listBid->setAlternatingRowColors(true);
//ui.listBid->setStyleSheet(
// "QListWidget::item[latest=true] {"
// " background-color: #ffcccc;" // 最新行的颜色
// "}"
// "QListWidget::item {"
// " background-color: white;" // 默认行的颜色
// "}"
//);
//// 添加项目后更新属性
//connect(ui.listAsk, &QListWidget::itemChanged, [=]() {
// if (ui.listAsk->count() > 0) {
// // 清除之前最新项的标记
// for (int i = 0; i < ui.listAsk->count(); ++i) {
// ui.listAsk->item(i)->setData(Qt::UserRole, QVariant());
// }
// // 标记最新项
// ui.listAsk->item(ui.listAsk->count() - 1)->setData(Qt::UserRole, "latest");
// }
//});
//connect(ui.listBid, &QListWidget::itemChanged, [=]() {
// if (ui.listAsk->count() > 0) {
// // 清除之前最新项的标记
// for (int i = 0; i < ui.listAsk->count(); ++i) {
// ui.listBid->item(i)->setData(Qt::UserRole, QVariant());
// }
// // 标记最新项
// ui.listAsk->item(ui.listBid->count() - 1)->setData(Qt::UserRole, "latest");
// }
//});
// 创建呼吸灯控件
m_lightWidget = new QBreathingLight();
ui.layoutBreathing->addWidget(m_lightWidget, 0, Qt::AlignCenter);
//// 创建自动测试定时器
//QTimer *autoTestTimer = new QTimer(this);
//connect(autoTestTimer, &QTimer::timeout, m_lightWidget, &QBreathingLight::triggerSignal);
//autoTestTimer->start(2000); // 每2秒自动触发一次
//connect(ui.btnConnOpend, &QPushButton::clicked, m_lightWidget, &QBreathingLight::triggerSignal);
}
void QMainwindow::initReplyManage()
{
// 读取默认订阅股票代码列表
readReplyCodefile(m_replyCodes);
LOG_INFO("订阅列表加载成功!");
m_dataAcquisition->m_InterfaceFutu->setReplyList(m_replyCodes);
// 设置代码输入规则
QIntValidator *intValidator = new QIntValidator(1, 99999, this);
ui.editInputCode->setValidator(intValidator);
// 添加/修改订阅项
QObject::connect(ui.btnAddCode, &QPushButton::clicked, [=]()
{
ReplyCodeItem order;
order.stockCode = toFixedDigitNumber(ui.editInputCode->text());
order.stockQuantityTh = ui.editQuantityTh->text().toFloat() * 1000;
bool bFinded = false;
for (const auto& item : m_replyCodes) {
if (item->stockCode == order.stockCode)
{
item->stockQuantityTh = order.stockQuantityTh;
m_replyCodeQuantity[item->stockCode] = order.stockQuantityTh;
bFinded = true;
LOG_INFO("更新完成,代码:" + order.stockCode + " ,阈值:"+QString::number(order.stockQuantityTh));
break;
}
}
if (!bFinded)
{
if (m_replyCodes.size() + 1 > 50)
{
LOG_WARNING("订阅列表添加失败,订阅数超过最大订阅数量!");
return;
}
auto newOrder = QSharedPointer<ReplyCodeItem>::create(order);
m_replyCodes.append(newOrder);
m_replyCodeQuantity.insert(order.stockCode, order.stockQuantityTh);
LOG_INFO("添加完成,代码:" + order.stockCode + " ,阈值:" + QString::number(order.stockQuantityTh));
}
updateCodeTable();
});
// 删除选订阅项
QObject::connect(ui.btnDeleteCode, &QPushButton::clicked, [=]() {
// 获取所有选中的行
QModelIndexList selected = ui.tableReplylist->selectionModel()->selectedRows();
// 删除选中行
for (int i = selected.size() - 1; i >= 0; --i) {
m_replyCodes.removeAt(selected[i].row());
m_replyCodeQuantity.remove(toFixedDigitNumber(ui.editInputCode->text()));
}
updateCodeTable();
});
/*
保存文件
*/
QObject::connect(ui.btnSaveCodefile, &QPushButton::clicked, [=]() {
saveReplyCodefile();
// 更新检测阈值
m_dataAcquisition->m_InterfaceFutu->processor.setreplyCodeQuantity(m_replyCodeQuantity);
});
}
void QMainwindow::updateCodeTable()
{
m_model->removeRows(0, m_model->rowCount());
for (const auto& order : m_replyCodes) {
QList<QStandardItem*> rowItems;
QStandardItem* codeItem = new QStandardItem(order->stockCode);
codeItem->setTextAlignment(Qt::AlignCenter);
rowItems << codeItem;
//rowItems << new QStandardItem(order->stockCode);
QStandardItem* quanityItem = new QStandardItem(QString::number(order->stockQuantityTh / 1000, 'f', 1) + "K");
quanityItem->setTextAlignment(Qt::AlignCenter);
rowItems << quanityItem;
m_model->appendRow(rowItems);
}
}
// 检查并格式化为5位数字
QString QMainwindow::toFixedDigitNumber(const QString& input) {
// 检查是否全为0-9数字
QRegularExpression re("^\\d+$");
if (!re.match(input).hasMatch()) {
return QString(); // 弹出提示框code 输入错误,暂时先返回空
}
// 长度检查和处理
if (input.length() > 5) {
return input.right(5); // 超过5位截取后5位
}
else if (input.length() < 5) {
return QString("%1").arg(input, 5, '0'); // 不足5位左侧补0
}
return input; // 正好5位直接返回
}
/*
加载订阅列表
*/
void QMainwindow::readReplyCodefile(QList<QSharedPointer<ReplyCodeItem>>& replyList)
{
QString appDir = QCoreApplication::applicationDirPath();
QString fileName = QDir(appDir).absoluteFilePath("../../config/replyCodeList.csv");
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_FATAL("无法打开文件。");
}
QTextStream in(&file);
in.setCodec("UTF-8"); // 设置编码,确保中文正常
replyList.clear(); // 清空原有数据
m_replyCodeQuantity.clear();
bool isFirstLine = true;
while (!in.atEnd()) {
QString line = in.readLine();
// 跳过空行
if (line.trimmed().isEmpty()) {
continue;
}
// 跳过标题行(假设第一行是标题)
if (isFirstLine) {
isFirstLine = false;
continue;
}
// 解析CSV行
QStringList fields = line.split(',');
// 确保有足够的字段根据你的CSV结构调整
if (/*fields.size() >= 3*/1) {
QString code = fields[0].trimmed();
QString description = fields[1].trimmed();
//bool isActive = fields[2].trimmed().toLower() == "true" || fields[2].trimmed() == "1";
ReplyCodeItem replyItem;
replyItem.stockCode = toFixedDigitNumber(code);
replyItem.stockQuantityTh = description.toFloat();
// 创建ReplyCodeItem并添加到列表
auto item = QSharedPointer<ReplyCodeItem>::create(replyItem);
m_replyCodes.append(item);
// 保存到映射表
m_replyCodeQuantity.insert(replyItem.stockCode, replyItem.stockQuantityTh);
}
else {
//qWarning() << "忽略格式不正确的行:" << line;
}
}
file.close();
updateCodeTable();
}
/*
保存订阅列表
*/
void QMainwindow::saveReplyCodefile()
{
//// 配置文件名
QString appDir = QCoreApplication::applicationDirPath();
QString fileName = QDir(appDir).absoluteFilePath("../../config/replyCodeList.csv");
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
LOG_FATAL("无法创建订阅文件。" );
return;
}
QTextStream out(&file);
out.setCodec("GBK"); // 确保使用UTF-8编码
// 写入表头
for (int col = 0; col < m_model->columnCount(); ++col) {
out << escapeCsv(m_model->headerData(col, Qt::Horizontal).toString());
if (col < m_model->columnCount() - 1) out << ",";
}
out << "\n";
// 写入数据
for (const auto &value : m_replyCodes) {
out << value->stockCode<<","<< value->stockQuantityTh << "\n";
}
file.close();
LOG_INFO("订阅列表保存成功!");
}
QString QMainwindow::escapeCsv(const QString &field) {
if (field.contains(',') || field.contains('"') || field.contains('\n')) {
QString escaped = field;
escaped.replace("\"", "\"\"");
return "\"" + escaped + "\"";
}
return field;
}