binbot-charts 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/components/TVChartContainer.js +45 -24
- package/dist/components/datafeed.js +2 -13
- package/dist/{index.js → components/index.js} +1 -1
- package/dist/datafeeds/README.md +3 -0
- package/dist/datafeeds/udf/README.md +46 -0
- package/dist/datafeeds/udf/dist/bundle.js +1 -0
- package/dist/datafeeds/udf/lib/data-pulse-provider.js +104 -0
- package/dist/datafeeds/udf/lib/helpers.js +20 -0
- package/dist/datafeeds/udf/lib/history-provider.js +73 -0
- package/dist/datafeeds/udf/lib/iquotes-provider.js +1 -0
- package/dist/datafeeds/udf/lib/quotes-provider.js +25 -0
- package/dist/datafeeds/udf/lib/quotes-pulse-provider.js +44 -0
- package/dist/datafeeds/udf/lib/requester.js +28 -0
- package/dist/datafeeds/udf/lib/symbols-storage.js +180 -0
- package/dist/datafeeds/udf/lib/udf-compatible-datafeed-base.js +252 -0
- package/dist/datafeeds/udf/lib/udf-compatible-datafeed.js +10 -0
- package/dist/datafeeds/udf/package.json +17 -0
- package/dist/datafeeds/udf/rollup.config.js +25 -0
- package/dist/datafeeds/udf/src/data-pulse-provider.ts +152 -0
- package/dist/datafeeds/udf/src/helpers.ts +38 -0
- package/dist/datafeeds/udf/src/history-provider.ts +134 -0
- package/dist/datafeeds/udf/src/iquotes-provider.ts +14 -0
- package/dist/datafeeds/udf/src/quotes-provider.ts +37 -0
- package/dist/datafeeds/udf/src/quotes-pulse-provider.ts +85 -0
- package/dist/datafeeds/udf/src/requester.ts +39 -0
- package/dist/datafeeds/udf/src/symbols-storage.ts +298 -0
- package/dist/datafeeds/udf/src/udf-compatible-datafeed-base.ts +369 -0
- package/dist/datafeeds/udf/src/udf-compatible-datafeed.ts +11 -0
- package/dist/datafeeds/udf/tsconfig.json +25 -0
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ Import it in your project as a React component
|
|
|
7
7
|
## How to start
|
|
8
8
|
|
|
9
9
|
1. Run `yarn install && yarn start`. It will build the project and open a default browser with the Charting Library.
|
|
10
|
+
2. `library_path` should be `node_modules/dist/charting_library`
|
|
11
|
+
3. Write a script to copy `charting_library` to `public/charting_library` during build. E.g. `cp -r node_modules/dist/charting_library/ src/public/charting_library`
|
|
10
12
|
|
|
11
13
|
## About This Project
|
|
12
14
|
|
|
@@ -7,53 +7,60 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
});
|
|
8
8
|
exports.default = void 0;
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var React = _interopRequireWildcard(require("react"));
|
|
11
11
|
|
|
12
12
|
var _charting_library = require("../charting_library");
|
|
13
13
|
|
|
14
|
-
var _datafeed = _interopRequireDefault(require("./datafeed"));
|
|
15
|
-
|
|
16
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
|
-
|
|
18
14
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
19
15
|
|
|
20
16
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
21
17
|
|
|
22
18
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
23
19
|
|
|
24
|
-
class TVChartContainer extends
|
|
20
|
+
class TVChartContainer extends React.PureComponent {
|
|
25
21
|
constructor(props) {
|
|
26
22
|
super(props);
|
|
27
23
|
|
|
28
24
|
_defineProperty(this, "tvWidget", null);
|
|
29
25
|
|
|
30
|
-
this.ref = /*#__PURE__*/
|
|
26
|
+
this.ref = /*#__PURE__*/React.createRef();
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
componentDidMount() {
|
|
34
30
|
const widgetOptions = {
|
|
35
|
-
symbol:
|
|
31
|
+
symbol: this.props.symbol,
|
|
36
32
|
// BEWARE: no trailing slash is expected in feed URL
|
|
37
|
-
datafeed:
|
|
38
|
-
|
|
33
|
+
datafeed: new window.Datafeeds.UDFCompatibleDatafeed(this.props.datafeedUrl),
|
|
34
|
+
interval: this.props.interval,
|
|
39
35
|
container: this.ref.current,
|
|
40
36
|
library_path: this.props.libraryPath,
|
|
41
|
-
locale:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
fullscreen:
|
|
49
|
-
autosize:
|
|
50
|
-
studies_overrides:
|
|
37
|
+
locale: 'en',
|
|
38
|
+
disabled_features: ['use_localstorage_for_settings'],
|
|
39
|
+
enabled_features: ['study_templates'],
|
|
40
|
+
charts_storage_url: this.props.chartsStorageUrl,
|
|
41
|
+
charts_storage_api_version: this.props.chartsStorageApiVersion,
|
|
42
|
+
client_id: this.props.clientId,
|
|
43
|
+
user_id: this.props.userId,
|
|
44
|
+
fullscreen: this.props.fullscreen,
|
|
45
|
+
autosize: this.props.autosize,
|
|
46
|
+
studies_overrides: this.props.studiesOverrides
|
|
51
47
|
};
|
|
52
48
|
const tvWidget = new _charting_library.widget(widgetOptions);
|
|
53
49
|
this.tvWidget = tvWidget;
|
|
54
50
|
tvWidget.onChartReady(() => {
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
tvWidget.headerReady().then(() => {
|
|
52
|
+
const button = tvWidget.createButton();
|
|
53
|
+
button.setAttribute('title', 'Click to show a notification popup');
|
|
54
|
+
button.classList.add('apply-common-tooltip');
|
|
55
|
+
button.addEventListener('click', () => tvWidget.showNoticeDialog({
|
|
56
|
+
title: 'Notification',
|
|
57
|
+
body: 'TradingView Charting Library API works correctly',
|
|
58
|
+
callback: () => {
|
|
59
|
+
console.log('Noticed!');
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
button.innerHTML = 'Check API';
|
|
63
|
+
});
|
|
57
64
|
});
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -65,7 +72,7 @@ class TVChartContainer extends _react.Component {
|
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
render() {
|
|
68
|
-
return /*#__PURE__*/
|
|
75
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
69
76
|
ref: this.ref,
|
|
70
77
|
style: {
|
|
71
78
|
height: "calc(100vh - 80px)"
|
|
@@ -75,4 +82,18 @@ class TVChartContainer extends _react.Component {
|
|
|
75
82
|
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
exports.default = TVChartContainer;
|
|
85
|
+
exports.default = TVChartContainer;
|
|
86
|
+
|
|
87
|
+
_defineProperty(TVChartContainer, "defaultProps", {
|
|
88
|
+
symbol: 'AAPL',
|
|
89
|
+
interval: 'D',
|
|
90
|
+
datafeedUrl: 'https://demo_feed.tradingview.com',
|
|
91
|
+
libraryPath: '/charting_library/',
|
|
92
|
+
chartsStorageUrl: 'https://saveload.tradingview.com',
|
|
93
|
+
chartsStorageApiVersion: '1.1',
|
|
94
|
+
clientId: 'tradingview.com',
|
|
95
|
+
userId: 'public_user_id',
|
|
96
|
+
fullscreen: false,
|
|
97
|
+
autosize: true,
|
|
98
|
+
studiesOverrides: {}
|
|
99
|
+
});
|
|
@@ -32,10 +32,8 @@ const configurationData = {
|
|
|
32
32
|
}],
|
|
33
33
|
symbols_types: [{
|
|
34
34
|
name: "crypto",
|
|
35
|
-
// `symbolType` argument for the `searchSymbols` method, if a user selects this symbol type
|
|
36
35
|
value: "crypto"
|
|
37
|
-
}
|
|
38
|
-
]
|
|
36
|
+
}]
|
|
39
37
|
};
|
|
40
38
|
|
|
41
39
|
async function getAllSymbols() {
|
|
@@ -65,6 +63,7 @@ class Datafeed {}
|
|
|
65
63
|
exports.default = Datafeed;
|
|
66
64
|
|
|
67
65
|
_defineProperty(Datafeed, "onReady", callback => {
|
|
66
|
+
debugger;
|
|
68
67
|
console.log("[onReady]: Method call");
|
|
69
68
|
setTimeout(() => callback(configurationData));
|
|
70
69
|
});
|
|
@@ -170,14 +169,4 @@ _defineProperty(Datafeed, "getBars", async (symbolInfo, resolution, periodParams
|
|
|
170
169
|
console.log("[getBars]: Get error", error);
|
|
171
170
|
onErrorCallback(error);
|
|
172
171
|
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
_defineProperty(Datafeed, "subscribeBars", (symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {
|
|
176
|
-
console.log("[subscribeBars]: Method call with subscribeUID:", subscribeUID);
|
|
177
|
-
(0, _streaming.subscribeOnStream)(symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback, lastBarsCache.get(symbolInfo.full_name));
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
_defineProperty(Datafeed, "unsubscribeBars", subscriberUID => {
|
|
181
|
-
console.log("[unsubscribeBars]: Method call with subscriberUID:", subscriberUID);
|
|
182
|
-
(0, _streaming.unsubscribeFromStream)(subscriberUID);
|
|
183
172
|
});
|
|
@@ -10,6 +10,6 @@ Object.defineProperty(exports, "TVChartContainer", {
|
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
var _TVChartContainer = _interopRequireDefault(require("./
|
|
13
|
+
var _TVChartContainer = _interopRequireDefault(require("./TVChartContainer"));
|
|
14
14
|
|
|
15
15
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# UDF Compatible Datafeed
|
|
2
|
+
|
|
3
|
+
This folder contains [UDF](https://github.com/tradingview/charting_library/wiki/UDF) datafeed adapter. It implements [JS API](https://github.com/tradingview/charting_library/wiki/JS%20API) and makes HTTP requests using [UDF](https://github.com/tradingview/charting_library/wiki/UDF) protocol.
|
|
4
|
+
|
|
5
|
+
You can use this datafeed adapter to plug your data if you implement [UDF](https://github.com/tradingview/charting_library/wiki/UDF) on your server. You can also scrutinize how it works before writing your own adapter.
|
|
6
|
+
|
|
7
|
+
This datafeed is implemented in [TypeScript](https://github.com/Microsoft/TypeScript/).
|
|
8
|
+
|
|
9
|
+
## Folders content
|
|
10
|
+
|
|
11
|
+
- `./src` folder contains the source code in TypeScript.
|
|
12
|
+
|
|
13
|
+
- `./lib` folder contains transpiled in es5 code. So, if you do not know how to use TypeScript - you can modify these files to change the result bundle later.
|
|
14
|
+
|
|
15
|
+
- `./dist` folder contains bundled JavaScript files which can be inlined into a page and used in the Widget Constructor.
|
|
16
|
+
|
|
17
|
+
## Build & bundle
|
|
18
|
+
|
|
19
|
+
Before building or bundling your code you need to run `npm install` to install dependencies.
|
|
20
|
+
|
|
21
|
+
`package.json` contains some handy scripts to build or generate the bundle:
|
|
22
|
+
|
|
23
|
+
- `npm run compile` to compile TypeScript source code into JavaScript files (output will be in `./lib` folder)
|
|
24
|
+
- `npm run bundle-js` to bundle multiple JavaScript files into one bundle (it also bundle polyfills)
|
|
25
|
+
- `npm run build` to compile and bundle (it is a combination of all above commands)
|
|
26
|
+
|
|
27
|
+
NOTE: if you want to minify the bundle code, you need to set `ENV` environment variable to a value different from `development`.
|
|
28
|
+
|
|
29
|
+
For example:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export ENV=prod
|
|
33
|
+
npm run bundle-js # or npm run build
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
or
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ENV=prod npm run bundle-js
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
or
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
ENV=prod npm run build
|
|
46
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports):"function"==typeof define&&define.amd?define(["exports"],s):s((e="undefined"!=typeof globalThis?globalThis:e||self).Datafeeds={})}(this,(function(e){"use strict";function s(e){return void 0===e?"":"string"==typeof e?e:e.message}class t{constructor(e,s){this._datafeedUrl=e,this._requester=s}getBars(e,t,r){const o={symbol:e.ticker||"",resolution:t,from:r.from,to:r.to};return void 0!==r.countBack&&(o.countback=r.countBack),void 0!==e.currency_code&&(o.currencyCode=e.currency_code),void 0!==e.unit_id&&(o.unitId=e.unit_id),new Promise(((e,t)=>{this._requester.sendRequest(this._datafeedUrl,"history",o).then((s=>{if("ok"!==s.s&&"no_data"!==s.s)return void t(s.errmsg);const r=[],o={noData:!1};if("no_data"===s.s)o.noData=!0,o.nextTime=s.nextTime;else{const e=void 0!==s.v,t=void 0!==s.o;for(let o=0;o<s.t.length;++o){const i={time:1e3*s.t[o],close:parseFloat(s.c[o]),open:parseFloat(s.c[o]),high:parseFloat(s.c[o]),low:parseFloat(s.c[o])};t&&(i.open=parseFloat(s.o[o]),i.high=parseFloat(s.h[o]),i.low=parseFloat(s.l[o])),e&&(i.volume=parseFloat(s.v[o])),r.push(i)}}e({bars:r,meta:o})})).catch((e=>{const r=s(e);console.warn(`HistoryProvider: getBars() failed, error=${r}`),t(r)}))}))}}class r{constructor(e,s){this._subscribers={},this._requestsPending=0,this._historyProvider=e,setInterval(this._updateData.bind(this),s)}subscribeBars(e,s,t,r){this._subscribers.hasOwnProperty(r)||(this._subscribers[r]={lastBarTime:null,listener:t,resolution:s,symbolInfo:e},e.name)}unsubscribeBars(e){delete this._subscribers[e]}_updateData(){if(!(this._requestsPending>0)){this._requestsPending=0;for(const e in this._subscribers)this._requestsPending+=1,this._updateDataForSubscriber(e).then((()=>{this._requestsPending-=1,this._requestsPending})).catch((e=>{this._requestsPending-=1,s(e),this._requestsPending}))}}_updateDataForSubscriber(e){const s=this._subscribers[e],t=parseInt((Date.now()/1e3).toString()),r=t-function(e,s){let t=0;t="D"===e||"1D"===e?s:"M"===e||"1M"===e?31*s:"W"===e||"1W"===e?7*s:s*parseInt(e)/1440;return 24*t*60*60}(s.resolution,10);return this._historyProvider.getBars(s.symbolInfo,s.resolution,{from:r,to:t,countBack:2,firstDataRequest:!1}).then((s=>{this._onSubscriberDataReceived(e,s)}))}_onSubscriberDataReceived(e,s){if(!this._subscribers.hasOwnProperty(e))return;const t=s.bars;if(0===t.length)return;const r=t[t.length-1],o=this._subscribers[e];if(null!==o.lastBarTime&&r.time<o.lastBarTime)return;if(null!==o.lastBarTime&&r.time>o.lastBarTime){if(t.length<2)throw new Error("Not enough bars in history for proper pulse update. Need at least 2.");const e=t[t.length-2];o.listener(e)}o.lastBarTime=r.time,o.listener(r)}}class o{constructor(e){this._subscribers={},this._requestsPending=0,this._quotesProvider=e,setInterval(this._updateQuotes.bind(this,1),1e4),setInterval(this._updateQuotes.bind(this,0),6e4)}subscribeQuotes(e,s,t,r){this._subscribers[r]={symbols:e,fastSymbols:s,listener:t}}unsubscribeQuotes(e){delete this._subscribers[e]}_updateQuotes(e){if(!(this._requestsPending>0))for(const t in this._subscribers){this._requestsPending++;const r=this._subscribers[t];this._quotesProvider.getQuotes(1===e?r.fastSymbols:r.symbols).then((e=>{this._requestsPending--,this._subscribers.hasOwnProperty(t)&&(r.listener(e),this._requestsPending)})).catch((e=>{this._requestsPending--,s(e),this._requestsPending}))}}}function i(e,s,t,r){const o=e[s];return!Array.isArray(o)||r&&!Array.isArray(o[0])?o:o[t]}function n(e,s,t){return e+(void 0!==s?"_%|#|%_"+s:"")+(void 0!==t?"_%|#|%_"+t:"")}class a{constructor(e,s,t){this._exchangesList=["NYSE","FOREX","AMEX"],this._symbolsInfo={},this._symbolsList=[],this._datafeedUrl=e,this._datafeedSupportedResolutions=s,this._requester=t,this._readyPromise=this._init(),this._readyPromise.catch((e=>{console.error(`SymbolsStorage: Cannot init, error=${e.toString()}`)}))}resolveSymbol(e,s,t){return this._readyPromise.then((()=>{const r=this._symbolsInfo[n(e,s,t)];return void 0===r?Promise.reject("invalid symbol"):Promise.resolve(r)}))}searchSymbols(e,s,t,r){return this._readyPromise.then((()=>{const o=[],i=0===e.length;e=e.toUpperCase();for(const r of this._symbolsList){const n=this._symbolsInfo[r];if(void 0===n)continue;if(t.length>0&&n.type!==t)continue;if(s&&s.length>0&&n.exchange!==s)continue;const a=n.name.toUpperCase().indexOf(e),u=n.description.toUpperCase().indexOf(e);if(i||a>=0||u>=0){if(!o.some((e=>e.symbolInfo===n))){const e=a>=0?a:8e3+u;o.push({symbolInfo:n,weight:e})}}}const n=o.sort(((e,s)=>e.weight-s.weight)).slice(0,r).map((e=>{const s=e.symbolInfo;return{symbol:s.name,full_name:s.full_name,description:s.description,exchange:s.exchange,params:[],type:s.type,ticker:s.name}}));return Promise.resolve(n)}))}_init(){const e=[],s={};for(const t of this._exchangesList)s[t]||(s[t]=!0,e.push(this._requestExchangeData(t)));return Promise.all(e).then((()=>{this._symbolsList.sort()}))}_requestExchangeData(e){return new Promise(((t,r)=>{this._requester.sendRequest(this._datafeedUrl,"symbol_info",{group:e}).then((s=>{try{this._onExchangeDataReceived(e,s)}catch(e){return void r(e)}t()})).catch((e=>{s(e),t()}))}))}_onExchangeDataReceived(e,s){let t=0;try{const e=s.symbol.length,r=void 0!==s.ticker;for(;t<e;++t){const e=s.symbol[t],o=i(s,"exchange-listed",t),a=i(s,"exchange-traded",t),c=a+":"+e,h=i(s,"currency-code",t),l=i(s,"unit-id",t),d=r?i(s,"ticker",t):e,_={ticker:d,name:e,base_name:[o+":"+e],full_name:c,listed_exchange:o,exchange:a,currency_code:h,original_currency_code:i(s,"original-currency-code",t),unit_id:l,original_unit_id:i(s,"original-unit-id",t),unit_conversion_types:i(s,"unit-conversion-types",t,!0),description:i(s,"description",t),has_intraday:u(i(s,"has-intraday",t),!1),has_no_volume:u(i(s,"has-no-volume",t),!1),minmov:i(s,"minmovement",t)||i(s,"minmov",t)||0,minmove2:i(s,"minmove2",t)||i(s,"minmov2",t),fractional:i(s,"fractional",t),pricescale:i(s,"pricescale",t),type:i(s,"type",t),session:i(s,"session-regular",t),session_holidays:i(s,"session-holidays",t),corrections:i(s,"corrections",t),timezone:i(s,"timezone",t),supported_resolutions:u(i(s,"supported-resolutions",t,!0),this._datafeedSupportedResolutions),has_daily:u(i(s,"has-daily",t),!0),intraday_multipliers:u(i(s,"intraday-multipliers",t,!0),["1","5","15","30","60"]),has_weekly_and_monthly:i(s,"has-weekly-and-monthly",t),has_empty_bars:i(s,"has-empty-bars",t),volume_precision:u(i(s,"volume-precision",t),0),format:"price"};this._symbolsInfo[d]=_,this._symbolsInfo[e]=_,this._symbolsInfo[c]=_,void 0===h&&void 0===l||(this._symbolsInfo[n(d,h,l)]=_,this._symbolsInfo[n(e,h,l)]=_,this._symbolsInfo[n(c,h,l)]=_),this._symbolsList.push(e)}}catch(r){throw new Error(`SymbolsStorage: API error when processing exchange ${e} symbol #${t} (${s.symbol[t]}): ${r.message}`)}}}function u(e,s){return void 0!==e?e:s}function c(e,s,t){const r=e[s];return Array.isArray(r)?r[t]:r}class h{constructor(e,s){this._datafeedUrl=e,this._requester=s}getQuotes(e){return new Promise(((t,r)=>{this._requester.sendRequest(this._datafeedUrl,"quotes",{symbols:e}).then((e=>{"ok"===e.s?t(e.d):r(e.errmsg)})).catch((e=>{const t=s(e);r(`network error: ${t}`)}))}))}}class l{constructor(e){e&&(this._headers=e)}sendRequest(e,s,t){if(void 0!==t){const e=Object.keys(t);0!==e.length&&(s+="?"),s+=e.map((e=>`${encodeURIComponent(e)}=${encodeURIComponent(t[e].toString())}`)).join("&")}const r={credentials:"same-origin"};return void 0!==this._headers&&(r.headers=this._headers),fetch(`${e}/${s}`,r).then((e=>e.text())).then((e=>JSON.parse(e)))}}e.UDFCompatibleDatafeed=class extends class{constructor(e,s,i,n=1e4){this._configuration={supports_search:!1,supports_group_request:!0,supported_resolutions:["1","5","15","30","60","1D","1W","1M"],supports_marks:!1,supports_timescale_marks:!1},this._symbolsStorage=null,this._datafeedURL=e,this._requester=i,this._historyProvider=new t(e,this._requester),this._quotesProvider=s,this._dataPulseProvider=new r(this._historyProvider,n),this._quotesPulseProvider=new o(this._quotesProvider),this._configurationReadyPromise=this._requestConfiguration().then((e=>{null===e&&(e={supports_search:!1,supports_group_request:!0,supported_resolutions:["1","5","15","30","60","1D","1W","1M"],supports_marks:!1,supports_timescale_marks:!1}),this._setupWithConfiguration(e)}))}onReady(e){this._configurationReadyPromise.then((()=>{e(this._configuration)}))}getQuotes(e,s,t){this._quotesProvider.getQuotes(e).then(s).catch(t)}subscribeQuotes(e,s,t,r){this._quotesPulseProvider.subscribeQuotes(e,s,t,r)}unsubscribeQuotes(e){this._quotesPulseProvider.unsubscribeQuotes(e)}getMarks(e,t,r,o,i){if(!this._configuration.supports_marks)return;const n={symbol:e.ticker||"",from:t,to:r,resolution:i};this._send("marks",n).then((e=>{if(!Array.isArray(e)){const s=[];for(let t=0;t<e.id.length;++t)s.push({id:c(e,"id",t),time:c(e,"time",t),color:c(e,"color",t),text:c(e,"text",t),label:c(e,"label",t),labelFontColor:c(e,"labelFontColor",t),minSize:c(e,"minSize",t)});e=s}o(e)})).catch((e=>{s(e),o([])}))}getTimescaleMarks(e,t,r,o,i){if(!this._configuration.supports_timescale_marks)return;const n={symbol:e.ticker||"",from:t,to:r,resolution:i};this._send("timescale_marks",n).then((e=>{if(!Array.isArray(e)){const s=[];for(let t=0;t<e.id.length;++t)s.push({id:c(e,"id",t),time:c(e,"time",t),color:c(e,"color",t),label:c(e,"label",t),tooltip:c(e,"tooltip",t)});e=s}o(e)})).catch((e=>{s(e),o([])}))}getServerTime(e){this._configuration.supports_time&&this._send("time").then((s=>{const t=parseInt(s);isNaN(t)||e(t)})).catch((e=>{s(e)}))}searchSymbols(e,t,r,o){if(this._configuration.supports_search){const i={limit:30,query:e.toUpperCase(),type:r,exchange:t};this._send("search",i).then((e=>{if(void 0!==e.s)return e.errmsg,void o([]);o(e)})).catch((e=>{s(e),o([])}))}else{if(null===this._symbolsStorage)throw new Error("UdfCompatibleDatafeed: inconsistent configuration (symbols storage)");this._symbolsStorage.searchSymbols(e,t,r,30).then(o).catch(o.bind(null,[]))}}resolveSymbol(e,t,r,o){const i=o&&o.currencyCode,n=o&&o.unitId;function a(e){t(e)}if(this._configuration.supports_group_request){if(null===this._symbolsStorage)throw new Error("UdfCompatibleDatafeed: inconsistent configuration (symbols storage)");this._symbolsStorage.resolveSymbol(e,i,n).then(a).catch(r)}else{const t={symbol:e};void 0!==i&&(t.currencyCode=i),void 0!==n&&(t.unitId=n),this._send("symbols",t).then((e=>{void 0!==e.s?r("unknown_symbol"):a(e)})).catch((e=>{s(e),r("unknown_symbol")}))}}getBars(e,s,t,r,o){this._historyProvider.getBars(e,s,t).then((e=>{r(e.bars,e.meta)})).catch(o)}subscribeBars(e,s,t,r,o){this._dataPulseProvider.subscribeBars(e,s,t,r)}unsubscribeBars(e){this._dataPulseProvider.unsubscribeBars(e)}_requestConfiguration(){return this._send("config").catch((e=>(s(e),null)))}_send(e,s){return this._requester.sendRequest(this._datafeedURL,e,s)}_setupWithConfiguration(e){if(this._configuration=e,void 0===e.exchanges&&(e.exchanges=[]),!e.supports_search&&!e.supports_group_request)throw new Error("Unsupported datafeed configuration. Must either support search, or support group request");!e.supports_group_request&&e.supports_search||(this._symbolsStorage=new a(this._datafeedURL,e.supported_resolutions||[],this._requester)),JSON.stringify(e)}}{constructor(e,s=1e4){const t=new l;super(e,new h(e,t),t,s)}},Object.defineProperty(e,"__esModule",{value:!0})}));
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { getErrorMessage, logMessage, } from './helpers';
|
|
2
|
+
export class DataPulseProvider {
|
|
3
|
+
constructor(historyProvider, updateFrequency) {
|
|
4
|
+
this._subscribers = {};
|
|
5
|
+
this._requestsPending = 0;
|
|
6
|
+
this._historyProvider = historyProvider;
|
|
7
|
+
setInterval(this._updateData.bind(this), updateFrequency);
|
|
8
|
+
}
|
|
9
|
+
subscribeBars(symbolInfo, resolution, newDataCallback, listenerGuid) {
|
|
10
|
+
if (this._subscribers.hasOwnProperty(listenerGuid)) {
|
|
11
|
+
logMessage(`DataPulseProvider: already has subscriber with id=${listenerGuid}`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
this._subscribers[listenerGuid] = {
|
|
15
|
+
lastBarTime: null,
|
|
16
|
+
listener: newDataCallback,
|
|
17
|
+
resolution: resolution,
|
|
18
|
+
symbolInfo: symbolInfo,
|
|
19
|
+
};
|
|
20
|
+
logMessage(`DataPulseProvider: subscribed for #${listenerGuid} - {${symbolInfo.name}, ${resolution}}`);
|
|
21
|
+
}
|
|
22
|
+
unsubscribeBars(listenerGuid) {
|
|
23
|
+
delete this._subscribers[listenerGuid];
|
|
24
|
+
logMessage(`DataPulseProvider: unsubscribed for #${listenerGuid}`);
|
|
25
|
+
}
|
|
26
|
+
_updateData() {
|
|
27
|
+
if (this._requestsPending > 0) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this._requestsPending = 0;
|
|
31
|
+
for (const listenerGuid in this._subscribers) { // tslint:disable-line:forin
|
|
32
|
+
this._requestsPending += 1;
|
|
33
|
+
this._updateDataForSubscriber(listenerGuid)
|
|
34
|
+
.then(() => {
|
|
35
|
+
this._requestsPending -= 1;
|
|
36
|
+
logMessage(`DataPulseProvider: data for #${listenerGuid} updated successfully, pending=${this._requestsPending}`);
|
|
37
|
+
})
|
|
38
|
+
.catch((reason) => {
|
|
39
|
+
this._requestsPending -= 1;
|
|
40
|
+
logMessage(`DataPulseProvider: data for #${listenerGuid} updated with error=${getErrorMessage(reason)}, pending=${this._requestsPending}`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
_updateDataForSubscriber(listenerGuid) {
|
|
45
|
+
const subscriptionRecord = this._subscribers[listenerGuid];
|
|
46
|
+
const rangeEndTime = parseInt((Date.now() / 1000).toString());
|
|
47
|
+
// BEWARE: please note we really need 2 bars, not the only last one
|
|
48
|
+
// see the explanation below. `10` is the `large enough` value to work around holidays
|
|
49
|
+
const rangeStartTime = rangeEndTime - periodLengthSeconds(subscriptionRecord.resolution, 10);
|
|
50
|
+
return this._historyProvider.getBars(subscriptionRecord.symbolInfo, subscriptionRecord.resolution, {
|
|
51
|
+
from: rangeStartTime,
|
|
52
|
+
to: rangeEndTime,
|
|
53
|
+
countBack: 2,
|
|
54
|
+
firstDataRequest: false,
|
|
55
|
+
})
|
|
56
|
+
.then((result) => {
|
|
57
|
+
this._onSubscriberDataReceived(listenerGuid, result);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
_onSubscriberDataReceived(listenerGuid, result) {
|
|
61
|
+
// means the subscription was cancelled while waiting for data
|
|
62
|
+
if (!this._subscribers.hasOwnProperty(listenerGuid)) {
|
|
63
|
+
logMessage(`DataPulseProvider: Data comes for already unsubscribed subscription #${listenerGuid}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const bars = result.bars;
|
|
67
|
+
if (bars.length === 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const lastBar = bars[bars.length - 1];
|
|
71
|
+
const subscriptionRecord = this._subscribers[listenerGuid];
|
|
72
|
+
if (subscriptionRecord.lastBarTime !== null && lastBar.time < subscriptionRecord.lastBarTime) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const isNewBar = subscriptionRecord.lastBarTime !== null && lastBar.time > subscriptionRecord.lastBarTime;
|
|
76
|
+
// 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
|
|
77
|
+
// old bar's last 5 seconds trades will be lost). Thus, at fist we should broadcast old bar updates when it's ready.
|
|
78
|
+
if (isNewBar) {
|
|
79
|
+
if (bars.length < 2) {
|
|
80
|
+
throw new Error('Not enough bars in history for proper pulse update. Need at least 2.');
|
|
81
|
+
}
|
|
82
|
+
const previousBar = bars[bars.length - 2];
|
|
83
|
+
subscriptionRecord.listener(previousBar);
|
|
84
|
+
}
|
|
85
|
+
subscriptionRecord.lastBarTime = lastBar.time;
|
|
86
|
+
subscriptionRecord.listener(lastBar);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function periodLengthSeconds(resolution, requiredPeriodsCount) {
|
|
90
|
+
let daysCount = 0;
|
|
91
|
+
if (resolution === 'D' || resolution === '1D') {
|
|
92
|
+
daysCount = requiredPeriodsCount;
|
|
93
|
+
}
|
|
94
|
+
else if (resolution === 'M' || resolution === '1M') {
|
|
95
|
+
daysCount = 31 * requiredPeriodsCount;
|
|
96
|
+
}
|
|
97
|
+
else if (resolution === 'W' || resolution === '1W') {
|
|
98
|
+
daysCount = 7 * requiredPeriodsCount;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
daysCount = requiredPeriodsCount * parseInt(resolution) / (24 * 60);
|
|
102
|
+
}
|
|
103
|
+
return daysCount * 24 * 60 * 60;
|
|
104
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* If you want to enable logs from datafeed set it to `true`
|
|
3
|
+
*/
|
|
4
|
+
const isLoggingEnabled = false;
|
|
5
|
+
export function logMessage(message) {
|
|
6
|
+
if (isLoggingEnabled) {
|
|
7
|
+
const now = new Date();
|
|
8
|
+
// tslint:disable-next-line:no-console
|
|
9
|
+
console.log(`${now.toLocaleTimeString()}.${now.getMilliseconds()}> ${message}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function getErrorMessage(error) {
|
|
13
|
+
if (error === undefined) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
else if (typeof error === 'string') {
|
|
17
|
+
return error;
|
|
18
|
+
}
|
|
19
|
+
return error.message;
|
|
20
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getErrorMessage, } from './helpers';
|
|
2
|
+
export class HistoryProvider {
|
|
3
|
+
constructor(datafeedUrl, requester) {
|
|
4
|
+
this._datafeedUrl = datafeedUrl;
|
|
5
|
+
this._requester = requester;
|
|
6
|
+
}
|
|
7
|
+
getBars(symbolInfo, resolution, periodParams) {
|
|
8
|
+
const requestParams = {
|
|
9
|
+
symbol: symbolInfo.ticker || '',
|
|
10
|
+
resolution: resolution,
|
|
11
|
+
from: periodParams.from,
|
|
12
|
+
to: periodParams.to,
|
|
13
|
+
};
|
|
14
|
+
if (periodParams.countBack !== undefined) {
|
|
15
|
+
requestParams.countback = periodParams.countBack;
|
|
16
|
+
}
|
|
17
|
+
if (symbolInfo.currency_code !== undefined) {
|
|
18
|
+
requestParams.currencyCode = symbolInfo.currency_code;
|
|
19
|
+
}
|
|
20
|
+
if (symbolInfo.unit_id !== undefined) {
|
|
21
|
+
requestParams.unitId = symbolInfo.unit_id;
|
|
22
|
+
}
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
this._requester.sendRequest(this._datafeedUrl, 'history', requestParams)
|
|
25
|
+
.then((response) => {
|
|
26
|
+
if (response.s !== 'ok' && response.s !== 'no_data') {
|
|
27
|
+
reject(response.errmsg);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const bars = [];
|
|
31
|
+
const meta = {
|
|
32
|
+
noData: false,
|
|
33
|
+
};
|
|
34
|
+
if (response.s === 'no_data') {
|
|
35
|
+
meta.noData = true;
|
|
36
|
+
meta.nextTime = response.nextTime;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const volumePresent = response.v !== undefined;
|
|
40
|
+
const ohlPresent = response.o !== undefined;
|
|
41
|
+
for (let i = 0; i < response.t.length; ++i) {
|
|
42
|
+
const barValue = {
|
|
43
|
+
time: response.t[i] * 1000,
|
|
44
|
+
close: parseFloat(response.c[i]),
|
|
45
|
+
open: parseFloat(response.c[i]),
|
|
46
|
+
high: parseFloat(response.c[i]),
|
|
47
|
+
low: parseFloat(response.c[i]),
|
|
48
|
+
};
|
|
49
|
+
if (ohlPresent) {
|
|
50
|
+
barValue.open = parseFloat(response.o[i]);
|
|
51
|
+
barValue.high = parseFloat(response.h[i]);
|
|
52
|
+
barValue.low = parseFloat(response.l[i]);
|
|
53
|
+
}
|
|
54
|
+
if (volumePresent) {
|
|
55
|
+
barValue.volume = parseFloat(response.v[i]);
|
|
56
|
+
}
|
|
57
|
+
bars.push(barValue);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
resolve({
|
|
61
|
+
bars: bars,
|
|
62
|
+
meta: meta,
|
|
63
|
+
});
|
|
64
|
+
})
|
|
65
|
+
.catch((reason) => {
|
|
66
|
+
const reasonString = getErrorMessage(reason);
|
|
67
|
+
// tslint:disable-next-line:no-console
|
|
68
|
+
console.warn(`HistoryProvider: getBars() failed, error=${reasonString}`);
|
|
69
|
+
reject(reasonString);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getErrorMessage, logMessage, } from './helpers';
|
|
2
|
+
export class QuotesProvider {
|
|
3
|
+
constructor(datafeedUrl, requester) {
|
|
4
|
+
this._datafeedUrl = datafeedUrl;
|
|
5
|
+
this._requester = requester;
|
|
6
|
+
}
|
|
7
|
+
getQuotes(symbols) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
this._requester.sendRequest(this._datafeedUrl, 'quotes', { symbols: symbols })
|
|
10
|
+
.then((response) => {
|
|
11
|
+
if (response.s === 'ok') {
|
|
12
|
+
resolve(response.d);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
reject(response.errmsg);
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
.catch((error) => {
|
|
19
|
+
const errorMessage = getErrorMessage(error);
|
|
20
|
+
logMessage(`QuotesProvider: getQuotes failed, error=${errorMessage}`);
|
|
21
|
+
reject(`network error: ${errorMessage}`);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getErrorMessage, logMessage, } from './helpers';
|
|
2
|
+
export class QuotesPulseProvider {
|
|
3
|
+
constructor(quotesProvider) {
|
|
4
|
+
this._subscribers = {};
|
|
5
|
+
this._requestsPending = 0;
|
|
6
|
+
this._quotesProvider = quotesProvider;
|
|
7
|
+
setInterval(this._updateQuotes.bind(this, 1 /* Fast */), 10000 /* Fast */);
|
|
8
|
+
setInterval(this._updateQuotes.bind(this, 0 /* General */), 60000 /* General */);
|
|
9
|
+
}
|
|
10
|
+
subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid) {
|
|
11
|
+
this._subscribers[listenerGuid] = {
|
|
12
|
+
symbols: symbols,
|
|
13
|
+
fastSymbols: fastSymbols,
|
|
14
|
+
listener: onRealtimeCallback,
|
|
15
|
+
};
|
|
16
|
+
logMessage(`QuotesPulseProvider: subscribed quotes with #${listenerGuid}`);
|
|
17
|
+
}
|
|
18
|
+
unsubscribeQuotes(listenerGuid) {
|
|
19
|
+
delete this._subscribers[listenerGuid];
|
|
20
|
+
logMessage(`QuotesPulseProvider: unsubscribed quotes with #${listenerGuid}`);
|
|
21
|
+
}
|
|
22
|
+
_updateQuotes(updateType) {
|
|
23
|
+
if (this._requestsPending > 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const listenerGuid in this._subscribers) { // tslint:disable-line:forin
|
|
27
|
+
this._requestsPending++;
|
|
28
|
+
const subscriptionRecord = this._subscribers[listenerGuid];
|
|
29
|
+
this._quotesProvider.getQuotes(updateType === 1 /* Fast */ ? subscriptionRecord.fastSymbols : subscriptionRecord.symbols)
|
|
30
|
+
.then((data) => {
|
|
31
|
+
this._requestsPending--;
|
|
32
|
+
if (!this._subscribers.hasOwnProperty(listenerGuid)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
subscriptionRecord.listener(data);
|
|
36
|
+
logMessage(`QuotesPulseProvider: data for #${listenerGuid} (${updateType}) updated successfully, pending=${this._requestsPending}`);
|
|
37
|
+
})
|
|
38
|
+
.catch((reason) => {
|
|
39
|
+
this._requestsPending--;
|
|
40
|
+
logMessage(`QuotesPulseProvider: data for #${listenerGuid} (${updateType}) updated with error=${getErrorMessage(reason)}, pending=${this._requestsPending}`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { logMessage } from './helpers';
|
|
2
|
+
export class Requester {
|
|
3
|
+
constructor(headers) {
|
|
4
|
+
if (headers) {
|
|
5
|
+
this._headers = headers;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
sendRequest(datafeedUrl, urlPath, params) {
|
|
9
|
+
if (params !== undefined) {
|
|
10
|
+
const paramKeys = Object.keys(params);
|
|
11
|
+
if (paramKeys.length !== 0) {
|
|
12
|
+
urlPath += '?';
|
|
13
|
+
}
|
|
14
|
+
urlPath += paramKeys.map((key) => {
|
|
15
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(params[key].toString())}`;
|
|
16
|
+
}).join('&');
|
|
17
|
+
}
|
|
18
|
+
logMessage('New request: ' + urlPath);
|
|
19
|
+
// Send user cookies if the URL is on the same origin as the calling script.
|
|
20
|
+
const options = { credentials: 'same-origin' };
|
|
21
|
+
if (this._headers !== undefined) {
|
|
22
|
+
options.headers = this._headers;
|
|
23
|
+
}
|
|
24
|
+
return fetch(`${datafeedUrl}/${urlPath}`, options)
|
|
25
|
+
.then((response) => response.text())
|
|
26
|
+
.then((responseTest) => JSON.parse(responseTest));
|
|
27
|
+
}
|
|
28
|
+
}
|