金融类最强图表工具TradingView

特斯比特 2021-11-19 10:11:37

这段时间刚好做币圈交易所运用到了现在最火的金融类图表库 – TradingView ,就是强大基本上现在的火币网币安网等大型交易所都在使用。

简介

带有开放API的可下载图表库。这是一个独立的解决方案可以将其下载托管在自己的服务器上连接自己的数据在自己的网站/应用程序免费使用。

适用于手机和桌面应用程序、 门户网站、博客和新闻网站。 当您想要完全控制图表并希望显示您所控制的数据时此为最佳选择。
在这里插入图片描述

使用教程

本教程使用的是nodejs 提供的接口所有的数据都本地mock出来的这样大家可以更加方便理解数据使用的问题。

步骤

1.注册TradingView账号然后申请TradingView的图表库申请地址https://cn.tradingview.com/how-it-works/注意必须是以公司名义申请不允许个人名义申请如果以个人名义申请或者你所在行业经过中国区经销商了解后不需要用到tradingview将无法给你提供github的开源代码下载。源码结构如下图
在这里插入图片描述
/charting_library 包含全部的图表库文件。
/charting_library/charting_library.min.js 包含外部图表库widget接口。此文件不应该被修改。
/charting_library/charting_library.min.d.ts 包含TypeScript定义的widget接口
/charting_library/datafeed-api.d.ts 包含TypeScript定义的datafeed接口。
/charting_library/datafeeds/udf/ 包含UDF-compatible 的datafeed包装类用于实现JS API通过UDF传输数据给图表库。例子中的datafeed包装器类实现了脉冲实时仿真数据。您可以自由编辑此文件。
/charting_library/static 文件夹中存储图表库内部资源不适用于其他目的。
/index.html 为使用Charting Library widget 的html例子。
/test.html 为不同的图表库自定义功能使用的示例。
/mobile*.html 也是Widget自定义的示例。
然后把整个项目clone 下来到本地。

2.安装nodejs 

3.安装koa (Koa – 基于 Node.js 平台的下一代 web 开发框架)

 $ npm i koa

4.安装koa-generator (Koa 脚手架)

$ npm i -g koa-generator

5.使用koa-generator 快速创建项目

$ koa2 TrandingPiewProject && cd TrandingPiewProject && npm install

koa-generator创建的项目已经集成了nodemon,所以大家可以直接使用nodemon启动项目

$ nodemon run start

打开浏览器 http://localhost:3001

就可以看到nodejs项目已经启动了

6.在nodejs创建3个接口

地址1http://localhost:3001/api/config (存放trandingview的 datafeed配置数据)

地址2http://localhost:3001/api/symbols (trandingview的 商品解析数据)

地址3http://localhost:3001/api/history (trandingview的 K线数据)

7.nodejs项目文件修改如下

app.js 修改部分黄色标出

const Koa = require('koa')
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
// 引入koa2-cors "允许跨域"
const cors = require('koa2-cors')

const index = require('./routes/index')
const api = require('./routes/api')

const app = new Koa()
app.use(cors())
// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(index.routes(), index.allowedMethods())
app.use(api.routes(), api.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app

在这里插入图片描述
把原来的routes下面的users.js改成api.js

贴上我的api.js

api.js 修改部分黄色标出

const router = require('koa-router')()

router.prefix('/api')

// 产生随机数的方法
function random(lower, upper) {
    return Math.floor(Math.random() * (upper - lower)) + lower;
}

// 获取两数中一数的方法
function rd(n, m) {
    var c = m - n + 1;
    return Math.floor(Math.random() * c + n);
}

// config接口
router.get('/config', async (ctx, next) => {
    ctx.body = { 
        "supports_search": true, //是否支持搜索
        "supports_group_request": false, //是否支持搜索或品种解析
        "supported_resolutions": ["30", "60", "240", "D"], //中的值D代表天dayW代表周weekM代表月2D是两天3W是三周6M是6个月表示支持显示的哪几种图日线图、2日线
        "supports_marks": false, 
        "supports_timescale_marks": false, 
    }
})
// symbols接口
router.get('/symbols', async (ctx, next) => {
    ctx.body = {
        "name": "CDCC/ETH", //品种名称
        "session": "24x7", //开盘时间
        "has_intraday": true, //显示符号是否具有历史盘中数据
        "timezone": "Asia/Shanghai", //时区
        "data_status": "delayed_streaming", 
        "supported_resolutions": ["5", "10", "15", "30", "60", "120", "240", "D", "W"],
        "intraday_multipliers": ["5", "60", "D"],
        "minmov": 20, //用于格式化用途
        "pricescale": 100000000, //数据显示的小数位数例如100显示0.01
        "type": "bitcoin" //类型
    }
})

// history接口
router.get('/history', async (ctx, next) => {
    let resolution = ctx.query.resolution // 商品名称或者代码
    let from = new Date(ctx.query.from * 1000) //获取数据的开始时间戳
    let to = new Date(ctx.query.to * 1000) //获取数据的结束时间戳


if (resolution == '1' || resolution == '5' || resolution == '10' || resolution == '15' || resolution == '30') {
    from.setMilliseconds(0)
    to.setMilliseconds(0)
    from.setSeconds(0)
    to.setSeconds(0)
} else if (resolution == '60') {
    from.setMilliseconds(0)
    to.setMilliseconds(0)
    from.setSeconds(0)
    to.setSeconds(0)
    from.setMinutes(0)
    to.setMinutes(0)
} else if (resolution == 'D') {
    from.setMilliseconds(0)
    to.setMilliseconds(0)
    from.setSeconds(0)
    to.setSeconds(0)
    from.setMinutes(0)
    to.setMinutes(0)
    from.setHours(0)
    to.setHours(0)
}
let text = from
const time = Math.floor((to.getTime() - from.getTime()) / 60 / 1000)
let num
if (resolution == 'D') {
    num = time / (60 * 24)
} else {
    num = time / resolution
}
let o = []
let c = []
let h = []
let l = []
let v = []
let t = []
let newnum = 3000;
for (var i &#61; 0; i < num; i&#43;&#43;) {
    t.push(text.getTime() / 1000)
    if (resolution &#61;&#61; 'D') {
        text.setMinutes(text.getMinutes() &#43; Number(24 * 60))
    } else {
        text.setMinutes(text.getMinutes() &#43; Number(resolution))
    }
    if (rd(1, 2) &#61;&#61; 1) {
        newnum &#43;&#61; random(1, 10)
        o.push(newnum)
        let h1 &#61; 0;
        let l1 &#61; 0;
        if (rd(1, 2) &#61;&#61; 1) {
            h1 &#61; newnum &#43; random(1, 10)
            h.push(h1)
            l1 &#61; h1 - random(1, 10)
            l.push(l1)
            c.push(rd(l1, h1))
        } else {
            h1 &#61; newnum - random(1, 10)
            h.push(h1)
            l1 &#61; h1 - random(1, 10)
            l.push(l1)
            c.push(rd(l1, h1))
        }
    } else {
        newnum -&#61; random(1, 10)
        if (newnum < 0) {
            newnum &#61; 0
        }
        o.push(newnum)
        if (rd(1, 2) &#61;&#61; 1) {
            h.push(newnum &#43; random(1, 10))
            l.push(newnum &#43; random(1, 10))
            c.push(newnum &#43; random(1, 10))
        } else {
            if ((newnum - random(1, 10)) < 0) {
                newnum &#61; 0
                h.push(0)
            } else {
                h.push(newnum - random(1, 10))
            }
            if ((newnum - random(1, 10)) < 0) {
                newnum &#61; 0
                l.push(0)
            } else {
                l.push(newnum - random(1, 10))
            }
            if ((newnum - random(1, 10)) < 0) {
                newnum &#61; 0
                c.push(0)
            } else {
                c.push(newnum - random(1, 10))
            }
        }
    }

    v.push(random(1, 1000))
}
ctx.body &#61; { 
    t: t, //表示时间将t的数值补000后就代表毫秒数比如js的(new Date).getTime()
    o: o, //表示open开盘价
    c: c, //表示close收盘价
    h: h, //表示high最高价
    l: l, //表示low最低价
    v: v, //表示volume成交量
    s: &#34;ok&#34; //表示状态返回数据是否成功ok表示成功
}
})

module.exports &#61; router

修改完之后其实上面创建的3个就可以访问了。mock 数据也完成了。

8.把github上clone下来的项目启动在自己本地的服务器我个人用了代理服务器nginxnginx 具体的安装流程请大家自己去搜索以及为什么用nginx这个就是因为接口跨域问题大家自己私下了解下就可以了我在这里就不多做解释了。

接着把克隆下来的charting_library文件移动到nginx的html文件下就可以了。
在这里插入图片描述

这里我贴出我的nginx.conf 文件

注意修改下里面charting_library文件夹根目录下的 test.html 文件把名字改成 index3.html。因为nginx跟恶心对test这个词有bug。

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] &#34;$request&#34; '
    #                  '$status $body_bytes_sent &#34;$http_referer&#34; '
    #                  '&#34;$http_user_agent&#34; &#34;$http_x_forwarded_for&#34;';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       8083;
        server_name  localhost;
        location / {
            root  ./html/charting_library;
            index  index3.html index3.htm;
        }
    }

    server {
       listen       8084;
       server_name  localhost;

       location / {
            proxy_pass http://localhost:8083;
            proxy_redirect default;
       }
       location /apis {
            rewrite  ^/apis/(.*)$ /$1 break;
            proxy_pass   http://localhost:3001;
       }

    }


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

好了这个时候项目就已经启动了。

9.然后修改Tradingview的配置就可以连接自己的nodesj mock出来的数据了。

tradingview 目录结构
在这里插入图片描述

然后只需要修改里里面的index.html(其实就是原来的test.html,只是我这里把它改成index3.html)

<!DOCTYPE html>
<html lang&#61;&#34;en&#34;>

<head>
    <meta http-equiv&#61;&#34;Content-Type&#34; content&#61;&#34;text/html; charset&#61;utf-8&#34; />
    <title>TradingView Charting Library demo -- testing mess</title>
    <!-- Fix for iOS Safari zooming bug -->
    <meta name&#61;&#34;viewport&#34; content&#61;&#34;width&#61;device-width,initial-scale&#61;1.0,maximum-scale&#61;1.0,minimum-scale&#61;1.0&#34;>
    <meta http-equiv&#61;&#34;X-UA-Compatible&#34; content&#61;&#34;IE&#61;Edge&#34;>
    <script src&#61;&#34;http://code.jquery.com/jquery-3.3.1.js&#34; integrity&#61;&#34;sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60&#61;&#34; crossorigin&#61;&#34;anonymous&#34;></script>
    <script type&#61;&#34;text/javascript&#34; src&#61;&#34;charting_library/charting_library.min.js&#34;></script>
    <script type&#61;&#34;text/javascript&#34; src&#61;&#34;datafeeds/udf/dist/polyfills.js&#34;></script>
    <script type&#61;&#34;text/javascript&#34; src&#61;&#34;datafeeds/udf/dist/bundle.js&#34;></script>
    <script type&#61;&#34;text/javascript&#34;>
    function getParameterByName(name) {
        name &#61; name.replace(/[\[]/, &#34;\\[&#34;).replace(/[\]]/, &#34;\\]&#34;);
        var regex &#61; new RegExp(&#34;[\\?&]&#34; &#43; name &#43; &#34;&#61;([^&#]*)&#34;),
            results &#61; regex.exec(location.search);
        return results &#61;&#61;&#61; null ? &#34;&#34; : decodeURIComponent(results[1].replace(/\&#43;/g, &#34; &#34;));
    }
TradingView.onready(function() {
    var widget &#61; window.tvWidget &#61; new TradingView.widget({
        debug: false,
        // 是否全屏
        fullscreen: true,
        // 初始商品
        symbol: 'CDCC/ETH',
        // 周期
        interval: 60,
        // 时区
        timezone: &#34;Asia/Shanghai&#34;,
        // id属性为指定要包含widget的DOM元素id
        container_id: &#34;tv_chart_container&#34;,
        // 语言
        locale: &#34;zh&#34;,
        // static文件夹的路径
        library_path: &#34;charting_library/&#34;,
        // JavaScript对象的实现接口 JS API 以反馈图表及数据
        datafeed: new Datafeeds.UDFCompatibleDatafeed(&#34;http://localhost:3001/api&#34;),
        // 自动调节大小
        autosize: true,
        // 主题颜色darklight
        theme: 'dark',
        time_frames: false,
        style: &#34;1&#34;,     
        overrides: {
            &#34;timeScale.rightOffset&#34;: 1,
            &#34;timeScale.barSpacing&#34;: 12.709329141645004,
            &#34;headerToolbarIndicators.backgroundColor&#34;: &#34;#dc1a5a&#34;,
            &#34;headerToolbarIndicators.backgroundColor&#34;: &#34;#dc1a5a&#34;,
            &#34;mainSeriesProperties.candleStyle.borderColor&#34;: &#34;#212c3f&#34;,
            &#34;mainSeriesProperties.candleStyle.borderDownColor&#34;: &#34;#dc1a5a&#34;,
            &#34;mainSeriesProperties.candleStyle.borderUpColor&#34;: &#34;#0fbb89&#34;,
            &#34;mainSeriesProperties.candleStyle.downColor&#34;: &#34;#dc1a5a&#34;,
            &#34;mainSeriesProperties.candleStyle.upColor&#34;: &#34;#0fbb89&#34;,
            &#34;mainSeriesProperties.candleStyle.wickDownColor&#34;: &#34;#dc1a5a&#34;,
            &#34;mainSeriesProperties.candleStyle.wickUpColor&#34;: &#34;#0fbb89&#34;,
            &#34;mainSeriesProperties.hollowCandleStyle.borderColor&#34;: &#34;#212c3f&#34;,
            &#34;mainSeriesProperties.hollowCandleStyle.borderDownColor&#34;: &#34;#dc1a5a&#34;,
            &#34;mainSeriesProperties.hollowCandleStyle.borderUpColor&#34;: &#34;#0fbb89&#34;,
            &#34;mainSeriesProperties.hollowCandleStyle.downColor&#34;: &#34;#dc1a5a&#34;,
            &#34;mainSeriesProperties.hollowCandleStyle.upColor&#34;: &#34;#0fbb89&#34;,
            &#34;mainSeriesProperties.showCountdown&#34;: false,
            &#34;mainSeriesProperties.style&#34;: 1,
            &#34;paneProperties.background&#34;: &#34;#131722&#34;,
            &#34;paneProperties.crossHairProperties.color&#34;: &#34;#999&#34;,
            &#34;paneProperties.horzGridProperties.color&#34;: &#34;#252c40&#34;,
            &#34;paneProperties.horzGridProperties.style&#34;: 2,
            &#34;paneProperties.legendProperties.showLegend&#34;: true,
            &#34;paneProperties.vertGridProperties.color&#34;: &#34;#131722&#34;,
            &#34;paneProperties.vertGridProperties.style&#34;: 2,
            &#34;symbolWatermarkProperties.color&#34;: &#34;#0e192b&#34;,
            &#34;volumePaneSize&#34;: &#34;small&#34;,
        },
        studies_overrides: {
            &#34;volume.volume.color.0&#34;: &#34;#0fbb89&#34;,
            &#34;volume.volume.color.1&#34;: &#34;#dc1a5a&#34;,
            &#34;volume.options.showStudyArguments&#34;: false,
            &#34;bollinger bands.median.color&#34;: &#34;#33FF88&#34;,
            &#34;bollinger bands.upper.linewidth&#34;: 0
        },
        disabled_features: [
            &#34;header_symbol_search&#34;,
            &#34;header_saveload&#34;,
            &#34;header_screenshot&#34;,
            &#34;header_chart_type&#34;,
            &#34;header_compare&#34;,
            &#34;header_undo_redo&#34;,
            &#34;timeframes_toolbar&#34;,
            &#34;volume_force_overlay&#34;,
            &#34;header_resolutions&#34;,
            &#34;display_market_status&#34;,
            &#34;timezone_menu&#34;,
            &#34;countdown&#34;,
            &#34;symbol_info&#34;
        ],
        enabled_features: [
            &#34;legend_context_menu&#34;
        ],

        // 指标模板
        charts_storage_api_version: '1.1',
        // 定制加载进度条
        loading_screen: { backgroundColor: &#34;#000000&#34; },
        'scalesProperties.fontSize': 14, // 设置坐标轴字体大小

    });

    // 参考线、 按钮、 时间切换
    var thats &#61; widget;
    var buttons &#61; [
        { title: '5分', resolution: '5', chartType: 1 },
        { title: '10分', resolution: '10', chartType: 1 },
        { title: '15分', resolution: '15', chartType: 1 },
        { title: '30分', resolution: '30', chartType: 1 },
        { title: '1小时', resolution: '60', chartType: 1 },
        { title: '2小时', resolution: '120', chartType: 1 },
        { title: '4小时', resolution: '240', chartType: 1 },
        { title: '1天', resolution: 'D', chartType: 1 },
        { title: '1周', resolution: 'W', chartType: 1 },
    ];

    var studies &#61; [];

    function createButton(buttons) {
        for (var i &#61; 0; i < buttons.length; i&#43;&#43;) {
            thats.createButton()
                .attr('title', buttons[i].title)
                .attr('data-interval', buttons[i].resolution)
                .addClass('btn-m')
                .text(buttons[i].title)
                .on('click', function(e) {
                    tvWidget.setSymbol(&#34;CDCC/ETH&#34;,$(this).attr('data-interval'),function(){});
                })
        }
    }

    function createStudy() {
        var id &#61; widget.chart().createStudy('Moving Average', false, false, [5], null, { 'Plot.color': 'rgb(150, 95, 196)' });
        studies.push(id);
        id &#61; widget.chart().createStudy('Moving Average', false, false, [10], null, { 'Plot.color': 'rgb(116,149,187)' });
        studies.push(id);
        id &#61; widget.chart().createStudy('Moving Average', false, false, [20], null, { &#34;plot.color&#34;: &#34;rgb(58,113,74)&#34; });
        studies.push(id);
        id &#61; widget.chart().createStudy('Moving Average', false, false, [30], null, { &#34;plot.color&#34;: &#34;rgb(118,32,99)&#34; });
        studies.push(id);
    }

    widget.onChartReady(function() {
        //设置均线种类 均线样式
        createStudy();
        //生成时间按钮
        createButton(buttons);
    });
});
</script>

上面是我的index3.html文件

修改完之后大家打开http://localhost:8083/ 就可以看到自己的TradingView 项目连接着自己的nodejs服务器mock出来的数据运行了是不是很有成就感啦

在这里插入图片描述

这个给大家介绍个好东西图表功能展示

地址 https://tradingview.gitee.io/featuresets/

这里我就是介绍了简单的ajax请求方法的要WS的后面我再慢慢更新哈。

以上只是一部分后续会继续更新完善。

期待期待期待

声明:本文内容不代表斑马投诉网站观点,内容仅供参考,不构成投资建议。投资有风险,选择需谨慎! 如涉及内容、版权等问题,请联系我们,我们会在第一时间作出调整!

相关文章