XLEdoo 2024-11-12 17:16 采纳率: 66.7%
浏览 17
已结题

C++行情软件的tick数据如何高效的合成K线

#写一个期货交易软件,其中涉及到行情时间,比如1分钟(还有3分钟 5分钟10分钟15 30 60 120)每天早上9点开盘,09:00-10:15  10:30-11:30 13:30-15:00 21:00-23:00 有很多交易的品种,而且交易的时间段都有所不同,我已经把不同的交易时间段品种进行了分类。
我在一个QTimer里每1秒运行一次函数,通过接受到的tick数据进行K线计算并推送bar的信息。

现在的方案是:
我用map预先把每个周期所对应的时间点手动添加进去,这样的好处是避免了调用时计算时间点的开销,弊端是看起来有点傻,类似下面:

// 定义一个时间段
#define F_1M_21002259 "2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159,2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243,2244,2245,2246,2247,2248,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259"

// 把时间段合并成某个品种的交易时间点
#define FN2300_M1  _isspace(F_1M_21002259 FSEP F_1M_09001459)

// 加入到map
static std::map<std::string, shine::string>  g_static_session_mapping = {
    {"FN2300_M1", FN2300_M1},
    {"FN0100_M1", FN0100_M1},
    {"FN0230_M1", FN0230_M1},
    {"FD0900_M1", FD0900_M1},
    {"FD1515_M1", FD1515_M1},
    {"SD0930_M1", SD0930_M1},
    //-------------------------
    {"FN2300_M3", FN2300_M3},
    {"FN0100_M3", FN0100_M3},
    {"FN0230_M3", FN0230_M3},
    {"FD0900_M3", FD0900_M3},
    {"FD1515_M3", FD1515_M3},
    {"SD0930_M3", SD0930_M3},

每个元素代表一个1分钟的时间点 还有3分钟 5分钟各个周期都需要单独写出来。
请问有没有更高效的方案?

这里需要说明一下,1分钟 3分钟 5分钟 10 15这些小周期比较好处理,其中还有1小时 2小时这样的周期比较麻烦,因为期货的交易时间段在10:15-10:30是暂停交易的,有的交易软件的1小时周期并不是点对点的,比如文华财经的1小时图就是 10:00-11:15 是一根K线,11:15-14:1514:15-15:00 是一根K线,而2小时的K线又有单独对应2小时的时间点,不规律的。这里看有没有什么办法处理。

共773个合约,每个合约每秒钟2个tick数据,一个小时有 60260*773=5565600个tick(实际的量比这个小,因为非主力合约交易不活跃),这个数据量还是非常恐怖的。

感谢大家的积极回答,已经收到了一些方案,还有一些细节需要处理:

//=============================================================================================
// 这里的时间点定义应该分两种情况:
// 1、判断是否为交易时间时,只需要判断tick的时间是否处于交易时段内即可,需要包含起始时间和结束时间在内
//    仅需要用到1分钟的级别即可。
// 2、Bar的触发因为是在每一个周期的最后一个tick时进行,所以需要计算出每个周期对应的最后一个时间,也就是
//    结束时间,而且因为10:15-10:30休市的情况,10分钟,30分钟,1小时,2小时这种周期就不好通过函数计算
//    目前的解决方案就是通过“硬编码”来定义每个特殊周期的触发时间点来单独控制。
//    例如:10分钟周期到10:10分这里就变成了10:10-10:35,10:35-10:45,10:45-10:55,......11:25-13:35
//    其他特殊周期也是如此处理的,希望能找到更好的解决方案。
//=============================================================================================
std::map<std::string, std::vector<TradingSession>> tradingSessions = {
    {"FN2300", { {9, 0, 10, 15}, {10, 30, 11, 30}, {13, 30, 15, 0}, {21, 0, 23, 0} }},
    {"FN0100", { {9, 0, 10, 15}, {10, 30, 11, 30}, {13, 30, 15, 0}, {21, 0, 1, 0} }},
    {"FN0230", { {9, 0, 10, 15}, {10, 30, 11, 30}, {13, 30, 15, 0}, {21, 0, 2, 30} }},
    {"FD0900", { {9, 0, 10, 15}, {10, 30, 11, 30}, {13, 30, 15, 0} }},
    {"FD1515", { {9, 0, 10, 15}, {10, 30, 11, 30}, {13, 30, 15, 15} }},
    {"SD0930", { {9, 30, 11, 30}, {13, 0, 15, 0} }}
};

std::vector<std::string> generateKLineTimes(const TradingSession& session, TrPeriod period) {
    std::vector<std::string> times;
 
    int period_minutes = static_cast<int>(period);
    int hour = session.startHour;
    int minute = session.startMinute;
 
    while (hour < session.endHour || (hour == session.endHour && minute < session.endMinute)) {
        std::ostringstream oss;
        oss << (hour < 10 ? "0" : "") << hour << (minute < 10 ? "0" : "") << minute;
        times.push_back(oss.str());
        qDebug() << oss.str().c_str();
        minute += period_minutes;
        if (minute >= 60) {
            hour += minute / 60;
            minute %= 60;
        }
    }
    return times;
}

std::vector<std::string> getKLineTimes(const std::string& product, TrPeriod period) {
    std::vector<std::string> allTimes;
    if (tradingSessions.find(product) != tradingSessions.end()) {
        for (const auto& session : tradingSessions[product]) {
            auto times = generateKLineTimes(session, period);
            allTimes.insert(allTimes.end(), times.begin(), times.end());
        }
    }
    return allTimes;
}

// 这里已经可以得到时间点了,问题在于一些特殊的周期该怎么处理才好?需要的是每个周期Bar结束时触发的那个时间点。谢谢大家!
  • 写回答

26条回答 默认 最新

  • 杨同学* 2024-11-12 20:37
    关注

    该回答引用ChatGPT4o, 并由本人整理回复, 如有帮助,还请采纳。
    在你的应用中,通过固定的时间点字符串方式来存储每个周期的时间点虽然能减少每次计算的开销,但确实显得不够灵活且难以维护。为了解决这个问题,以下是一些更高效的方案,能够避免手动定义大量的时间点,同时保持计算的高效性。

    方案 1:使用算法动态生成K线周期的时间点

    可以在程序初始化时根据每个交易品种的时间段,使用算法动态生成不同周期的时间点,而不需要手动定义每个时间点。可以将每个时间段和周期的配置信息保存为数据结构,然后生成所有时间点并缓存,以便在需要时快速查找。

    实现步骤:

    1. 定义交易时间段结构体,每个品种包含多个时间段。
    2. 定义周期枚举,如1分钟、3分钟、5分钟等。
    3. 初始化时动态生成时间点,根据时间段和周期生成所有可能的时间点并缓存。

    以下是具体的实现示例:

    #include <iostream>
    #include <map>
    #include <vector>
    #include <string>
    
    struct TimeRange {
        int start;  // 开始时间,如 900 表示 09:00
        int end;    // 结束时间,如 1015 表示 10:15
    };
    
    enum KLinePeriod {
        M1 = 1,
        M3 = 3,
        M5 = 5,
        M10 = 10,
        M15 = 15,
        M30 = 30,
        M60 = 60,
        M120 = 120
    };
    
    std::map<KLinePeriod, std::vector<int>> GenerateTimePoints(const std::vector<TimeRange>& tradingSessions, KLinePeriod period) {
        std::map<KLinePeriod, std::vector<int>> timePoints;
    
        for (const auto& session : tradingSessions) {
            int currentTime = session.start;
            while (currentTime <= session.end) {
                timePoints[period].push_back(currentTime);
                // 增加时间点
                currentTime += period;
                // 处理分钟的进位情况
                if (currentTime % 100 >= 60) {
                    currentTime = (currentTime / 100 + 1) * 100 + (currentTime % 100 - 60);
                }
            }
        }
    
        return timePoints;
    }
    
    int main() {
        // 定义交易时段
        std::vector<TimeRange> tradingSessions = {{900, 1015}, {1030, 1130}, {1330, 1500}, {2100, 2300}};
    
        // 生成1分钟的K线时间点
        auto timePoints = GenerateTimePoints(tradingSessions, M1);
    
        // 输出生成的时间点
        for (const auto& point : timePoints[M1]) {
            std::cout << point << " ";
        }
        return 0;
    }
    

    方案 2:利用分组缓存+滚动窗口计算

    在生成K线的过程中,只需要维护每个周期的当前最新时间点,而不是生成整个时间表。可以使用滚动窗口的方式根据收到的Tick数据的时间戳来判断是否到了下一个K线周期:

    1. 根据Tick数据的时间戳实时判断当前K线是否完成。
    2. 如果K线周期到了(例如1分钟完成),则将当前K线数据推送,然后将窗口滑动到下一个时间点。
    3. 动态滚动计算,不需要提前存储所有时间点。

    这样做的好处是节省内存,只保存最新的K线数据;缺点是需要在每个Tick数据处理时判断时间周期。

    方案 3:预计算缓存 + 高效查找

    在程序启动时,可以使用方案1生成所有时间点,并将每个时间点按周期(1分钟、3分钟、5分钟等)缓存到std::unordered_map中。这样在Tick数据到来时,可以直接查找时间点,而不是逐个遍历判断。

    #include <iostream>
    #include <unordered_map>
    #include <vector>
    
    struct TimeRange {
        int start;
        int end;
    };
    
    enum KLinePeriod {
        M1 = 1,
        M3 = 3,
        M5 = 5,
        M10 = 10,
        M15 = 15,
        M30 = 30,
        M60 = 60,
        M120 = 120
    };
    
    std::unordered_map<KLinePeriod, std::unordered_map<int, bool>> PrecomputeTimeMap(const std::vector<TimeRange>& sessions, KLinePeriod period) {
        std::unordered_map<KLinePeriod, std::unordered_map<int, bool>> timeMap;
    
        for (const auto& session : sessions) {
            int time = session.start;
            while (time <= session.end) {
                timeMap[period][time] = true;
                time += period;
                if (time % 100 >= 60) {
                    time = (time / 100 + 1) * 100 + (time % 100 - 60);
                }
            }
        }
    
        return timeMap;
    }
    
    int main() {
        std::vector<TimeRange> sessions = {{900, 1015}, {1030, 1130}, {1330, 1500}, {2100, 2300}};
        auto timeMap = PrecomputeTimeMap(sessions, M1);
    
        // 判断某时间点是否存在于缓存中
        int currentTime = 910;
        if (timeMap[M1].find(currentTime) != timeMap[M1].end()) {
            std::cout << "时间点 " << currentTime << " 在1分钟周期内" << std::endl;
        } else {
            std::cout << "时间点 " << currentTime << " 不在1分钟周期内" << std::endl;
        }
    
        return 0;
    }
    

    方案总结

    • 方案 1(动态生成):在程序初始化时生成所有需要的时间点,适合缓存所有周期的时间点。
    • 方案 2(滚动窗口):实时滚动生成K线,只维护当前时间点的窗口,节省内存但增加每秒判断的计算开销。
    • 方案 3(预计算缓存):结合方案1和unordered_map的查找效率,将每个周期的时间点放入哈希表中,加速查找。

    在你的场景中,方案1或方案3是较好的选择。它们既可以避免手动维护大量的时间点,又可以实现高效的时间点查找和推送K线。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(25条)

报告相同问题?

问题事件

  • 系统已结题 11月24日
  • 已采纳回答 11月16日
  • 赞助了问题酬金15元 11月13日
  • 修改了问题 11月13日
  • 展开全部

悬赏问题

  • ¥15 分析下图所示同步时序逻辑电路的逻辑功能。
  • ¥15 halcon联合c#遇到了问题不能解决
  • ¥15 xshell无法连接提示ssh服务器拒绝密码
  • ¥15 AT89C52单片机C语言关于串口通信的位操作
  • ¥20 需要步骤截图(标签-服务器|关键词-map)
  • ¥50 gki vendor hook
  • ¥15 灰狼算法和蚁群算法如何结合
  • ¥15 这是一个利用ESP32自带按键和LED控制的录像代码,编译过程出现问题,请解决并且指出错误,指导如何处理 ,协助完成代码并上传代码
  • ¥20 stm32f103,hal库 hal_usart_receive函数接收不到数据。
  • ¥20 求结果和代码,sas利用OPTEX程序和D-efficiency生成正交集