vue 使用TradingView制作K线图(模仿火币)详解

前言:
项目需求要写K线图,echarts图表实现不了里面的功能,所以要用到TradingView,这个真心让人头疼,百度的东西也很少,花了很久才实现出来了。
效果图:
k线是实时更新的,可以正常根据数据跳动
vue 使用TradingView制作K线图(模仿火币)详解

index.html 引入js和css文件

我这里是vue-cli4 ,vue-cli4和vue-cli2文件夹不一样
如果你是vue-cli4 里面的css和js 记得放在public文件夹里面 ,vue-cli2的话就是正常使用
vue 使用TradingView制作K线图(模仿火币)详解

创建容器

先建立一个div,用来保存k线图 (样式根据自己项目修改)

<template>
  <div class="tvContent">
    <div id="tv_chart_container"></div>
  </div>
</template>

<style scoped>
.tvContent {
  width: 100%;
  height: 800px;
  background: #061f46;
  border: 1px solid #4390e4;
  border-radius: 5px;
  padding: 20px;
  box-sizing: border-box;
}
#tv_chart_container {
  width: 100%;
  height: 100%;
}
</style>

父组件截图

引入组件 并将需要的参数传给子组件

vue 使用TradingView制作K线图(模仿火币)详解

剩下的都是子组件

props希望父组件穿的值

props: {
    symbol: {
      type: String,
      required: true,
      default: "BTC/USDT", //币种类型
    },
    currency_id: {
      type: Number,
      required: true,  //id
    },
  },

监听父组件传过来的类型

watch: {
    listenState: function (a, b) {
      //监听交易对
      if (a != b && b != "") {
        this.widget.setSymbol(
          a,
          localStorage.getItem("tim"),
          function onReadyCallback() {}
        ); //切换币种
      }
    },
    symbol: {
      handler(n) {
      //  这个地方是socket的用法 不会的请看socket那篇文章
        this.$socket.on("reconnect");
      },
      deep: true, // 深度监听父组件传过来对象变化
      immediate: true,
    },
  },

data() 里面的数据

      widget: null, //创建的实例
      symbolInfo: null, //信息
      priceScale: 100000, //价格精度
      aa: null,
      time1: null,  

创建 createWidget() 函数

createWidget() {
      let _this = this;
      this.$nextTick(function () {
        let widget = (_this.widget = new TradingView.widget({
          symbol: _this.symbol,
          interval: 1,
          debug: false,
          fullscreen: false,
          autosize: true,
          container_id: "tv_chart_container",
          datafeed: _this.createFeed(),
          library_path: "static/tradeview/charting_library/",
          custom_css_url: "bundles/new.css",
          locale: "zh",
          width: "100%",
          allow_symbol_change: true,
          drawings_access: {
            type: "black",
            // tools: [{name: "Regression Trend"}]//todo: moje
            tools: [
              { name: "Trend Line", grayed: true },
              { name: "Trend Angle", grayed: true },
            ], //todo: bb
          },
          disabled_features: [
            //  禁用的功能
            "left_toolbar", //左侧菜单栏
            "widget_logo", //底部logo
            "header_saveload", //头部保存功能
            "compare_symbol",
            "display_market_status",
            "go_to_date",
            "header_chart_type", //头部类型 下面有自定义
            "header_compare",
            "header_interval_dialog_button",
            "header_resolutions",
            "header_screenshot", //图片上传
            "header_symbol_search",
            "header_undo_redo",
            // "legend_context_menu", //显示币种名称
            "show_hide_button_in_legend",
            "show_interval_dialog_on_key_press",
            // "symbol_info",
            "timeframes_toolbar", //底部时间信息
            "use_localstorage_for_settings",
            "volume_force_overlay",
          ],
          enabled_features: [
            //  启用的功能(备注:disable_resolution_rebuild 功能用于控制当时间范围为1个月时,日期刻度是否都是每个月1号
            "dont_show_boolean_study_arguments",
            "use_localstorage_for_settings",
            "remove_library_container_border",
            "save_chart_properties_to_local_storage",
            "side_toolbar_in_fullscreen_mode",
            "hide_last_na_study_output",
            "constraint_dialogs_movement",
            "legend_widget",
          ],
          charts_storage_url: "http://saveload.tradingview.com",
          charts_storage_api_version: "1.1",
          toolbar_bg: "transparent",
          timezone: "Asia/Shanghai",
          studies_overrides: {
            "volume.precision": "1000",
          },
          overrides: _this.overrides(),
        }));

        widget.MAStudies = [];
        widget.selectedIntervalButton = null;
        widget.onChartReady(function () {
          let buttonArr = [
            {
              value: "5",
              period: "5min",
              text: "5分",
              chartType: 1,
              type: "5min",
            },
            {
              value: "15",
              period: "15min",
              text: "15分",
              chartType: 1,
              type: "15min",
            },
            {
              value: "30",
              period: "30min",
              text: "30分",
              chartType: 1,
              type: "30min",
            },
            {
              value: "60",
              period: "60min",
              text: "60分",
              chartType: 1,
              type: "60min",
            },
            {
              value: "1D",
              period: "1D",
              text: "1天",
              chartType: 1,
              type: "1day",
            },
            {
              value: "1W",
              period: "1W",
              text: "1周",
              chartType: 1,
              type: "1week",
            },
            {
              value: "1M",
              period: "1mon",
              text: "1月",
              chartType: 1,
              type: "1mon",
            },
          ];
          let btn = {};
          let nowTime = "";

          buttonArr.forEach((v, i) => {
            let button = widget.createButton();
            button.attr("title", v.text).addClass("my2").text(v.text);
            if (v.text === "5分") {
              button.css({
                color: "#5786d2",
                "border-bottom": "1px solid #5786d2",
              });
              localStorage.setItem("tim", "5");
            }
            btn = button.on("click", function (e) {
              $(this).parents(".left").children().find(".my2").removeAttr("style");
              handleClick(e, v.value, v.type);
              button.css({
                color: "#5786d2",
                "border-bottom": "1px solid #5786d2",
              });
              _this.$store.commit("upType", v.type);
              widget.chart().setChartType(v.chartType); //改变K线类型
            });
          });
          let handleClick = (e, value, type) => {
            _this.setSymbol = function (symbol, value) {
              gh.chart().setSymbol(symbol, value);
            };
            widget.chart().setResolution(value, function onReadyCallback() {}); //改变分辨率
            $(e.target)
              .addClass("mydate")
              .closest("div.space-single")
              .siblings("div.space-single")
              .find("div.button")
              .removeClass("mydate");
          };
        });

        _this.widget = widget;
      });
    },

createWidget() 函数 里面的注意事项

1. 如果你是vue-cli4 的话 library_pathcustom_css_url 的路径必须要和我的一样,如果用相对路径会报错,vue-cli2的话就是正常使用

vue 使用TradingView制作K线图(模仿火币)详解
2. 禁用的功能和启用的功能的选择 代码中用到的是一部分功能 根据我的备注 可自行删减 没有的可去文档中寻找

TradingView 中文开发文档 (功能集地址)
https://aitrade.ga/books/tradingview/book/Featuresets.htm

vue 使用TradingView制作K线图(模仿火币)详解

3. 自定义导航栏周期

vue 使用TradingView制作K线图(模仿火币)详解
设置默认时间样式以及切换
vue 使用TradingView制作K线图(模仿火币)详解
创建 createFeed() 函数

createFeed() {
      let this_vue = this;
      let Datafeed = {};

      Datafeed.DataPulseUpdater = function (datafeed, updateFrequency) {
        this._datafeed = datafeed;
        this._subscribers = {};

        this._requestsPending = 0;
        var that = this;

        var update = function () {
          if (that._requestsPending > 0) {
            return;
          }

          for (var listenerGUID in that._subscribers) {
            var subscriptionRecord = that._subscribers[listenerGUID];
            var resolution = subscriptionRecord.resolution;

            var datesRangeRight = parseInt(new Date().valueOf() / 1000);

            //	BEWARE: please note we really need 2 bars, not the only last one
            //	see the explanation below. `10` is the `large enough` value to work around holidays
            var datesRangeLeft =
              datesRangeRight - that.periodLengthSeconds(resolution, 10);

            that._requestsPending++;

            (function (_subscriptionRecord) {
              // eslint-disable-line
              that._datafeed.getBars(
                _subscriptionRecord.symbolInfo,
                resolution,
                datesRangeLeft,
                datesRangeRight,
                function (bars) {
                  that._requestsPending--;

                  //	means the subscription was cancelled while waiting for data
                  if (!that._subscribers.hasOwnProperty(listenerGUID)) {
                    return;
                  }

                  if (bars.length === 0) {
                    return;
                  }

                  var lastBar = bars[bars.length - 1];
                  if (
                    !isNaN(_subscriptionRecord.lastBarTime) &&
                    lastBar.time < _subscriptionRecord.lastBarTime
                  ) {
                    return;
                  }

                  var subscribers = _subscriptionRecord.listeners;

                  //	BEWARE: this one isn't working when first update comes and this update makes a new bar. In this case
                  //	_subscriptionRecord.lastBarTime = NaN
                  var isNewBar =
                    !isNaN(_subscriptionRecord.lastBarTime) &&
                    lastBar.time > _subscriptionRecord.lastBarTime;

                  //	Pulse updating may miss some trades data (ie, if pulse period = 10 secods and new bar is started 5 seconds later after the last update, the
                  //	old bar's last 5 seconds trades will be lost). Thus, at fist we should broadcast old bar updates when it's ready.
                  if (isNewBar) {
                    if (bars.length < 2) {
                      throw new Error(
                        "Not enough bars in history for proper pulse update. Need at least 2."
                      );
                    }

                    var previousBar = bars[bars.length - 2];
                    for (var i = 0; i < subscribers.length; ++i) {
                      subscribers[i](previousBar);
                    }
                  }

                  _subscriptionRecord.lastBarTime = lastBar.time;

                  for (var i = 0; i < subscribers.length; ++i) {
                    subscribers[i](lastBar);
                  }
                },

                //	on error
                function () {
                  that._requestsPending--;
                }
              );
            })(subscriptionRecord);
          }
        };

        if (typeof updateFrequency != "undefined" && updateFrequency > 0) {
          setInterval(update, updateFrequency);
        }
      };

      Datafeed.DataPulseUpdater.prototype.periodLengthSeconds = function (
        resolution,
        requiredPeriodsCount
      ) {
        var daysCount = 0;
        if (resolution === "D") {
          daysCount = requiredPeriodsCount;
        } else if (resolution === "M") {
          daysCount = 31 * requiredPeriodsCount;
        } else if (resolution === "W") {
          daysCount = 7 * requiredPeriodsCount;
        } else {
          daysCount = (requiredPeriodsCount * resolution) / (24 * 60);
        }

        return daysCount * 24 * 60 * 60;
      };

      Datafeed.DataPulseUpdater.prototype.subscribeDataListener = function (
        symbolInfo,
        resolution,
        newDataCallback,
        listenerGUID
      ) {
        this._datafeed._logMessage("Subscribing " + listenerGUID);

        if (!this._subscribers.hasOwnProperty(listenerGUID)) {
          this._subscribers[listenerGUID] = {
            symbolInfo: symbolInfo,
            resolution: resolution,
            lastBarTime: NaN,
            listeners: [],
          };
        }

        this._subscribers[listenerGUID].listeners.push(newDataCallback);
      };

      Datafeed.DataPulseUpdater.prototype.unsubscribeDataListener = function (
        listenerGUID
      ) {
        this._datafeed._logMessage("Unsubscribing " + listenerGUID);
        delete this._subscribers[listenerGUID];
      };

      Datafeed.Container = function (updateFrequency) {
        this._configuration = {
          supports_search: false,
          supports_group_request: false,
          supported_resolutions: ["5", "15", "30", "60", "1D", "1W", "1M"],
          supports_marks: true,
          supports_timescale_marks: true,
          exchanges: ["gh"],
        };

        this._barsPulseUpdater = new Datafeed.DataPulseUpdater(
          this,
          updateFrequency || 10 * 1000
        );
        // this._quotesPulseUpdater = new Datafeed.QuotesPulseUpdater(this);

        this._enableLogging = true;
        this._callbacks = {};

        this._initializationFinished = true;
        this._fireEvent("initialized");
        this._fireEvent("configuration_ready");
      };

      Datafeed.Container.prototype._fireEvent = function (event, argument) {
        if (this._callbacks.hasOwnProperty(event)) {
          var callbacksChain = this._callbacks[event];
          for (var i = 0; i < callbacksChain.length; ++i) {
            callbacksChain[i](argument);
          }

          this._callbacks[event] = [];
        }
      };

      Datafeed.Container.prototype._logMessage = function (message) {
        if (this._enableLogging) {
          var now = new Date();
        }
      };

      Datafeed.Container.prototype.on = function (event, callback) {
        if (!this._callbacks.hasOwnProperty(event)) {
          this._callbacks[event] = [];
        }

        this._callbacks[event].push(callback);
        return this;
      };

      Datafeed.Container.prototype.onReady = function (callback) {
        let that = this;
        if (that._configuration) {
          setTimeout(function () {
            callback(that._configuration);
          }, 0);
        } else {
          this.on("configuration_ready", function () {
            callback(that._configuration);
          });
        }
      };

      Datafeed.Container.prototype.resolveSymbol = function (
        symbolName,
        onSymbolResolvedCallback,
        onResolveErrorCallback
      ) {
        this._logMessage("GOWNO :: resolve symbol " + symbolName);
        Promise.resolve().then(() => {
          onSymbolResolvedCallback({
            name: this_vue.symbol,
            timezone: "Asia/Shanghai",
            pricescale: this_vue.priceScale,
            minmov: 1, //minmov(最小波动), pricescale(价格精度), minmove2, fractional(分数)
            minmov2: 0, //这是一个神奇的数字来格式化复杂情况下的价格。
            ticker: this_vue.symbol,
            description: "",
            type: "bitcoin",
            volume_precision: 8,
            // "exchange-traded": "sdt",
            // "exchange-listed": "sdt",
            //现在,这两个字段都为某个交易所的略称。将被显示在图表的图例中,以表示此商品。目前此字段不用于其他目的。
            has_intraday: true,
            has_weekly_and_monthly: true,
            has_no_volume: false, //布尔表示商品是否拥有成交量数据。
            session: "24x7",
            supported_resolutions: ["5", "15", "30", "60", "1D", "1W", "1M"],
          });
        });
      };

      //初始化数据
      Datafeed.Container.prototype.getBars = async function (
        symbolInfo,
        resolution,
        rangeStartDate,
        rangeEndDate,
        onHistoryCallback,
        one rrorCallback
      ) {
        if (
          resolution.indexOf("D") == -1 &&
          resolution.indexOf("W") == -1 &&
          resolution.indexOf("M") == -1
        ) {
          resolution = resolution + "min";
        } else if (resolution.indexOf("W") != -1 || resolution.indexOf("M") != -1) {
          resolution = resolution;
        }
        //this_vue.newTimeshar  我请求历史数据的封装方法 换成自己的
        const res = await this_vue.newTimeshar({
          from: rangeStartDate,
          to: rangeEndDate,
          symbol: symbolInfo.name,
          period: resolution,
          currency_id: this_vue.currency_id,
        });

        if (res.code == 1 && res.data.length > 0) {
          this_vue.$store.commit("upSma1", res.data[res.data.length - 2].sma1);
          this_vue.$store.commit("upSma2", res.data[res.data.length - 2].sma2);
          //我是实时传送数据到后台 如果用不到自己删除
          this_vue.time1 = setInterval(function () {
            this_vue.$socket.emit("sub", this_vue.emitData); //触发socket连接
          }, 1000);
          //清楚计时器
          this_vue.$once("hook:beforeDestroy", () => {
            clearInterval(this_vue.time1);
          });
          res.data.forEach((item, i) => {
            item.open = Number(item.open);
            item.close = Number(item.close);
            item.high = Number(item.high);
            item.low = Number(item.low);
          });
          onHistoryCallback(res.data, { noData: false });
          onHistoryCallback([], { noData: true });
        }
        if (!res.data || res.code == -1) {
          onHistoryCallback([], { noData: true });
        }
        if (res.data && res.data.length == 0) {
          onHistoryCallback([], { noData: true });
        }
      };
      //实时数据
      Datafeed.Container.prototype.subscribeBars = function (
        symbolInfo,
        resolution,
        onRealtimeCallback,
        listenerGUID,
        onResetCacheNeededCallback
      ) {
        this_vue.connect(onRealtimeCallback);

        //this._barsPulseUpdater.subscribeDataListener(symbolInfo, resolution, onRealtimeCallback, listenerGUID, onResetCacheNeededCallback);
      };

      Datafeed.Container.prototype.unsubscribeBars = function (listenerGUID) {
        this._barsPulseUpdater.unsubscribeDataListener(listenerGUID);
      };

      return new Datafeed.Container();
    },

createFeed() 函数 里面的注意事项

1. 获取历史数据

this_vue.newTimeshar()是我封装的获取数据的接口 换成你自己的就可
vue 使用TradingView制作K线图(模仿火币)详解
vue 使用TradingView制作K线图(模仿火币)详解
2. 实时更新数据
this_vue.connect()实时回调vue 使用TradingView制作K线图(模仿火币)详解

创建 overrides() 函数

overrides() {
      let style = {
        up: "#12b886", //升
        down: "#fa5252", //降
        bg: "#061f46",  //背景
        grid: "rgba(122, 152, 247, .2)",
        cross: "#fff", //十字线
        border: "rgba(122, 152, 247, .2)",
        text: "rgba(122, 152, 247, .6)", //文字
        areatop: "rgba(122, 152, 247, .2)",
        areadown: "rgba(122, 152, 247, .2)",
        line: "rgba(122, 152, 247, .2)",
      };
      return {
        volumePaneSize: "medium", //large, medium, small, tiny
        "paneProperties.topMargin": "20",
        "scalesProperties.lineColor": style.text,
        "scalesProperties.textColor": style.text,
        "paneProperties.background": style.bg, //改变背景色的重要代码
        "paneProperties.vertGridProperties.color": style.grid,
        "paneProperties.horzGridProperties.color": style.grid,
        "paneProperties.crossHairProperties.color": style.cross,
        "paneProperties.crossHairProperties.lineType": 2,
        "paneProperties.legendProperties.showLegend": true,
        "paneProperties.legendProperties.showStudyArguments": true,
        "paneProperties.legendProperties.showStudyTitles": true,
        "paneProperties.legendProperties.showStudyValues": true,
        "paneProperties.legendProperties.showSeriesTitle": true,
        "paneProperties.legendProperties.showSeriesOHLC": true,
        "mainSeriesProperties.candleStyle.upColor": style.up,
        "mainSeriesProperties.candleStyle.downColor": style.down,
        "mainSeriesProperties.candleStyle.drawWick": true,
        "mainSeriesProperties.candleStyle.drawBorder": true,
        "mainSeriesProperties.candleStyle.borderColor": style.border,
        "mainSeriesProperties.candleStyle.borderUpColor": style.up,
        "mainSeriesProperties.candleStyle.borderDownColor": style.down,
        "mainSeriesProperties.candleStyle.wickUpColor": style.up,
        "mainSeriesProperties.candleStyle.wickDownColor": style.down,
        "mainSeriesProperties.candleStyle.barColorsOnPrevClose": false,
        "mainSeriesProperties.hollowCandleStyle.upColor": style.up,
        "mainSeriesProperties.hollowCandleStyle.downColor": style.down,

        "mainSeriesProperties.hollowCandleStyle.drawWick": true,
        "mainSeriesProperties.hollowCandleStyle.drawBorder": true,
        "mainSeriesProperties.hollowCandleStyle.borderColor": style.border,
        "mainSeriesProperties.hollowCandleStyle.borderUpColor": style.up,
        "mainSeriesProperties.hollowCandleStyle.borderDownColor": style.down,
        "mainSeriesProperties.hollowCandleStyle.wickColor": style.line,
        "mainSeriesProperties.haStyle.upColor": style.up,
        "mainSeriesProperties.haStyle.downColor": style.down,
        "mainSeriesProperties.haStyle.drawWick": true,
        "mainSeriesProperties.haStyle.drawBorder": true,
        "mainSeriesProperties.haStyle.borderColor": style.border,
        "mainSeriesProperties.haStyle.borderUpColor": style.up,
        "mainSeriesProperties.haStyle.borderDownColor": style.down,
        "mainSeriesProperties.haStyle.wickColor": style.border,
        "mainSeriesProperties.haStyle.barColorsOnPrevClose": false,
        "mainSeriesProperties.barStyle.upColor": style.up,
        "mainSeriesProperties.barStyle.downColor": style.down,
        "mainSeriesProperties.barStyle.barColorsOnPrevClose": false,
        "mainSeriesProperties.barStyle.dontDrawOpen": false,
        "mainSeriesProperties.lineStyle.color": style.border,
        "mainSeriesProperties.lineStyle.linewidth": 2,
        "mainSeriesProperties.lineStyle.styleType": 2,
        "mainSeriesProperties.lineStyle.linestyle": 2,
        "mainSeriesProperties.lineStyle.priceSource": "close",
        "mainSeriesProperties.areaStyle.color1": style.areatop,
        "mainSeriesProperties.areaStyle.color2": style.areadown,
        "mainSeriesProperties.areaStyle.linecolor": style.border,
        "mainSeriesProperties.areaStyle.linewidth": 2,
        "mainSeriesProperties.areaStyle.linestyle": 2,
        "mainSeriesProperties.areaStyle.priceSource": "close",
      };
    },

overrides() 函数 里面的注意事项

颜色根据自己的项目进行修改 (其他样式参考中文文档)

TradingView 中文开发文档 (形状与覆盖地址)
https://aitrade.ga/books/tradingview/book/Shapes-and-Overrides.html

vue 使用TradingView制作K线图(模仿火币)详解

tv全部完整代码:
我里面用的是socket.io连接的后台,进行的实时对接数据,
如果你是用的其他方法,直接把socket.io里面的东西替换就行。
如果你对socket.io感兴趣,请看链接: https://blog.csdn.net//article/details/106191646.

<template>
  <div class="tvContent">
    <div id="tv_chart_container"></div>
  </div>
</template>

<style scoped>
.tvContent {
  width: 100%;
  height: 800px;
  background: #061f46;
  border: 1px solid #4390e4;
  border-radius: 5px;
  padding: 20px;
  box-sizing: border-box;
}
#tv_chart_container {
  width: 100%;
  height: 100%;
}
</style>
<script>
import { mapActions } from "vuex";
export default {
  name: "tv",
  props: {
    symbol: {
      type: String,
      required: true,
      default: "BTC/USDT",
    },
    currency_id: {
      type: Number,
      required: true,
    },
  },
  data() {
    return {
      widget: null, //创建的实例
      symbolInfo: null, //信息
      priceScale: 100000, //价格精度
      aa: null,
      time1: null, 
    };
  },
  // 我使用的socket
  sockets: {
    //查看socket是否渲染成功
    connect() {},
    disconnect() {
      console.log("断开链接");
    }, //检测socket断开链接
    reconnect() {
      console.log("重新链接");
      // this.$socket.emit("connection", 1);
      this.$socket.open();
    },
    //客户端接收后台传输的socket事件
    kline: function (msg) {
      let obj = {};
      var that = this;
      let type = that.$store.state.type;

      if (that.symbol == msg.symbol && msg.period == type) {
        obj.open = Number(msg.open);
        obj.low = Number(msg.low);
        obj.high = Number(msg.high);
        obj.close = Number(msg.close);
        obj.volume = Number(msg.volume);
        obj.time = Number(msg.time);
        if (that.$store.state.nextId != msg.id) {
          that.$store.commit("upId", msg.id);
          that.$store.commit("upSma1", msg.sma1);
          that.$store.commit("upSma2", msg.sma2);
        }

        this.aa && this.aa(obj);
      }
    },
  },
  computed: {
    listenState() {
      //监听交易对
      return this.symbol;
    },
    kind() {
      return this.symbol.split("/")[0].toLowerCase();
    },
    // 我传给后台的东西 自己进行删减
    emitData() {
      return (
        this.kind +
        "-" +
        this.$store.state.type +
        "-" +
        this.$store.state.sma1 +
        "-" +
        this.$store.state.sma2
      );
    },
  },
  watch: {
    listenState: function (a, b) {
      //监听交易对
      if (a != b && b != "") {
        this.widget.setSymbol(
          a,
          localStorage.getItem("tim"),
          function onReadyCallback() {}
        ); //切换币种
      }
    },
    symbol: {
      handler(n) {
        this.$socket.on("reconnect");
      },
      deep: true, // 深度监听父组件传过来对象变化
      immediate: true,
    },
  },
  mounted() {
    this.createWidget();
  },
  destroyed() {
    this.removeWidget();
  },

  methods: {
    ...mapActions({
      newTimeshar: "home/newTimeshar",
    }),
    connect(real) {
      this.aa = real;
      let that = this;
      //实时数据进行回调链接socket
      this.$socket.on("connect", function () {
        this.$socket.on("kline", (msg) => {
          let obj = {};
          let type = that.$store.state.type;
          if (that.symbol == msg.symbol && msg.period == type) {
            obj.open = Number(msg.open);
            obj.low = Number(msg.low);
            obj.high = Number(msg.high);
            obj.close = Number(msg.close);
            obj.volume = Number(msg.volume);
            obj.time = Number(msg.time);
            if (that.$store.state.nextId != msg.id) {
              that.$store.commit("upId", msg.id);
              that.$store.commit("upSma1", msg.sma1);
              that.$store.commit("upSma2", msg.sma2);
            }

            real(obj);
          }
        });
      });
    },
    createWidget() {
      let _this = this;
      this.$nextTick(function () {
        let widget = (_this.widget = new TradingView.widget({
          symbol: _this.symbol,
          interval: 1,
          debug: false,
          fullscreen: false,
          autosize: true,
          container_id: "tv_chart_container",
          // datafeed: new Datafeeds.UDFCompatibleDatafeed(
          //   "http://demo_feed.tradingview.com"
          // ),
          datafeed: _this.createFeed(),
          library_path: "static/tradeview/charting_library/",
          custom_css_url: "bundles/new.css",
          locale: "zh",
          width: "100%",
          allow_symbol_change: true,
          drawings_access: {
            type: "black",
            // tools: [{name: "Regression Trend"}]//todo: moje
            tools: [
              { name: "Trend Line", grayed: true },
              { name: "Trend Angle", grayed: true },
            ], //todo: bb
          },
          disabled_features: [
            //  禁用的功能
            // "left_toolbar", //左侧菜单栏
            "widget_logo", //底部logo
            "header_saveload", //头部保存功能
            "compare_symbol",
            "display_market_status",
            "go_to_date",
            "header_chart_type", //头部类型 下面有自定义
            "header_compare",
            "header_interval_dialog_button",
            "header_resolutions",
            "header_screenshot", //图片上传
            "header_symbol_search",
            "header_undo_redo",
            // "legend_context_menu", //显示币种名称
            "show_hide_button_in_legend",
            "show_interval_dialog_on_key_press",
            // "symbol_info",
            "timeframes_toolbar", //底部时间信息
            "use_localstorage_for_settings",
            "volume_force_overlay",
          ],
          enabled_features: [
            //  启用的功能(备注:disable_resolution_rebuild 功能用于控制当时间范围为1个月时,日期刻度是否都是每个月1号
            "dont_show_boolean_study_arguments",
            "use_localstorage_for_settings",
            "remove_library_container_border",
            "save_chart_properties_to_local_storage",
            "side_toolbar_in_fullscreen_mode",
            "hide_last_na_study_output",
            "constraint_dialogs_movement",
            "legend_widget",
          ],
          charts_storage_url: "http://saveload.tradingview.com",
          charts_storage_api_version: "1.1",
          toolbar_bg: "transparent",
          timezone: "Asia/Shanghai",
          studies_overrides: {
            "volume.precision": "1000",
          },
          overrides: _this.overrides(),
        }));

        widget.MAStudies = [];
        widget.selectedIntervalButton = null;
        widget.onChartReady(function () {
          let buttonArr = [
            {
              value: "1",
              period: "1min",
              text: "1分",
              chartType: 1,
              type: "1min",
            },
            {
              value: "5",
              period: "5min",
              text: "5分",
              chartType: 1,
              type: "5min",
            },
            {
              value: "30",
              period: "30min",
              text: "30分",
              chartType: 1,
              type: "30min",
            },
            {
              value: "60",
              period: "60min",
              text: "60分",
              chartType: 1,
              type: "60min",
            },
            {
              value: "1D",
              period: "1D",
              text: "1天",
              chartType: 1,
              type: "1day",
            },
            {
              value: "1W",
              period: "1W",
              text: "1周",
              chartType: 1,
              type: "1week",
            },
            {
              value: "1M",
              period: "1mon",
              text: "1月",
              chartType: 1,
              type: "1mon",
            },
          ];
          let btn = {};
          let nowTime = "";

          buttonArr.forEach((v, i) => {
            let button = widget.createButton();
            button.attr("title", v.text).addClass("my2").text(v.text);
            if (v.text === "5分") {
              button.css({
                color: "#5786d2",
                "border-bottom": "1px solid #5786d2",
              });
              localStorage.setItem("tim", "5");
            }
            btn = button.on("click", function (e) {
              $(this).parents(".left").children().find(".my2").removeAttr("style");
              handleClick(e, v.value, v.type);
              button.css({
                color: "#5786d2",
                "border-bottom": "1px solid #5786d2",
              });
              _this.$store.commit("upType", v.type);
              widget.chart().setChartType(v.chartType); //改变K线类型
            });
          });
          let handleClick = (e, value, type) => {
            _this.setSymbol = function (symbol, value) {
              gh.chart().setSymbol(symbol, value);
            };
            widget.chart().setResolution(value, function onReadyCallback() {}); //改变分辨率
            $(e.target)
              .addClass("mydate")
              .closest("div.space-single")
              .siblings("div.space-single")
              .find("div.button")
              .removeClass("mydate");
          };
        });

        _this.widget = widget;
      });
    },
    createFeed() {
      let this_vue = this;
      let Datafeed = {};

      Datafeed.DataPulseUpdater = function (datafeed, updateFrequency) {
        this._datafeed = datafeed;
        this._subscribers = {};

        this._requestsPending = 0;
        var that = this;

        var update = function () {
          if (that._requestsPending > 0) {
            return;
          }

          for (var listenerGUID in that._subscribers) {
            var subscriptionRecord = that._subscribers[listenerGUID];
            var resolution = subscriptionRecord.resolution;

            var datesRangeRight = parseInt(new Date().valueOf() / 1000);

            //	BEWARE: please note we really need 2 bars, not the only last one
            //	see the explanation below. `10` is the `large enough` value to work around holidays
            var datesRangeLeft =
              datesRangeRight - that.periodLengthSeconds(resolution, 10);

            that._requestsPending++;

            (function (_subscriptionRecord) {
              // eslint-disable-line
              that._datafeed.getBars(
                _subscriptionRecord.symbolInfo,
                resolution,
                datesRangeLeft,
                datesRangeRight,
                function (bars) {
                  that._requestsPending--;

                  //	means the subscription was cancelled while waiting for data
                  if (!that._subscribers.hasOwnProperty(listenerGUID)) {
                    return;
                  }

                  if (bars.length === 0) {
                    return;
                  }

                  var lastBar = bars[bars.length - 1];
                  if (
                    !isNaN(_subscriptionRecord.lastBarTime) &&
                    lastBar.time < _subscriptionRecord.lastBarTime
                  ) {
                    return;
                  }

                  var subscribers = _subscriptionRecord.listeners;

                  //	BEWARE: this one isn't working when first update comes and this update makes a new bar. In this case
                  //	_subscriptionRecord.lastBarTime = NaN
                  var isNewBar =
                    !isNaN(_subscriptionRecord.lastBarTime) &&
                    lastBar.time > _subscriptionRecord.lastBarTime;

                  //	Pulse updating may miss some trades data (ie, if pulse period = 10 secods and new bar is started 5 seconds later after the last update, the
                  //	old bar's last 5 seconds trades will be lost). Thus, at fist we should broadcast old bar updates when it's ready.
                  if (isNewBar) {
                    if (bars.length < 2) {
                      throw new Error(
                        "Not enough bars in history for proper pulse update. Need at least 2."
                      );
                    }

                    var previousBar = bars[bars.length - 2];
                    for (var i = 0; i < subscribers.length; ++i) {
                      subscribers[i](previousBar);
                    }
                  }

                  _subscriptionRecord.lastBarTime = lastBar.time;

                  for (var i = 0; i < subscribers.length; ++i) {
                    subscribers[i](lastBar);
                  }
                },

                //	on error
                function () {
                  that._requestsPending--;
                }
              );
            })(subscriptionRecord);
          }
        };

        if (typeof updateFrequency != "undefined" && updateFrequency > 0) {
          setInterval(update, updateFrequency);
        }
      };

      Datafeed.DataPulseUpdater.prototype.periodLengthSeconds = function (
        resolution,
        requiredPeriodsCount
      ) {
        var daysCount = 0;
        if (resolution === "D") {
          daysCount = requiredPeriodsCount;
        } else if (resolution === "M") {
          daysCount = 31 * requiredPeriodsCount;
        } else if (resolution === "W") {
          daysCount = 7 * requiredPeriodsCount;
        } else {
          daysCount = (requiredPeriodsCount * resolution) / (24 * 60);
        }

        return daysCount * 24 * 60 * 60;
      };

      Datafeed.DataPulseUpdater.prototype.subscribeDataListener = function (
        symbolInfo,
        resolution,
        newDataCallback,
        listenerGUID
      ) {
        this._datafeed._logMessage("Subscribing " + listenerGUID);

        if (!this._subscribers.hasOwnProperty(listenerGUID)) {
          this._subscribers[listenerGUID] = {
            symbolInfo: symbolInfo,
            resolution: resolution,
            lastBarTime: NaN,
            listeners: [],
          };
        }

        this._subscribers[listenerGUID].listeners.push(newDataCallback);
      };

      Datafeed.DataPulseUpdater.prototype.unsubscribeDataListener = function (
        listenerGUID
      ) {
        this._datafeed._logMessage("Unsubscribing " + listenerGUID);
        delete this._subscribers[listenerGUID];
      };

      Datafeed.Container = function (updateFrequency) {
        this._configuration = {
          supports_search: false,
          supports_group_request: false,
          supported_resolutions: ["5", "15", "30", "60", "1D", "1W", "1M"],
          supports_marks: true,
          supports_timescale_marks: true,
          exchanges: ["gh"],
        };

        this._barsPulseUpdater = new Datafeed.DataPulseUpdater(
          this,
          updateFrequency || 10 * 1000
        );
        // this._quotesPulseUpdater = new Datafeed.QuotesPulseUpdater(this);

        this._enableLogging = true;
        this._callbacks = {};

        this._initializationFinished = true;
        this._fireEvent("initialized");
        this._fireEvent("configuration_ready");
      };

      Datafeed.Container.prototype._fireEvent = function (event, argument) {
        if (this._callbacks.hasOwnProperty(event)) {
          var callbacksChain = this._callbacks[event];
          for (var i = 0; i < callbacksChain.length; ++i) {
            callbacksChain[i](argument);
          }

          this._callbacks[event] = [];
        }
      };

      Datafeed.Container.prototype._logMessage = function (message) {
        if (this._enableLogging) {
          var now = new Date();
        }
      };

      Datafeed.Container.prototype.on = function (event, callback) {
        if (!this._callbacks.hasOwnProperty(event)) {
          this._callbacks[event] = [];
        }

        this._callbacks[event].push(callback);
        return this;
      };

      Datafeed.Container.prototype.onReady = function (callback) {
        let that = this;
        if (that._configuration) {
          setTimeout(function () {
            callback(that._configuration);
          }, 0);
        } else {
          this.on("configuration_ready", function () {
            callback(that._configuration);
          });
        }
      };

      Datafeed.Container.prototype.resolveSymbol = function (
        symbolName,
        onSymbolResolvedCallback,
        onResolveErrorCallback
      ) {
        this._logMessage("GOWNO :: resolve symbol " + symbolName);
        Promise.resolve().then(() => {
          onSymbolResolvedCallback({
            name: this_vue.symbol,
            timezone: "Asia/Shanghai",
            pricescale: this_vue.priceScale,
            minmov: 1, //minmov(最小波动), pricescale(价格精度), minmove2, fractional(分数)
            minmov2: 0, //这是一个神奇的数字来格式化复杂情况下的价格。
            ticker: this_vue.symbol,
            description: "",
            type: "bitcoin",
            volume_precision: 8,
            // "exchange-traded": "sdt",
            // "exchange-listed": "sdt",
            //现在,这两个字段都为某个交易所的略称。将被显示在图表的图例中,以表示此商品。目前此字段不用于其他目的。
            has_intraday: true,
            has_weekly_and_monthly: true,
            has_no_volume: false, //布尔表示商品是否拥有成交量数据。
            session: "24x7",
            supported_resolutions: ["5", "15", "30", "60", "1D", "1W", "1M"],
          });
        });
      };

      //初始化数据
      Datafeed.Container.prototype.getBars = async function (
        symbolInfo,
        resolution,
        rangeStartDate,
        rangeEndDate,
        onHistoryCallback,
        one rrorCallback
      ) {
        if (
          resolution.indexOf("D") == -1 &&
          resolution.indexOf("W") == -1 &&
          resolution.indexOf("M") == -1
        ) {
          resolution = resolution + "min";
        } else if (resolution.indexOf("W") != -1 || resolution.indexOf("M") != -1) {
          resolution = resolution;
        }

        //this_vue.newTimeshar  我请求历史数据的封装方法 换成自己的
        const res = await this_vue.newTimeshar({
          from: rangeStartDate,
          to: rangeEndDate,
          symbol: symbolInfo.name,
          period: resolution,
          currency_id: this_vue.currency_id,
        });

        if (res.code == 1 && res.data.length > 0) {
          this_vue.$store.commit("upSma1", res.data[res.data.length - 2].sma1);
          this_vue.$store.commit("upSma2", res.data[res.data.length - 2].sma2);
          //我是实时传送数据到后台 如果用不到自己删除
          this_vue.time1 = setInterval(function () {
            this_vue.$socket.emit("sub", this_vue.emitData); //触发socket连接
          }, 1000);
          //清楚计时器
          this_vue.$once("hook:beforeDestroy", () => {
            clearInterval(this_vue.time1);
          });
          res.data.forEach((item, i) => {
            item.open = Number(item.open);
            item.close = Number(item.close);
            item.high = Number(item.high);
            item.low = Number(item.low);
          });
          onHistoryCallback(res.data, { noData: false });
          onHistoryCallback([], { noData: true });
        }
        if (!res.data || res.code == -1) {
          onHistoryCallback([], { noData: true });
        }
        if (res.data && res.data.length == 0) {
          onHistoryCallback([], { noData: true });
        }
      };
      //实时数据
      Datafeed.Container.prototype.subscribeBars = function (
        symbolInfo,
        resolution,
        onRealtimeCallback,
        listenerGUID,
        onResetCacheNeededCallback
      ) {
        this_vue.connect(onRealtimeCallback);

        //this._barsPulseUpdater.subscribeDataListener(symbolInfo, resolution, onRealtimeCallback, listenerGUID, onResetCacheNeededCallback);
      };

      Datafeed.Container.prototype.unsubscribeBars = function (listenerGUID) {
        this._barsPulseUpdater.unsubscribeDataListener(listenerGUID);
      };

      return new Datafeed.Container();
    },

    updateData(data) {
      if (data) {
        this.$emit("real-time", data);
      }
    },

    updateWidget(item) {
      this.symbolInfo = {
        name: item,
        ticker: item,
        description: "",
        session: "24x7",
        supported_resolutions: ["5", "15", "30", "60", "1D", "1W", "1M"],
        has_intraday: true,
        has_daily: true,
        has_weekly_and_monthly: true,
        timezone: "UTC",
      };

      this.removeWidget();
      this.createWidget();
    },
    removeWidget() {
      if (this.widget) {
        this.widget.remove();
        this.widget = null;
      }
    },
    overrides() {
      let style = {
        up: "#12b886", //升
        down: "#fa5252", //降
        bg: "#061f46",  //背景
        grid: "rgba(122, 152, 247, .2)",
        cross: "#fff", //十字线
        border: "rgba(122, 152, 247, .2)",
        text: "rgba(122, 152, 247, .6)", //文字
        areatop: "rgba(122, 152, 247, .2)",
        areadown: "rgba(122, 152, 247, .2)",
        line: "rgba(122, 152, 247, .2)",
      };
      return {
        volumePaneSize: "medium", //large, medium, small, tiny
        "paneProperties.topMargin": "20",
        "scalesProperties.lineColor": style.text,
        "scalesProperties.textColor": style.text,
        "paneProperties.background": style.bg, //改变背景色的重要代码
        "paneProperties.vertGridProperties.color": style.grid,
        "paneProperties.horzGridProperties.color": style.grid,
        "paneProperties.crossHairProperties.color": style.cross,
        "paneProperties.crossHairProperties.lineType": 2,
        "paneProperties.legendProperties.showLegend": true,
        "paneProperties.legendProperties.showStudyArguments": true,
        "paneProperties.legendProperties.showStudyTitles": true,
        "paneProperties.legendProperties.showStudyValues": true,
        "paneProperties.legendProperties.showSeriesTitle": true,
        "paneProperties.legendProperties.showSeriesOHLC": true,
        "mainSeriesProperties.candleStyle.upColor": style.up,
        "mainSeriesProperties.candleStyle.downColor": style.down,
        "mainSeriesProperties.candleStyle.drawWick": true,
        "mainSeriesProperties.candleStyle.drawBorder": true,
        "mainSeriesProperties.candleStyle.borderColor": style.border,
        "mainSeriesProperties.candleStyle.borderUpColor": style.up,
        "mainSeriesProperties.candleStyle.borderDownColor": style.down,
        "mainSeriesProperties.candleStyle.wickUpColor": style.up,
        "mainSeriesProperties.candleStyle.wickDownColor": style.down,
        "mainSeriesProperties.candleStyle.barColorsOnPrevClose": false,
        "mainSeriesProperties.hollowCandleStyle.upColor": style.up,
        "mainSeriesProperties.hollowCandleStyle.downColor": style.down,

        "mainSeriesProperties.hollowCandleStyle.drawWick": true,
        "mainSeriesProperties.hollowCandleStyle.drawBorder": true,
        "mainSeriesProperties.hollowCandleStyle.borderColor": style.border,
        "mainSeriesProperties.hollowCandleStyle.borderUpColor": style.up,
        "mainSeriesProperties.hollowCandleStyle.borderDownColor": style.down,
        "mainSeriesProperties.hollowCandleStyle.wickColor": style.line,
        "mainSeriesProperties.haStyle.upColor": style.up,
        "mainSeriesProperties.haStyle.downColor": style.down,
        "mainSeriesProperties.haStyle.drawWick": true,
        "mainSeriesProperties.haStyle.drawBorder": true,
        "mainSeriesProperties.haStyle.borderColor": style.border,
        "mainSeriesProperties.haStyle.borderUpColor": style.up,
        "mainSeriesProperties.haStyle.borderDownColor": style.down,
        "mainSeriesProperties.haStyle.wickColor": style.border,
        "mainSeriesProperties.haStyle.barColorsOnPrevClose": false,
        "mainSeriesProperties.barStyle.upColor": style.up,
        "mainSeriesProperties.barStyle.downColor": style.down,
        "mainSeriesProperties.barStyle.barColorsOnPrevClose": false,
        "mainSeriesProperties.barStyle.dontDrawOpen": false,
        "mainSeriesProperties.lineStyle.color": style.border,
        "mainSeriesProperties.lineStyle.linewidth": 2,
        "mainSeriesProperties.lineStyle.styleType": 2,
        "mainSeriesProperties.lineStyle.linestyle": 2,
        "mainSeriesProperties.lineStyle.priceSource": "close",
        "mainSeriesProperties.areaStyle.color1": style.areatop,
        "mainSeriesProperties.areaStyle.color2": style.areadown,
        "mainSeriesProperties.areaStyle.linecolor": style.border,
        "mainSeriesProperties.areaStyle.linewidth": 2,
        "mainSeriesProperties.areaStyle.linestyle": 2,
        "mainSeriesProperties.areaStyle.priceSource": "close",
      };
    },
  },
};
</script>

TradingView 拓展

TradingView设置均线和隐藏均线: https://blog.csdn.net//article/details/118582663.

vue TradingView为k线做标记: https://blog.csdn.net//article/details/118584281.

上一篇:萌新小白第一章——PyCharm创建第一个代码项目(PyCharm如何编辑代码)


下一篇:java中Object类讲解(二)