QT5.11.3 绘制期货K线图

K线图
分时图
右侧明细显示栏全部功能实现
全景图,未载入分时数据,因为很挺麻烦的

这里只是基于QT坐标系绘制了基本的K线图,没有实现其他的指标插入;没有使用QCharts的原因很简单,不够灵活,效率也低。之前测试过QCharts绘制的蜡烛图,1000个K线就卡吐了,关闭了缩放动画也是很卡的,试想,把螺纹钢2009年的数据全部显示出来估计就完全跨了,所以还是自己画吧!

CTrMdGridBase 是基类,负责绘制基础网格

CTrMdTickChart 是分时图的类

绘图还是基于QWidget::paintEvent(QPaintEvent* event)

void CTrMdTickChart::paintEvent(QPaintEvent* event)
{
    Q_UNUSED(event);
    if(this->parent()) resize(this->parentWidget()->width(), this->parentWidget()->height());
    QPainter painter(this);
    drawBaseLine(&painter);
    drawVerticalTimeDuration(&painter);
}
void CTrMdGridBase::drawBaseLine(QPainter* painter)
{
    QPen pen;
    pen.setColor(coRed);
    painter->setPen(pen);

    // top
    painter->drawLine(0, iTopHight, iWidth, iTopHight);
    // left
    painter->drawLine(iRightWidth, iTopHight, iRightWidth, iHight);
    // right
    painter->drawLine(iWidth - iRightWidth, iTopHight, iWidth - iRightWidth, iHight);
    // bottom
    painter->drawLine(0, iHight - iBotHight, iWidth, iHight - iBotHight);

    int h = m_SizeOne.height() / 4;
    // middle horizontal line
    painter->drawLine(iLeftWidth, h*2 + iTopHight, m_SizeOne.width() + iLeftWidth, h*2 + iTopHight);

    pen.setColor(coRedBlk);
    pen.setStyle(Qt::DashLine);
    painter->setPen(pen);
    // horizontal line
    painter->drawLine(iLeftWidth, h + iTopHight, m_SizeOne.width() + iLeftWidth, h + iTopHight);
    painter->drawLine(iLeftWidth, h*3 + iTopHight, m_SizeOne.width() + iLeftWidth, h*3 + iTopHight);

    pen.setColor(Qt::white);
    painter->setPen(pen);
    painter->drawText(10, 19, InstrumentID);

    if(m_maxPrice != 0.00 && m_minPrice != 0.00)
    {
        pen.setColor(coGray);
        painter->setPen(pen);
        painter->drawText(10, 43, QString::number(m_maxPrice));
        painter->drawText(10, (iHight - iBotHight) / 2 + 17, QString::number(m_maxPrice - (m_maxPrice - m_minPrice) / 2));
        painter->drawText(10, iHight - iBotHight - 5, QString::number(m_minPrice));
    }

}

QT 绘图的最小单位是int 整型,因为数据量比较大,在计算坐标的时候基本上都是double的,在painter->drawLine的时候参数都会提示”warning: implicit conversion turns floating-point number into integer: ‘double’ to ‘int'”,这里还是说明一下,必须使用double,如果强转为int的话到最后坐标是不对的。

void CTrMdTickChart::readQuoteFile()
{
    QString file = QString(AppPath + "/TrQuotes/%1/%2.csv").arg(InstrumentID).arg("20190404");
    QFile q(file);
    if(!q.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        return;
    }

    while (!q.atEnd())
    {
        QString line = q.readLine().replace("\n", "");
        QStringList list = line.split(",");
        m_maxPrice = list.at(3).toDouble() > m_maxPrice ? list.at(3).toDouble() : m_maxPrice;
        m_minPrice = (m_minPrice == 0.00) ? list.at(3).toDouble() : (list.at(3).toDouble() < m_minPrice ? list.at(3).toDouble() : m_minPrice);

        QStringList timeMin = list.at(1).split(":");
        QString timeSign = QString("%1%2%3").arg(timeMin.at(0)).arg(timeMin.at(1)).arg(timeMin.at(2));
        if(m_mapTickDatas.contains(timeSign))
        {
            m_mapTickDatas[timeSign]    =   list.at(3).toDouble();
        }
    }
    q.close();

}

void CTrMdTickChart::drawPricePoint(QPainter* painter)
{
    QPen pen;
    pen.setColor(coYell);
    pen.setStyle(Qt::SolidLine);
    painter->setPen(pen);

    for(auto it = m_mapTickDatas.begin(); it != m_mapTickDatas.end(); it++)
    {
        if(it.value() != 0.00)
        {
            QPoint pnow = pricePoint(it.key(), it.value());
            QPoint pprve;

            int index = m_vecTickDatas.indexOf(it.key());
            if(index != 0)
            {
                if(m_mapTickDatas.contains(m_vecTickDatas.at(index-1)))
                {
                    pprve = pricePoint(m_vecTickDatas.at(index-1), m_mapTickDatas[m_vecTickDatas.at(index-1)]);
                }

            }
            if(pprve != QPoint())
                painter->drawLine(pprve, pnow);
        }
    }
}

QPoint CTrMdTickChart::pricePoint(QString st, double price)
{
    double  iwidthSpot      =   static_cast<double>(m_SizeOne.width()) / static_cast<double>(m_iSizeofSpot);
    double  dVheightSpot    =   static_cast<double>(m_SizeOne.height()) / static_cast<double>(m_maxPrice-m_minPrice);

    if(price == 0.00) return QPoint();
    int in = m_vecTickDatas.indexOf(st);
    QPoint p = QPoint(iwidthSpot*in+iLeftWidth, iTopHight+dVheightSpot*(m_maxPrice - price));
    return p;
}

说一下思路:

以螺纹钢为例,因为有夜盘,以每分钟绘制一个点来计算的话,全天应该有345分钟,也就是345个点。

绘制前首先获取绘图区域的大小

void CTrMdGridBase::updateWidgetSize()
{
    iWidth = size().width();
    iHight = size().height();
    iBotHight = iHight >= 700 ? 200 : 100;
    m_SizeOne.setWidth(iWidth - iLeftWidth - iRightWidth);
    m_SizeOne.setHeight(iHight - iBotHight - iTopHight);
}
// iLeftWidth, iRightWidth, iTopHight, iBotHight 分别是左右上下预留的高度,也就是显示头部信息,左边价格信息区域的高宽度,除去这些地方才是最后中间绘图区域的大小

每次改变窗口大小状态时需要更新一次

void CTrMdGridBase::changeEvent(QEvent* event)
{
    Q_UNUSED(event);
    updateWidgetSize();
}

void CTrMdGridBase::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);
    updateWidgetSize();
}

绘制时,用绘图区宽度(m_SizeOne.width())除以345,即可获得每分钟占绘图区的大小宽度,通过m_maxPrice,m_minPrice最高价-最低价,可计算出价格波动区间,通过与绘图区域的高度可计算出纵坐标的值。这样就完成了所有的坐标计算,K线图和分时图同理。

成交明细的计算参考自“雪山”的博文 《用CTP接口实现期货交易明细分析(2)》

CTP的文章很多,但是真正有参考意义的并不多,太多的demo都处于初级阶段,希望大家积极参与分享CTP的技术文章。