QCustomPlot之K线图(十七)

效果图

K线图须要引入的类:QCPFinancial,首先来看下K线图的示意图:html

K线图示意图

其中阳线在中国通常使用红色表示,阴线使用绿色表示apache

K线图的数据结构

QCPFinancialDataQCPFinancial所使用的数据结构,包含五个数据类型,以下所示:数据结构

数据 含义
key key轴坐标
open 开盘
close 关盘
low 最低
high 最高

K线图的风格

函数 含义
setChartStyle csOhlc(美国线)
csCandlestick(蜡烛图)
setWidth wtAbsolute(像素)
wtAxisRectRatio(轴矩形比例)
wtPlotCoords(坐标轴,默认)
setTwoColored 是否显示两种颜色,即阳线和阴线能够有各自的颜色
setBrushPositive 阳线画刷
setBrushNegative 阴线画刷
setPenPositive 阳线画笔
setPenNegative 阴线画笔

timeSeriesToOhlc函数

若是数据仅有一系列值(例如价格与时间)可用,则能够使用静态函数timeSeriesToOhlc生成合并的OHLC数据,而后将其传递给setData函数app

参数 含义
time 时间
value
timeBinSize 时间间隔大小,通常是一天(3600*24)
timeBinOffset 时间起始,通常传入time[0]

完整示例

来源:echartsecharts

class MyAxisTickerText : public QCPAxisTickerText
{
protected:
    virtual QVector<double> createTickVector(double tickStep, const QCPRange &range) Q_DECL_OVERRIDE
    {
        Q_UNUSED(tickStep)
        QVector<double> result;
        if (mTicks.isEmpty())
            return result;

        auto start = mTicks.lowerBound(range.lower);
        auto end = mTicks.upperBound(range.upper);
        if (start != mTicks.constBegin()) --start;
        if (end != mTicks.constEnd()) ++end;

        int count = cleanMantissa(std::distance(start, end) / double(mTickCount + 1e-10));

        auto it = start;
        while (it != end) {
            result.append(it.key());
            int step = count;
            if (step == 0) ++it;
            while (--step >= 0 && it != end)
                ++it;
        }

        return result;
    }
};

void MainWindow::setupShangHaiIndexDemo(QCustomPlot *customPlot)
{
    const QColor BrushPositive("#ec0000");
    const QColor PenPositive("#8a0000");
    const QColor BrushNegative("#00da3c");
    const QColor PenNegative("#008f28");

    const QVector<QString> rawTimes = {
        "2013/1/24", "2013/1/25", "2013/1/28", "2013/1/29", "2013/1/30", "2013/1/31", "2013/2/1", "2013/2/4", "2013/2/5",  "2013/2/6", "2013/2/7",
        "2013/2/8",  "2013/2/18", "2013/2/19", "2013/2/20", "2013/2/21", "2013/2/22", "2013/2/25", "2013/2/26", "2013/2/27", "2013/2/28", "2013/3/1",
        "2013/3/4",  "2013/3/5",  "2013/3/6",  "2013/3/7", "2013/3/8",  "2013/3/11", "2013/3/12", "2013/3/13", "2013/3/14", "2013/3/15", "2013/3/18",
        "2013/3/19", "2013/3/20", "2013/3/21", "2013/3/22", "2013/3/25", "2013/3/26", "2013/3/27", "2013/3/28", "2013/3/29", "2013/4/1", "2013/4/2",
        "2013/4/3",  "2013/4/8",  "2013/4/9",  "2013/4/10", "2013/4/11", "2013/4/12", "2013/4/15", "2013/4/16", "2013/4/17", "2013/4/18", "2013/4/19",
        "2013/4/22", "2013/4/23", "2013/4/24", "2013/4/25", "2013/4/26", "2013/5/2",  "2013/5/3",  "2013/5/6",  "2013/5/7",  "2013/5/8",  "2013/5/9",
        "2013/5/10", "2013/5/13", "2013/5/14", "2013/5/15", "2013/5/16", "2013/5/17", "2013/5/20", "2013/5/21", "2013/5/22", "2013/5/23", "2013/5/24",
        "2013/5/27", "2013/5/28", "2013/5/29", "2013/5/30", "2013/5/31", "2013/6/3",  "2013/6/4",  "2013/6/5",  "2013/6/6",  "2013/6/7",  "2013/6/13",
    };
    // 数据意义:开盘(open),收盘(close),最低(lowest),最高(highest)
    const QVector<QVector<double>> rawDatas = {
        { 2320.26,2320.26,2287.3,2362.94}, { 2300,2291.3,2288.26,2308.38}, { 2295.35,2346.5,2295.35,2346.92}, { 2347.22,2358.98,2337.35,2363.8},
        { 2360.75,2382.48,2347.89,2383.76}, { 2383.43,2385.42,2371.23,2391.82}, {2377.41,2419.02,2369.57,2421.15}, {2425.92,2428.15,2417.58,2440.38},
        {2411,2433.13,2403.3,2437.42}, {2432.68,2434.48,2427.7,2441.73}, {2430.69,2418.53,2394.22,2433.89}, {2416.62,2432.4,2414.4,2443.03},
        { 2441.91,2421.56,2415.43,2444.8}, { 2420.26,2382.91,2373.53,2427.07}, { 2383.49,2397.18,2370.61,2397.94}, { 2378.82,2325.95,2309.17,2378.82},
        { 2322.94,2314.16,2308.76,2330.88}, { 2320.62,2325.82,2315.01,2338.78}, { 2313.74,2293.34,2289.89,2340.71}, { 2297.77,2313.22,2292.03,2324.63},
        { 2322.32,2365.59,2308.92,2366.16}, {2364.54,2359.51,2330.86,2369.65}, {2332.08,2273.4,2259.25,2333.54}, {2274.81,2326.31,2270.1,2328.14},
        {2333.61,2347.18,2321.6,2351.44}, {2340.44,2324.29,2304.27,2352.02}, {2326.42,2318.61,2314.59,2333.67}, { 2314.68,2310.59,2296.58,2320.96},
        { 2309.16,2286.6,2264.83,2333.29}, { 2282.17,2263.97,2253.25,2286.33}, { 2255.77,2270.28,2253.31,2276.22}, { 2269.31,2278.4,2250,2312.08},
        { 2267.29,2240.02,2239.21,2276.05}, { 2244.26,2257.43,2232.02,2261.31}, { 2257.74,2317.37,2257.42,2317.86}, { 2318.21,2324.24,2311.6,2330.81},
        { 2321.4,2328.28,2314.97,2332}, { 2334.74,2326.72,2319.91,2344.89}, { 2318.58,2297.67,2281.12,2319.99}, { 2299.38,2301.26,2289,2323.48},
        { 2273.55,2236.3,2232.91,2273.55}, { 2238.49,2236.62,2228.81,2246.87}, {2229.46,2234.4,2227.31,2243.95}, {2234.9,2227.74,2220.44,2253.42},
        {2232.69,2225.29,2217.25,2241.34}, {2196.24,2211.59,2180.67,2212.59}, {2215.47,2225.77,2215.47,2234.73}, { 2224.93,2226.13,2212.56,2233.04},
        { 2236.98,2219.55,2217.26,2242.48}, { 2218.09,2206.78,2204.44,2226.26}, { 2199.91,2181.94,2177.39,2204.99}, { 2169.63,2194.85,2165.78,2196.43},
        { 2195.03,2193.8,2178.47,2197.51}, { 2181.82,2197.6,2175.44,2206.03}, { 2201.12,2244.64,2200.58,2250.11}, { 2236.4,2242.17,2232.26,2245.12},
        { 2242.62,2184.54,2182.81,2242.62}, { 2187.35,2218.32,2184.11,2226.12}, { 2213.19,2199.31,2191.85,2224.63}, { 2203.89,2177.91,2173.86,2210.58},
        {2170.78,2174.12,2161.14,2179.65}, {2179.05,2205.5,2179.05,2222.81}, {2212.5,2231.17,2212.5,2236.07}, {2227.86,2235.57,2219.44,2240.26},
        {2242.39,2246.3,2235.42,2255.21}, {2246.96,2232.97,2221.38,2247.86}, { 2228.82,2246.83,2225.81,2247.67},  { 2247.68,2241.92,2231.36,2250.85},
        { 2238.9,2217.01,2205.87,2239.93}, { 2217.09,2224.8,2213.58,2225.19}, { 2221.34,2251.81,2210.77,2252.87}, { 2249.81,2282.87,2248.41,2288.09},
        { 2286.33,2299.99,2281.9,2309.39},  { 2297.11,2305.11,2290.12,2305.3},  { 2303.75,2302.4,2292.43,2314.18}, { 2293.81,2275.67,2274.1,2304.95},
        { 2281.45,2288.53,2270.25,2292.59}, { 2286.66,2293.08,2283.94,2301.7}, { 2293.4,2321.32,2281.47,2322.1}, { 2323.54,2324.02,2321.17,2334.33},
        { 2316.25,2317.75,2310.49,2325.72}, { 2320.74,2300.59,2299.37,2325.53}, {2300.21,2299.25,2294.11,2313.43}, {2297.1,2272.42,2264.76,2297.1},
        {2270.71,2270.93,2260.87,2276.86}, {2264.43,2242.11,2240.07,2266.69}, {2242.26,2210.9,2205.07,2250.63}, { 2190.1,2148.35,2126.22,2190.1}
    };

    QSharedPointer<QCPAxisTickerText> textTicker(new MyAxisTickerText);     // 文字轴
    textTicker->setTickCount(10);
    QCPDataContainer<QCPFinancialData> datas;
    QVector<double> timeDatas, MA5Datas, MA10Datas, MA20Datas, MA30Datas;

    MA5Datas = calculateMA(rawDatas, 5);
    MA10Datas = calculateMA(rawDatas, 10);
    MA20Datas = calculateMA(rawDatas, 20);
    MA30Datas = calculateMA(rawDatas, 30);

    for (int i = 0; i < rawTimes.size(); ++i) {
        timeDatas.append(i);

        QCPFinancialData data;
        data.key = i;
        data.open = rawDatas.at(i).at(0);
        data.close = rawDatas.at(i).at(1);
        data.low = rawDatas.at(i).at(2);
        data.high = rawDatas.at(i).at(3);
        datas.add(data);

        textTicker->addTick(i, rawTimes.at(i));
    }
    

    QCPFinancial *financial = new QCPFinancial(customPlot->xAxis, customPlot->yAxis);
    financial->setName("日K");
    financial->setBrushPositive(BrushPositive);
    financial->setPenPositive(PenPositive);
    financial->setBrushNegative(BrushNegative);
    financial->setPenNegative(PenNegative);
    financial->data()->set(datas);

    const QVector<QColor> ColorOptions = {
        "#c23531", "#2f4554", "#61a0a8", "#d48265"
    };

    QCPGraph *graph = customPlot->addGraph();
    graph->setName("MA5");
    graph->setData(timeDatas, MA5Datas);
    graph->setPen(ColorOptions.at(0));
    graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(ColorOptions.at(0), 2), QBrush(Qt::white), 8));
    graph->setSmooth(true);

    graph = customPlot->addGraph();
    graph->setName("MA10");
    graph->setData(timeDatas, MA10Datas);
    graph->setPen(ColorOptions.at(1));
    graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(ColorOptions.at(1), 2), QBrush(Qt::white), 8));
    graph->setSmooth(true);

    graph = customPlot->addGraph();
    graph->setName("MA20");
    graph->setData(timeDatas, MA20Datas);
    graph->setPen(ColorOptions.at(2));
    graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(ColorOptions.at(2), 2), QBrush(Qt::white), 8));
    graph->setSmooth(true);

    graph = customPlot->addGraph();
    graph->setName("MA30");
    graph->setData(timeDatas, MA30Datas);
    graph->setPen(ColorOptions.at(3));
    graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(ColorOptions.at(3), 2), QBrush(Qt::white), 8));
    graph->setSmooth(true);

    customPlot->xAxis->setTicker(textTicker);
    customPlot->rescaleAxes();
    customPlot->xAxis->scaleRange(1.05, customPlot->xAxis->range().center());
    customPlot->yAxis->scaleRange(1.05, customPlot->yAxis->range().center());
    customPlot->legend->setVisible(true);
}

QVector<double> MainWindow::calculateMA(const QVector<QVector<double> > &v, int dayCount)
{
    auto func = [](double result, const QVector<double> &v2){
      return result + v2[1];
    };

    QVector<double> result;
    for (int i = 0; i < v.size(); ++i) {
        if (i < dayCount) {
            result.append(qQNaN());
        } else {
            double sum = std::accumulate(v.begin() + i - dayCount + 1, v.begin() + i + 1, 0.0, func);
            result.append(sum / dayCount);
        }
    }
    return result;
}

最后

  1. 不使用 QCPAxisTickerDateTime 做为轴标签,是由于数据的日期不是连续的,使用QCPAxisTickerDateTime会致使不连续的部分有间隔,若是须要使用QCPAxisTickerDateTime的话须要设置setTickOrigin为时间的第一个数据,否则的话会发生K线图与坐标轴对应不上的状况,同时还要设置K线图的宽度setWidth,例如一天的宽度financial->setWidth(3600 * 24 * 0.8),乘以0.8是为了稍微缩小一点
  2. 继承QCPAxisTickerText的缘由是由于QCPAxisTickerText在数据比较多的时候轴标签会挤在一块儿,密密麻麻的很差看
相关文章
相关标签/搜索