【PA交易】前端根据内盘商品期货Tick数据合并日线Bar

概要

本文是JS的合并tick数据位日线Bar的实现。同时提供笔者自己整理过的几个农产品主力合约从2020年到2024年的tick数据资源,更多品种tick数据资源可以通过笔者上传资源进行下载,后续笔者会不断补充上传,可关注本号方便收到最新更新上传的其他分享。

背景

我们从网上下载的Tick数据总会担心是否可靠,其中最简单常用的办法就是合并成K线图进行验证。通常的交易软件对于历史合约不会显示太久远的分钟线(毕竟存储成本太高了),但是多数软件都支持历史合约的日线显示。比如文华就可以显示日线,例如下图为文华中09合约的2022年的交易日线图。

测试数据

测试用数据是已经整理好导入MySQL5.7版本数据库豆粕期货1月、5月、9月三个主力合约从2020年到2024年的tick数据,数据可以结合本文描述的测试程序和文华历史数据对比进行验证。

百度网盘连接(测试目的,请勿商用):

链接:https://pan.baidu.com/s/1JG1eXB3MBJ9c8TTxHF1vZQ 
提取码:ezme 

验证程序

考虑到Python的mpl_finance如果是数据比对的目的从显示和选择等各个方面都比较麻烦,笔者直接使用Echart来作为tick数据验证。后端使用一段简单的express直接访问数据库,做CRUD的透传:

const express = require('express');
const mysql = require('mysql');

const app = express();


async function runQuery(dbname, query) {
    return new Promise((resolve) => {
        //主要为了示意目的.
        // 对于不同的品种采用了分库分表, 为了测试方便没有使用预创建连接池的方式,
        // 而是简单粗暴的使用了每次请求都创建连接, 
        const connection = mysql.createConnection({
            host: 'localhost',
            user: 'root',
            password: '123456',
            database: dbname
        });


        console.log(query)
        connection.connect((err) => {
            if (err) {
                resolve(500);
                // return res.status(500).send('Database connection failed');
            }

            connection.query(query, (error, results) => {
                connection.end(); // 执行完查询后关闭连接

                if (error) {
                    resolve(500);
                }

                resolve(results);
            });
        });
    });
}

app.get('/getDatas', async (req, res) => {
    //---------------------------------------------------
    // 从请求的查询参数中获取值
    const {db, tbl, exchg, code, start, end} = req.query

    //转换起止日期
    const {startDate, endDate} = convertStartEnd(start, end);

    // 构建SQL查询语句
    let query = `SELECT * FROM `;
    if (tbl === 'bar15m') {
        query += tbl + ` WHERE datetime BETWEEN '${startDate}' AND '${endDate}' AND symbol='${symbol}' `;
    } else {
        query += tbl + ` WHERE datetime BETWEEN '${startDate}' AND '${endDate}' `;
    }
    query += ' ORDER BY `datetime`;';

    const resp = await runQuery(db, query);

    return res.json({data: resp});
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

注意, 数据使用了每个品种分库, 每个合约分表的方式. 数据验证程序后端中直接在请求中建立连接。这只是为了比较简单的去实现和演示功能。如果为了测试方便可以考虑自行提前建立连接的方式。

同时前端使用最简单输入框进行查询参数选择:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>客户端测试</title>
    <!-- 引入 ECharts -->
    <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.3.2/echarts.min.js"></script>
</head>
<body>

<!-- 添加输入框 -->
<div>
    <label for="y-extends">K线图Y轴扩展系数 (0~1):</label>
    <input type="number" id="y-extends" min="0" max="1" step="0.01" value="0.05">
    <br> <br>
    <!-- 略 -->
    
</div>

<!-- 用于显示图表的div元素 -->
<div id="mainChart" style="width: 600px;height:400px;"></div>

<script type="module">
    import {fetchAndShowDatas, setChart} from './functions.js';

    document.getElementById('fetchDataBtn').addEventListener('click', function () {
        fetchAndShowDatas().then(
        // 略....
    });
</script>

</body>
</html>

functions.js文件中, 接口fetchAndShowDatas()主要操作包括:

  1. 读取几个input的数值
  2. 组成请求字符串, 下载Tick数据
  3. 合并K线
  4. Echart显示

对于比较简单的内容不再赘述,仅分享一下合并K线的逻辑:


function mergeDayBars(tickDatas) {

    // UTC时间转为北京时间(UTC+8),并获取日期和小时
    const processTick = (tick) => {
        let ts = new Date(tick.datetime).toLocaleString();
        const [dateStr, timeStr] = ts.split(' ');
        const [hour] = timeStr.split(':');
        return {dateStr, hour: parseInt(hour)};
    };

    const makeCurrentBar = (barData, tick, dateStr) => {
        if (!barData) {
            return {
                date: dateStr,
                open: tick.price,
                high: tick.price,
                low: tick.price,
                close: tick.price,
                vol: tick.vol
            }
        } else {
            barData.date = dateStr;
            barData.close = tick.price;
            barData.high = Math.max(barData.high, tick.price);
            barData.low = Math.min(barData.low, tick.price);
            barData.vol = tick.vol;
            return barData;
        }
    }
    const dayBars = [];
    let lastTick = {date: null, hour: null};
    let currentBar = {open: null, high: null, low: null, close: null, vol: 0};

    const closeForOneDay = () => {
        console.log(`Day bar ends: `, currentBar);
        dayBars.push(currentBar);
        currentBar = {open: null, high: null, low: null, close: null, vol: 0};
    };

    console.log('first tick:', tickDatas[0]);
    console.log('last tick:', tickDatas[tickDatas.length-1]);

    // 主函数逻辑
    tickDatas.forEach((tick) => {
        const {dateStr, hour} = processTick(tick);

        if (!tick.price || tick.amount === 0) {
            return;
        }
        if (hour < 20 && hour > 16) { //丢弃非交易时间的数据
            return;
        }

        //----------------------------------------------
        //核心逻辑
        //第一个Tick, 无需特殊处理直接存入当前bar中
        if (!lastTick.date || !lastTick.hour) {
            currentBar = makeCurrentBar(null, tick, dateStr);
            lastTick.date = dateStr;
            lastTick.hour = hour;
            return;
        }

        const curDayTrade = hour < 19 && hour > 7;
        const lastDayTrade = lastTick.hour < 19 && lastTick.hour > 7;

        if (lastTick.date === dateStr && curDayTrade === lastDayTrade) {
            //同一日, 直接存储即可
            currentBar = makeCurrentBar(currentBar, tick, dateStr);
        } else if (curDayTrade) {
            if (lastDayTrade) {
                //不是同一日, 上一个Tick是日盘, 说明当前bar没有夜盘, 存储bar, 并且生成新的bar
                closeForOneDay();
                console.log(`Day bar ends: next tick ${tick}, last tick=${lastTick.date}:${lastTick.hour}`);
                currentBar = makeCurrentBar(null, tick, dateStr);
            } else {
                //不是同一日, 上一个Tick是夜盘, 直接使用当前bar日期去更新正在处理的bar, 同时正常计算
                currentBar = makeCurrentBar(currentBar, tick, dateStr);
            }
        } else {
            //如果进入夜盘, 且日期不是同一天, 那么无论如何都应该保存旧的bar, 并且生成新的bar
            closeForOneDay();
            console.log(`Day bar ends: next tick ${tick}, last tick=${lastTick.date}:${lastTick.hour}`);
            currentBar = makeCurrentBar(null, tick, dateStr);
        }

        //更新上一个Tick信息
        lastTick.date = dateStr;
        lastTick.hour = hour;
    });

    return dayBars;
}

代码核心核心方法是遍历tick的时候,循环中每次迭代生成一个currentBar,然后根据日夜盘的转换去决定是否将currentBar写入最后结果。使用lastTick记录了上一个tick的日期和时间。算法考虑了日盘和夜盘未开盘的情况。

源码资源

下面连接是本文中测试目的的数据验证代码的资源连接:

A交易前端根据内盘商品期货Tick数据合并日线Bar源代码

前文中网盘链接里面的测试数据下载后导入MySQL5.7以后版本结合此代码直接可验证tick数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值