1let BARS = {}; 2let SERVER_TIME; 3let LASTPRICE_DATA = {}; 4 5class ChartingLibrary { 6 path = 'app/lib/tradingview'; 7 settings = { 8 A: { 9 overrides: { 10 'paneProperties.topMargin': 10, 11 'paneProperties.legendProperties.showSeriesTitle': true, 12 'paneProperties.legendProperties.showSeriesOHLC': true, 13 'mainSeriesProperties.style': 1, 14 'mainSeriesProperties.highLowAvgPrice.highLowPriceLinesVisible': true, 15 'mainSeriesProperties.highLowAvgPrice.highLowPriceLabelsVisible': true 16 }, 17 disabled_features: [ 18 // 'header_screenshot', 19 'save_chart_properties_to_local_storage', 20 ], 21 enabled_features: [ 22 'hide_last_na_study_output', 23 // 'two_character_bar_marks_labels' 24 ], 25 studies_overrides: {} 26 }, 27 S: { 28 overrides: { 29 'paneProperties.topMargin': 10, 30 'paneProperties.legendProperties.showSeriesTitle': true, 31 'mainSeriesProperties.style': 3 32 }, 33 disabled_features: [ 34 // header_widget 35 'header_widget', 36 'left_toolbar', 37 'context_menus', 38 'border_around_the_chart', 39 'timezone_menu', 40 'chart_property_page_timezone_sessions', 41 // use_localstorage_for_settings 42 'use_localstorage_for_settings' 43 ], 44 enabled_features: [], 45 studies_overrides: {} 46 }, 47 X: { 48 overrides: { 49 'paneProperties.vertGridProperties.color': 'transparent', 50 'paneProperties.horzGridProperties.color': 'transparent', 51 'paneProperties.legendProperties.showStudyArguments': false, 52 'paneProperties.legendProperties.showStudyTitles': false, 53 'paneProperties.legendProperties.showStudyValues': false, 54 'paneProperties.legendProperties.showSeriesTitle': false, 55 'paneProperties.legendProperties.showSeriesOHLC': false, 56 'paneProperties.legendProperties.showLegend': false, 57 'paneProperties.legendProperties.showBarChange': false, 58 'scalesProperties.fontSize': 10, 59 'scalesProperties.lineColor': '#191c2f', 60 'scalesProperties.textColor': '#989dd2', 61 'mainSeriesProperties.style': 3, 62 'mainSeriesProperties.areaStyle.linewidth': 2 63 }, 64 disabled_features: [ 65 'header_widget', 66 'left_toolbar', 67 'control_bar', 68 'timeframes_toolbar', 69 'context_menus', 70 'display_market_status', 71 'symbol_info', 72 'timezone_menu', 73 'popup_hints', 74 'use_localstorage_for_settings' 75 ], 76 enabled_features: [], 77 studies_overrides: {} 78 } 79 }; 80 81 constructor() { 82 this.code; 83 this.widget; 84 this.datafeed = new TradingViewDataFeed(); 85 86 appendScript(this.path + '/charting_library/charting_library.standalone.js'); 87 appendScript(this.path + '/datafeeds/udf/dist/bundle.js'); 88 } 89 90 applyColor(color) { 91 let color1, color2, linecolor; 92 93 if (color == 'text-color-green') { 94 linecolor = '#4FA77D'; 95 color1 = 'rgba(79, 167, 125, 0.5)'; 96 color2 = 'rgba(79, 167, 125, 0)'; 97 } else if (color == 'text-color-red') { 98 linecolor = '#C16161'; 99 color1 = 'rgba(193, 97, 97, 0.5)'; 100 color2 = 'rgba(193, 97, 97, 0)' 101 } else if (color == 'text-color-yellow') { 102 linecolor = '#b18637'; 103 color1 = 'rgba(203, 185, 89, 0.5)'; 104 color2 = 'rgba(203, 185, 89, 0)'; 105 } else { 106 linecolor = '#5189C9'; 107 color1 = 'rgba(81, 137, 201, 0.5)'; 108 color2 = 'rgba(81, 137, 201, 0)'; 109 } 110 111 if (this.widget) { 112 this.widget.applyOverrides({ 113 'mainSeriesProperties.areaStyle.color1': color1, 114 'mainSeriesProperties.areaStyle.color2': color2, 115 'mainSeriesProperties.areaStyle.linecolor': linecolor 116 }); 117 } 118 } 119 120 applyColorTemp() { 121 if (this.widget) { 122 this.widget.applyOverrides({ 123 'mainSeriesProperties.areaStyle.color1': 'rgba(81, 137, 201, 0.5)', 124 'mainSeriesProperties.areaStyle.color2': 'rgba(81, 137, 201, 0)', 125 'mainSeriesProperties.areaStyle.linecolor': '#5189C9' 126 }); 127 } 128 } 129 130 applyColorStock(close) { 131 try { 132 let last = close || sc.PriceLast(this.code) || sc.PricePrev(this.code); 133 let prev = sc.PricePrev(this.code); 134 135 this.applyColor(getColorClassChange(last - prev)); 136 } catch (error) { 137 138 } 139 } 140 141 async createWidget(param) { 142 let library_path = this.path + '/charting_library/'; 143 let { 144 type, 145 symbol, 146 container_id, 147 width, 148 height, 149 interval, 150 timeframe, 151 time_frames 152 } = param; 153 154 if (!window.cl_libs) { 155 await new Promise(r => setTimeout(r, 1000)); window.cl_libs = true; 156 } 157 158 this.hilo = param.hilo; 159 160 let p = { 161 symbol, // * 162 container: container_id, // * 163 datafeed: this.datafeed, // * 164 timezone: 'Asia/Jakarta', 165 // Setting this property to true will make the chart write detailed API logs into the browser console 166 debug: false, 167 library_path, 168 width: width ? width : '100%', 169 height: height ? height : '100%', 170 // study_count_limit: study_count_limit ? study_count_limit : '5', 171 locale: 'en', 172 numeric_formatting: { 173 decimal_sign: ',' 174 },
175 overrides: this.getSettings(type, 'overrides', {}), 176 disabled_features: this.getSettings(type, 'disabled_features', []), 177 enabled_features: this.getSettings(type, 'enabled_features', []), 178 studies_overrides: this.getSettings(type, 'studies_overrides', {}), 179 time_frames: time_frames ? time_frames : this.getTimeFrames(), 180 theme: 'Dark', 181 custom_css_url: 'static/css/chart.css', 182 snapshot_url: 'https://x.ipot.id/tv' 183 }; 184 185 if (interval) { 186 p['interval'] = interval; 187 } 188 189 if (timeframe) { 190 p['timeframe'] = timeframe; 191 } 192 193 // * - Required 194 this.widget = new TradingView.widget(p); 195 this.stock = getSecname(symbol); 196 this.code = symbol; 197 198 return await new Promise((resolve, reject) => { 199 let ctx = this; 200 201 this.widget.onChartReady(function () { 202 let elInfo = $$('#' + container_id).next(); 203 204 if (elInfo.length > 0 && elInfo.hasClass('container-info')) { 205 elInfo.on('click', function () { 206 app.dialog.alert("Chart yang kami gunakan disediakan oleh TradingView, sebuah platform charting bagi para trader dan investor dari seluruh penjuru dunia. Anda dapat menemukan kilasan dari berbagai instrumen seperti nilai tukar BTC USD, harga emas, saham, dan masih banyak lagi. Selain dari itu, TradingView juga menyediakan berbagai peralatan finansial gratis untuk digunakan seperti Kalender Ekonomi, Stock Screener dan juga Widget-Widget Finansial.") 207 }); 208 209 elInfo.removeClass('display-none'); 210 } 211 212 if (param.type == 'A') { 213 ctx.widget.chart().createStudy('Moving Average', false, false, null, null); 214 ctx.widget.chart().createStudy('Relative Strength Index', false, false, null, null); 215 } 216 217 // ctx.widget.chart().refreshMarks(); 218 219 ctx.widget.applyOverrides({ 220 'paneProperties.backgroundType': 'solid', 221 'paneProperties.background': '#05082c' 222 }); 223 224 if (ctx.stock) { 225 set(); 226 } else { 227 setTimeout(() => { 228 ctx.stock = getSecname(symbol); 229 set(); 230 }, 1000); 231 } 232 233 function set() { 234 if (ctx.stock) { 235 let entityId = { 236 prev: '', 237 // high: '', 238 // low: '' 239 }; 240 241 if (interval == 1) { 242 entityId.prev = ctx.createShape('prev'); 243 // entityId.high = ctx.createShape('high'); 244 // entityId.low = ctx.createShape('low'); 245 246 if (ctx.hilo) { 247 ctx.widget.applyOverrides({ 248 'mainSeriesProperties.highLowAvgPrice.highLowPriceLinesVisible': true, 249 'mainSeriesProperties.highLowAvgPrice.highLowPriceLabelsVisible': true 250 }); 251 } 252 } 253 254 ctx.widget.activeChart().onVisibleRangeChanged().subscribe( 255 null, ({ from, to }) => { 256 let resolution = ctx.widget.activeChart().resolution(); 257 258 if (resolution == '1') { 259 ctx.applyColorStock(); 260 } else { 261 ctx.applyColorTemp(); 262 } 263 } 264 ); 265 266 ctx.widget.activeChart().onSymbolChanged().subscribe(null, (data) => { 267 ctx.applyColorStock(); 268 269 if (ctx.hilo) { 270 ctx.widget.activeChart().removeEntity(entityId.prev); 271 // ctx.widget.activeChart().removeEntity(entityId.high); 272 // ctx.widget.activeChart().removeEntity(entityId.low); 273 } 274 275 setTimeout(() => { 276 entityId.prev = ctx.createShape('prev'); 277 // entityId.high = ctx.createShape('high'); 278 // entityId.low = ctx.createShape('low'); 279 }, 1000); 280 }); 281 282 if (interval == 1) { 283 ctx.applyColorStock(); 284 } else if (interval != '1D') { 285 ctx.applyColorTemp(); 286 } 287 } 288 } 289
290 ctx.widget.subscribe('onTick', function (data) { 291 let resolution = ctx.widget.activeChart().resolution(); 292 293 ctx.stock = getSecname(symbol); 294 295 if (ctx.stock && resolution == '1' && data) { 296 ctx.applyColorStock(data.close); 297 } 298 }); 299 300 resolve(true); 301 }); 302 }); 303 } 304 305 createShape(label) { 306 let point = { 307 price: 0 308 }; 309 310 if (!this.hilo) { 311 return; 312 } 313 314 if (label == 'prev') { 315 let symbol = this.widget.activeChart().symbol(); 316 let code = symbol.split(':')[1]; 317 let plast = sc.PriceLast(code); 318 let pprev = sc.PricePrev(code); 319 320 if (pprev && plast != pprev) { 321 point.price = pprev; 322 323 return this.widget.chart() 324 .createShape(point, { 325 shape: 'horizontal_line', 326 overrides: { 327 linecolor: '#b18637' 328 } 329 }); 330 } 331 } 332 // else if (label == 'high') { 333 // let high = this.datafeed.high; 334 335 // if (high > 0) { 336 // point.price = high; 337 338 // return this.widget.chart() 339 // .createShape(point, { 340 // shape: 'horizontal_line', 341 // overrides: { 342 // linecolor: '#4FA77D' 343 // } 344 // }); 345 // } 346 // } else if (label == 'low') { 347 // let low = this.datafeed.low; 348 349 // if (low > 0) { 350 // point.price = low; 351 352 // return this.widget.chart() 353 // .createShape(point, { 354 // shape: 'horizontal_line', 355 // overrides: { 356 // linecolor: '#C16161' 357 // } 358 // }); 359 // } 360 // } 361 } 362 363 getTimeFrames() { 364 return [ 365 { text: '15y', resolution: '1M', description: '15 Year' }, 366 { text: '10y', resolution: '1M', description: '10 Year' }, 367 { text: '5y', resolution: '1W', description: '5 Year' }, 368 { text: '3y', resolution: 'D', description: '3 Year' }, 369 { text: '1y', resolution: 'D', description: '1 Year' }, 370 { text: '6m', resolution: 'D', description: '6 Month' }, 371 { text: '3m', resolution: 'D', description: '3 Month' }, 372 { text: '1m', resolution: 'D', description: '1 Month' }, 373 { text: '5d', resolution: 'D', description: '5 Day' }, 374 { text: '1d', resolution: '1', description: '1 Day' } 375 ]; 376 } 377 378 getSettings(type, prop, template) { 379 try { 380 return this.settings[type][prop] ? this.settings[type][prop] : template; 381 } catch (error) { 382 return template; 383 } 384 } 385 386 setTimeframe(resolution, timeframe, etf = false) { 387 let ctx = this; 388 389 logger('setTimeframe: ', [resolution, timeframe]); 390 391 this.widget.chart().setResolution(resolution, function () { 392 let from; 393 let to = moment().unix(); 394 395 if (etf) { 396 ctx.widget.mainSeriesPriceFormatter()._priceScale = 1; 397 ctx.widget.applyOverrides({ 398 'mainSeriesProperties.highLowAvgPrice.highLowPriceLinesVisible': false, 399 'mainSeriesProperties.highLowAvgPrice.highLowPriceLabelsVisible': false 400 }); 401 } 402 403 if (timeframe == 'YTD') { 404 from = moment().startOf('year').unix(); 405 } else { 406 if (timeframe[1] == 'D') { 407 if (timeframe[0] == '1') { 408 if (etf) { 409 ctx.widget.mainSeriesPriceFormatter()._priceScale = 10000; 410 ctx.widget.applyOverrides({ 411 'mainSeriesProperties.highLowAvgPrice.highLowPriceLinesVisible': true, 412 'mainSeriesProperties.highLowAvgPrice.highLowPriceLabelsVisible': true 413 }); 414 } 415 416 try { 417 let ss = ctx.datafeed.session.split(','); 418 let hh = ss[0].split('-')[0]; 419 420 if (hh) { 421 hh = (hh + '').slice(0, 2); 422 } 423 424 from = moment().set('hour', +hh).set('minute', 0).set('second', 0).unix(); 425 } catch (error) { 426 // console.log(error); 427 } 428 } else { 429 from = moment().subtract(timeframe[0], 'days').unix(); 430 } 431 } else if (timeframe[1] == 'M') { 432 from = moment().subtract(timeframe[0], 'months').unix(); 433 } else if (timeframe[1] == 'Y') { 434 from = moment().subtract(timeframe[0], 'years').unix(); 435 } 436 } 437 438 if (from) { 439 ctx.widget.chart().setVisibleRange( 440 { from, to }, 441 { percentRightMargin: 20 } 442 ).then(() => { 443 444 }); 445 } 446 }); 447 } 448} 449 450class LightWeightChart { 451 452 constructor() { 453 this.hi; 454 this.lo; 455 this.hi_pl; 456 this.lo_pl;
457 this.createPriceLineHiLo = 'Y'; 458 459 this.tv = new TradingViewDataFeed(); 460 this.chart; 461 462 appendScript('app/lib/lightweight-charts/dist/lightweight-charts.standalone.production.js'); 463 } 464 465 async createChart(param, trending = false) { 466 let { 467 code, 468 container_id, 469 parameters_custom, 470 createPriceLineHiLo 471 } = param; 472 473 if ($$('#' + container_id).length > 0) { 474 $$('#' + container_id).empty(); 475 } 476 477 if (createPriceLineHiLo) { 478 this.createPriceLineHiLo = param.createPriceLineHiLo; 479 } 480 481 if (!window.lw_libs) { 482 await new Promise(r => setTimeout(r, 1000)); window.lw_libs = true; 483 } 484 485 let p = { 486 layout: { 487 backgroundColor: 'transparent', 488 textColor: '#989dd2' 489 }, 490 priceScale: { 491 position: 'right' 492 }, 493 timeScale: { 494 visible: false 495 }, 496 crosshair: { 497 vertLine: { 498 visible: false, 499 labelVisible: false 500 }, 501 horzLine: { 502 visible: false, 503 labelVisible: false 504 } 505 }, 506 grid: { 507 vertLines: { 508 visible: false 509 }, 510 horzLines: { 511 visible: false 512 } 513 }, 514 handleScroll: false, 515 handleScale: false 516 }; 517 518 Object.assign(p, parameters_custom); 519 520 let chart = LightweightCharts.createChart(container_id, p); 521 522 if (trending) { 523 let series = chart.addAreaSeries({ 524 lineWidth: 2, 525 crosshairMarkerVisible: false, 526 priceLineVisible: false, 527 autoscaleInfoProvider: original => { 528 let res = original(); 529 530 if (res) { 531 let { 532 minValue, maxValue 533 } = res.priceRange; 534 535 if (param.type == 'brt') { 536 if (minValue < 50 && maxValue < 50) { 537 res.priceRange.maxValue = 50; 538 } else if (minValue > 50 && maxValue > 50) { 539 res.priceRange.minValue = 50; 540 } 541 } else { 542 res.priceRange.minValue = 0; 543 } 544 } 545 546 return res; 547 } 548 }); 549 550 series.applyOptions({ 551 topColor: 'rgba(81, 137, 201, 0.5)', 552 bottomColor: 'rgba(81, 137, 201, 0)', 553 lineColor: '#5189C9' 554 }); 555 556 if (param.type == 'brt') { 557 series.createPriceLine({ 558 price: 50, 559 color: '#b18637' 560 }); 561 } else { 562 series.createPriceLine({ 563 price: 0, 564 color: '#5189C9' 565 }); 566 } 567 568 return { 569 chart, series 570 }; 571 } else { 572 await this.onReady(); 573 574 let symbolInfo = await this.onResolveSymbol(code);
575 let time = await this.getServerTime(true); 576 let bars = await this.getBars(symbolInfo, '1', time, true); 577 let data = this.getData(bars); 578 let plast = sc.PriceLast(code); 579 let pprev = sc.PricePrev(code); 580 581 let series = chart.addAreaSeries({ 582 lineWidth: 2, 583 crosshairMarkerVisible: false, 584 priceLineVisible: false, 585 autoscaleInfoProvider: original => { 586 let res = original(); 587 588 if (res) { 589 let { 590 minValue, maxValue 591 } = res.priceRange; 592 593 if (this.hi && this.lo) { 594 res.priceRange.minValue = this.lo; 595 res.priceRange.maxValue = this.hi; 596 } 597 598 if (pprev > 0) { 599 if (pprev < minValue) { 600 res.priceRange.minValue = pprev; 601 } else if (pprev > maxValue) { 602 res.priceRange.maxValue = pprev; 603 } 604 } 605 } 606 607 return res; 608 } 609 }); 610 611 if (pprev > 0) { 612 series.createPriceLine({ 613 price: pprev, 614 color: pprev == 1 ? '#5189C9' : '#b18637' 615 }); 616 617 this.updateChart(series, this.hi, this.lo, pprev, plast); 618 } 619 620 series.setData(data); 621 622 series.applyOptions({ 623 priceFormat: { 624 precision: 0, 625 minMove: 1 626 } 627 }); 628 629 chart.timeScale().fitContent(); 630 631 this.onRealTime(symbolInfo, '1', function (res) { 632 res.value = res.close; 633 634 series.update(res); 635 636 // context.updateChart(series, context.hi, context.lo, price.pprev, price.plast); 637 }); 638 639 return series; 640 } 641 } 642 643 async getBars(symbolInfo, resolution, time, caching = false) { 644 return await new Promise((resolve, reject) => { 645 let cache = BARS[symbolInfo.name]; 646 647 if (cache && caching) { 648 resolve(cache); 649 } 650 651 let periodParams = { 652 from: null, 653 to: time, 654 firstDataRequest: true 655 }; 656 657 this.tv.getBars(symbolInfo, resolution, periodParams, function cb_onHistory(res) { 658 if (caching) { 659 BARS[symbolInfo.name] = res; 660 } 661 662 resolve(res); 663 }, function cb_onError(err) { 664 reject(err); 665 }); 666 }); 667 } 668 669 getData(bars) { 670 let data = []; 671 let time; 672 673 bars.forEach(el => { 674 el.value = el.close; 675 676 if (this.hi < el.high || !this.hi) { 677 this.hi = el.high; 678 } 679 680 if (this.lo > el.low || !this.lo) { 681 this.lo = el.low; 682 } 683 684 if (time != el.time) { 685 data.push(el); 686 } 687 688 time = el.time; 689 }); 690 691 return data; 692 } 693 694 async getServerTime(caching = false) { 695 return await new Promise((resolve, reject) => { 696 this.tv.getServerTime((time) => { 697 if (caching && SERVER_TIME) { 698 resolve(SERVER_TIME); 699 } 700 701 if (time) { 702 SERVER_TIME = time; 703 704 setTimeout(() => { 705 SERVER_TIME = ''; 706 }, 1000); 707 708 resolve(time); 709 } else { 710 reject(); 711 } 712 }); 713 }); 714 } 715 716 async onReady() { 717 return await new Promise((resolve, reject) => { 718 this.tv.onReady((res) => { 719 if (res) { 720 resolve(res); 721 } else { 722 reject(); 723 } 724 }); 725 }); 726 } 727 728 onRealTime(symbolInfo, resolution, cb) { 729 let subscriberUID = symbolInfo.ticker + '_' + resolution; 730 731 this.tv.subscribeBars(symbolInfo, resolution, function cb_onRealtime(res) { 732 cb(res); 733 }, subscriberUID); 734 } 735 736 async onResolveSymbol(code) { 737 return await new Promise((resolve, reject) => { 738 this.tv.resolveSymbol(code, 739 (symbolInfo) => { 740 resolve(symbolInfo); 741 }, 742 (err) => { 743 reject(err); 744 } 745 ); 746 }); 747 } 748 749 updateChart(series, phi, plo, pprev, plast) { 750 let hi_pl_param = { 751 price: phi, 752 color: '#4FA77D', 753 title: 'Hi' 754 }; 755 let lo_pl_param = { 756 price: plo, 757 color: '#C16161', 758 title: 'Lo' 759 }; 760 let lineColor; 761 let topColor; 762 let bottomColor; 763 764 if (plast > pprev) {
765 lineColor = '#4fa77d'; 766 topColor = 'rgba(79, 167, 125, 0.5)'; 767 bottomColor = 'rgba(79, 167, 125, 0)'; 768 } else if (plast < pprev) { 769 lineColor = '#c16161'; 770 topColor = 'rgba(193, 97, 97, 0.5)'; 771 bottomColor = 'rgba(193, 97, 97, 0)' 772 } else if (plast == pprev) { 773 lineColor = '#b18637'; 774 topColor = 'rgba(203, 185, 89, 0.5)'; 775 bottomColor = 'rgba(203, 185, 89, 0)'; 776 } 777 778 series.applyOptions({ 779 topColor, bottomColor, lineColor 780 }); 781 782 if (phi && plo && this.createPriceLineHiLo == 'Y') { 783 if (this.hi_pl) { 784 if (this.hi != phi) { 785 series.removePriceLine(this.hi_pl); 786 this.hi_pl = series.createPriceLine(hi_pl_param); 787 } 788 } else { 789 this.hi_pl = series.createPriceLine(hi_pl_param); 790 } 791 792 if (this.lo_pl) { 793 if (this.lo != plo) { 794 series.removePriceLine(this.lo_pl); 795 this.lo_pl = series.createPriceLine(lo_pl_param); 796 } 797 } else { 798 this.lo_pl = series.createPriceLine(lo_pl_param); 799 } 800 } 801 } 802} 803 804class TradingViewDataFeed { 805 806 constructor() { 807 this.cmd = 'tvdfeed'; 808 this.high = 0; 809 this.low = 0; 810 this.session; 811 812 this.barsCache = []; 813 } 814 815 onReady(cb) { 816 let param = { 817 cmd: 'onReady' 818 }; 819 820 sc.SendWait(param, this.cmd, (err, data) => { 821 if (data) { 822 data.exchanges.push( 823 { value: 'INDICES', name: 'INDICES', desc: 'Sectoral' }, 824 { value: 'OTHER', name: 'OTHER', desc: 'Other' } 825 ); 826 // data.supports_marks = true; 827 cb(data); 828 } else { 829 cb(null); 830 } 831 }); 832 } 833 834 async searchSymbols(userInput, exchange = 'IDX', symbolType, cb) { 835 let results = []; 836 837 if (!userInput) { 838 cb(results); 839 return; 840 } 841 842 let pat = ";([^;]*" + userInput + "[^;]*);"; 843 let re = new RegExp(pat, "gi"); 844 let m, p; 845 let cacpat = sc.cache.pattern; 846 847 if (exchange == 'IDX') { 848 p = 'stock'; 849 } else if (exchange == 'INDICES') { 850 p = 'indicesname'; 851 852 if (cacpat.indicesname == ';;') { 853 await svc.getIndices('*'); 854 } 855 } else if (exchange == 'OTHER') { 856 p = 'otherdataname'; 857 } else { 858 p = '*'; 859 860 if (cacpat.indicesname == ';;') { 861 await svc.getIndices('*'); 862 } 863 } 864 865 if (p == '*') { 866 let o = cacpat.stock + cacpat.indicesname + cacpat.otherdataname; 867 m = o.match(re, "gi"); 868 } else { 869 m = cacpat[p].match(re, "gi"); 870 } 871 872 if (m) { 873 let keys = m.map((el) => el.replace(/;/g, "")); 874 875 for (let i = 0; i < keys.length; i++) { 876 let item = keys[i].split(":"); 877 878 results.push({ 879 description: item[1], 880 exchange, 881 full_name: item[0], 882 symbol: item[0], 883 ticker: item[0], 884 // type: 'stock' 885 }); 886 887 if (results.length == 50) { 888 break; 889 } 890 } 891 } 892 893 cb(results); 894 } 895 896 resolveSymbol(symbolName, cb_onSymbolResolved, cb_onResolveError) { 897 let ctx = this; 898 let param = { 899 cmd: 'resolveSymbol', 900 symbolName 901 }; 902 903 sc.SendWait(param, this.cmd, (err, data) => { 904 if (data) { 905 ctx.high = 0; 906 ctx.low = 0; 907 ctx.session = data.session; 908 909 data.visible_plots_set = data.has_no_volume; 910 911 delete data.has_no_volume;; 912 913 cb_onSymbolResolved(data); 914 } else {
915 cb_onResolveError(err); 916 } 917 }); 918 } 919 920 getBars(symbolInfo, resolution, periodParams, cb_onHistory, cb_onError) { 921 let ctx = this; 922 let rbars = []; 923 let { 924 from, countBack, to, firstDataRequest 925 } = periodParams; 926 let param = { 927 cmd: 'getBars', 928 symbolInfo, 929 resolution, 930 from, 931 to, 932 firstDataRequest 933 }; 934 let code = symbolInfo.name; 935 936 // logger('getBars: ', param); 937 938 if (code[0] == 'I' && code.length >= 5 && resolution == '1' && !ctx.getBarsCustom) { 939 ctx.getBarsCustom = true; 940 let sec = code.slice(1); 941 let today = moment().format('yyyy-MM-DD'); 942 let param = { 943 service: 'midata', 944 cmd: 'query', 945 param: { 946 source: 'datafeed', 947 index: 'en_qu_general_df', 948 args: ['TTC', '&', today, today, sec, '60', '', '', '', '', '', '', '', '', '', '', '', ''] 949 } 950 }; 951 952 // logger('TTC') 953 954 sc.SendWait(param, function (err, data) { 955 if (err) { 956 if (err == 'NODATA') { 957 cb_onHistory([], { noData: true }); 958 } else { 959 cb_onError('NODATA'); 960 } 961 } else { 962 if (data) { 963 // "TTC|&|2024-08-21|11:31:00|258.594|258.79|258.257|258.363" 964 // TTC|RefId|sdate|stime|Open|High|Low|Close 965 966 let string = data.en_qu_general_df; 967 let arr = string.split('|'); 968 let sdate = arr[2]; 969 let stime = arr[3]; 970 let open = +arr[4]; 971 let high = +arr[5]; 972 let low = +arr[6]; 973 let close = +arr[7]; 974 let time = moment(new Date(sdate + ' ' + stime)).unix() * 1000; 975 let bar = { 976 close, 977 high, 978 isBarClosed: false, 979 isLastBar: true, 980 low, 981 open, 982 time, 983 volume: 0 984 }; 985 986 if (ctx.high < bar.high) { 987 ctx.high = bar.high; 988 } 989 990 if (ctx.low == 0) { 991 ctx.low = bar.low; 992 } else if (bar.low < ctx.low) { 993 ctx.low = bar.low; 994 } 995 996 rbars.push(bar); 997 } else if (rbars.length > 0) { 998 cb_onHistory(rbars, { 999 noData: rbars.length == 0 1000 }); 1001 } else { 1002 cb_onHistory([], { noData: false }); 1003 } 1004 } 1005 }); 1006 } else { 1007 let today = new Date().setHours(0, 0, 0, 0); 1008 1009 sc.SendWait(param, this.cmd, (err, data) => { 1010 if (err) { 1011 if (err == 'NODATA') { 1012 cb_onHistory([], { noData: true }); 1013 } else { 1014 cb_onError('NODATA'); 1015 } 1016 } else { 1017 if (data) { 1018 let bar = JSON.parse(data); 1019 let day = new Date(bar.time).setHours(0, 0, 0, 0); 1020 1021 if (today == day) { 1022 if (ctx.high < bar.high) { 1023 ctx.high = bar.high; 1024 } 1025 1026 if (ctx.low == 0) { 1027 ctx.low = bar.low; 1028 } else if (bar.low < ctx.low) { 1029 ctx.low = bar.low; 1030 } 1031 1032 let date = new Date(); 1033 date.setHours(9, 0, 0, 0) 1034 let time = Math.trunc(date.getTime()); 1035 1036 if (bar.time == time && window.prevIHSG && code == 'COMPOSITE') { 1037 bar.open = +window.prevIHSG; 1038 bar.close = +window.prevIHSG; 1039 } 1040 } 1041 1042 rbars.push(bar); 1043 } else if (rbars.length > 0) { 1044 logger(code, rbars) 1045 1046 cb_onHistory(rbars, { 1047 noData: rbars.length == 0 1048 }); 1049 } else {
1050 cb_onHistory([], { noData: false }); 1051 } 1052 } 1053 }); 1054 } 1055 } 1056 1057 getMarks(symbolInfo, startDate, endDate, onDataCallback, resolution) { 1058 // console.log('getMarks: ', [symbolInfo, startDate, endDate, onDataCallback, resolution]); 1059 let arr = [ 1060 { 1061 id: 1, 1062 time: endDate, 1063 color: 'green', 1064 text: ['This is the mark pop-up text.'], 1065 label: 'B', 1066 minSize: 14 1067 }, 1068 { 1069 id: 2, 1070 time: endDate, 1071 color: 'red', 1072 text: ['This is the mark pop-up text.'], 1073 label: 'S', 1074 minSize: 14 1075 }, 1076 { 1077 id: 3, 1078 time: 1684886400000 / 1000, 1079 color: 'red', 1080 text: ['This is the mark pop-up text.'], 1081 label: 'B', 1082 minSize: 14 1083 } 1084 ]; 1085 1086 // 1685491200000 1087 // 2114355600 1088 1089 // for (let i = 0; i < 3; i++) { 1090 // arr.push({ 1091 // id: i + 1, 1092 // time: endDate, 1093 // color: 'red', 1094 // text: ['This is the mark pop-up text.'], 1095 // label: 'Se', 1096 // minSize: 14 1097 // }) 1098 // } 1099 1100 onDataCallback( 1101 arr 1102 ); 1103 }; 1104 1105 subscribeBars(symbolInfo, resolution, cb_onRealtime, subscriberUID, cb_onResetCacheNeeded) { 1106 let code = symbolInfo.name; 1107 let param = { 1108 cmd: 'subscribe', 1109 service: 'mi', 1110 rtype: resolution == '15' ? 'BAR15' : 'BAR1', 1111 resolution, 1112 code, 1113 subsid: subscriberUID 1114 }; 1115 1116 if (code[0] == 'I' && code.length >= 5 && resolution == 1) { 1117 let sec = getRETCode(code); 1118 let param = { 1119 cmd: 'subscribe', 1120 service: 'mi', 1121 rtype: 'ETFS', 1122 code: sec, 1123 subsid: subscriberUID + '_TV_ETF' 1124 }; 1125 1126 sc.Subscribe(param, (err, res) => { 1127 if (res) { 1128 let data = res.data.line; 1129 let string = data.split('|'); 1130 let last_price = +string[5]; 1131 let bar = { 1132 close: last_price, 1133 high: last_price, 1134 isBarClosed: false, 1135 isLastBar: true, 1136 low: last_price, 1137 open: last_price, 1138 time: moment().unix() * 1000, 1139 volume: 0 1140 }; 1141 1142 cb_onRealtime(bar, code); 1143 } 1144 }); 1145 } else { 1146 this.subscribeLastPrice = false; 1147 sc.Subscribe(param, (err, data) => { 1148 if (data) { 1149 let json = JSON.parse(data.data); 1150 1151 if (resolution == 1) { 1152 cb_onRealtime(json, code); 1153 } else if (LASTPRICE_DATA[code]) { 1154 let { 1155 popen, phi, plo, price 1156 } = LASTPRICE_DATA[code]; 1157 1158 json.close = price; 1159 json.open = popen; 1160 json.high = phi; 1161 json.low = plo; 1162 1163 cb_onRealtime(json, code); 1164 } else if (!this.subscribeLastPrice) { 1165 let param = { 1166 cmd: 'lastprice', 1167 code, 1168 subsid: subscriberUID + '_lastprice' 1169 }; 1170 1171 this.subscribeLastPrice = true; 1172 1173 sc.Subscribe(param, (err, data) => { 1174 let { 1175 popen, phi, plo, price 1176 } = data.data 1177 1178 if (data) { 1179 json.close = price; 1180 json.open = popen; 1181 json.high = phi; 1182 json.low = plo; 1183 1184 cb_onRealtime(json, code); 1185 1186 LASTPRICE_DATA[code] = data.data; 1187 } 1188 }); 1189 } 1190 } 1191 }); 1192 } 1193 } 1194 1195 unsubscribeBars(subscriberUID) { 1196 try { 1197 let code = subscriberUID.split('_')[0].split('.')[1]; 1198 1199 if (code) { 1200 delete LASTPRICE_DATA[code]; 1201 } 1202 } catch (error) { 1203 1204 } 1205 1206 sc.Unsubscribe(subscriberUID); 1207 sc.Unsubscribe(subscriberUID + '_lastprice') 1208 sc.Unsubscribe(subscriberUID + '_TV_ETF'); 1209 } 1210 1211 calculateHistoryDepth(resolution, resolutionBack, intervalBack) { 1212 let r = +resolution; 1213 1214 if (!isNaN(r)) { 1215 if (r == 1) return { resolutionBack: "D", intervalBack: 1 }; 1216 return { resolutionBack: "D", intervalBack: 1 }; 1217 } 1218 1219 if (resolution == "D") return { resolutionBack: "M", intervalBack: 1 }; 1220 if (resolution == "M") return { resolutionBack: "M", intervalBack: 1 }; 1221 } 1222 1223 getServerTime(cb) { 1224 let param = { 1225 cmd: 'getServerTime' 1226 }; 1227 1228 sc.SendWait(param, this.cmd, (err, data) => { 1229 if (data) { 1230 cb(data); 1231 } 1232 }); 1233 } 1234}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.