UNPKG

ai-stock-view-chart

Version:

Interactive stock charting library with candlestick, line charts, crosshair sync, overlays, and theming.

1 lines 92.3 kB
const t={name:"light",background:"#FFFFFF",chartAreaBackground:"#F8F8F8",gridColor:"#E0E0E0",textColor:"#333333",candleUp:"#4CAF50",candleDown:"#F44336",candleBorderColor:"#333333",lineColor:"#2196F3",crosshairColor:"#9E9E9E",overlayTextColor:"#333333",positiveColor:"rgba(76, 175, 80, 0.8)",negativeColor:"rgba(244, 67, 54, 0.8)"},i={name:"dark",background:"#1A1A1A",chartAreaBackground:"#2C2C2C",gridColor:"#444444",textColor:"#E0E0E0",candleUp:"#66BB6A",candleDown:"#EF5350",candleBorderColor:"#E0E0E0",lineColor:"#64B5F6",crosshairColor:"#BDBDBD",overlayTextColor:"#E0E0E0",positiveColor:"rgba(102, 187, 106, 0.8)",negativeColor:"rgba(239, 83, 80, 0.8)",borderColorUseBodyColor:!0};class s{plotTotalHeight;plotTotalWidth;yAxisWidth=80;bottomMargin=40;leftMargin=0;topMargin=0;constructor(t,i,s){this.canvasWidth=t,this.canvasHeight=i,this.plotConfigs=s,this.plots={},this.calculateLayout()}calculateLayout(){let t=0;const i=this.plotConfigs.filter(t=>!t.overlay).reduce((t,i)=>t+i.heightRatio,0);this.plotConfigs.forEach(s=>{if(s.overlay){const t=this.plots.main;t&&(this.plots[s.id]={...t})}else{const e=this.canvasWidth-this.leftMargin-this.yAxisWidth,n=this.canvasHeight-this.topMargin-this.bottomMargin,h=s.heightRatio/i*n;this.plots[s.id]={x:this.leftMargin,y:t+this.topMargin,width:e,height:h},t+=h}}),this.plotTotalHeight=t,this.plotTotalWidth=this.canvasWidth-this.yAxisWidth}getPlotLayout(t){return this.plots[t]||null}updatePlotIndicator(t,i){"delete"===i&&(this.plotConfigs=this.plotConfigs.filter(i=>!i.indicator||i.indicator.id!==t.id&&i.targetId!==t.targetId)),this.calculateLayout()}updateCanvasDimensions(t,i,s=void 0){this.canvasWidth=t,this.canvasHeight=i,void 0!==s&&(this.yAxisWidth=s),this.calculateLayout()}getPlotTotalWidth(){return this.plotTotalWidth}getPlotTotalHeight(){return this.plotTotalHeight}updatePlotConfigurations(t){this.plotConfigs=t,this.calculateLayout()}}class e{constructor(t,i,s=0){this.allData=t,this.visibleCount=Math.min(i,t.length+s),this.rightPadding=s,this.maxStartIndex=Math.max(0,t.length-this.visibleCount+s),this.startIndex=Math.max(0,t.length-(this.visibleCount-s))}MIN_PORT_VISIBLE_COUNT=10;getVisibleData(){const t=Math.min(this.startIndex+this.visibleCount,this.allData.length);return this.allData.slice(this.startIndex,t)}getVisibleStartEndTime(){const t=this.getVisibleData();return t&&0!==t.length?{startTime:t[0].time,endTime:t[t.length-1].time}:null}scroll(t){this.maxStartIndex=this.allData.length-this.visibleCount+this.rightPadding;const i=this.startIndex+t,s=Math.min(Math.round(.1*this.visibleCount),Math.round(.1*this.allData.length)),e=this.maxStartIndex+s;this.startIndex=Math.max(0,Math.min(e,i))}zoom(t,i){if(t<1&&this.startIndex<=0&&(window.dispatchEvent(new CustomEvent("requestOlderData",{detail:{currentOldestDataTime:this.allData[0]?.time,requestedCount:Math.round(.5*this.visibleCount),zoomFactor:t,anchorIndex:i}})),0===this.startIndex))return;const s=this.startIndex+i;let e=t;Math.round(this.visibleCount/t)>200&&t<.99&&(e*=.99);const n=Math.max(this.MIN_PORT_VISIBLE_COUNT,Math.min(this.allData.length,Math.round(this.visibleCount/e))),h=i/this.visibleCount,o=s-Math.round(n*h);this.visibleCount=n,this.maxStartIndex=Math.max(0,this.allData.length-this.visibleCount);const a=Math.round(.05*this.visibleCount),l=Math.max(-a,0),r=this.maxStartIndex+a;this.startIndex=Math.max(l,Math.min(r,o)),this.startIndex<=l&&t<1&&window.dispatchEvent(new CustomEvent("requestOlderData",{detail:{currentOldestDataTime:this.allData[0]?.time,requestedCount:Math.round(.5*this.visibleCount)}}))}updateData(t){this.allData=t,this.startIndex=Math.max(0,this.allData.length-this.visibleCount)}}function n(t,i,s,e,n){return(t-i)*n}function h(t,i,s,e,n){const h=s-i;return 0===h?n+e/2:n+e-(t-i)/h*e}function o(t){return{period:t,count:0,initialSum:0,ema:null,lastValue:null}}function a(t,i,s=!1){const e={...i};if(s&&null!==e.ema&&null!==e.lastValue){const i=2/(e.period+1),s=(e.ema-e.lastValue*i)/(1-i);e.ema=t*i+s*(1-i),e.lastValue=t}else if(!s){if(e.count<e.period)e.initialSum+=t,e.count++,e.count===e.period&&(e.ema=e.initialSum/e.period);else{const i=2/(e.period+1);e.ema=t*i+e.ema*(1-i)}e.lastValue=t}return{value:e.ema,state:e}}function l(t,i,s=!1){const e=a(t,i.fast,s),n=a(t,i.slow,s),h=null!==e.value&&null!==n.value?e.value-n.value:null;let o={value:null,state:i.signal};null!==h&&(o=a(h,i.signal,s));const l=null!==h&&null!==o.value?h-o.value:null;return{macdLine:h,signalLine:o.value,histogram:l,state:{fast:e.state,slow:n.state,signal:o.state}}}function r(t){return{period:t,window:[],sum:0}}function c(t,i,s=!1){let e=[...i.window],n=i.sum;if(s&&e.length>0){const i=e[e.length-1];e[e.length-1]=t,n=n-i+t}else e.push(t),n+=t,e.length>i.period&&(n-=e.shift());return{value:e.length===i.period?n/i.period:null,state:{period:i.period,window:e,sum:n}}}function d(t,i,s=!1){const e={...i};if(null===e.lastPrice)return e.lastPrice=t,{value:null,state:e};const n=t-e.lastPrice,h=Math.max(0,n),o=Math.max(0,-n);return s?e.gains.length>0&&(e.gains[e.gains.length-1]=h,e.losses[e.losses.length-1]=o):(e.gains.push(h),e.losses.push(o),e.gains.length>e.period&&(e.gains.shift(),e.losses.shift())),e.lastPrice=t,e.gains.length===e.period?(0===e.avgGain?(e.avgGain=e.gains.reduce((t,i)=>t+i,0)/e.period,e.avgLoss=e.losses.reduce((t,i)=>t+i,0)/e.period):(e.avgGain=(e.avgGain*(e.period-1)+h)/e.period,e.avgLoss=(e.avgLoss*(e.period-1)+o)/e.period),0===e.avgLoss?{value:100,state:e}:{value:100-100/(1+e.avgGain/e.avgLoss),state:e}):{value:null,state:e}}function u(t,i,s=!1,e=!1){let n=[...i.window],h=i.sum,o=i.sumSquares;if(s&&n.length>0){const i=n[n.length-1];n[n.length-1]=t,h=h-i+t,o=o-i*i+t*t}else if(n.push(t),h+=t,o+=t*t,n.length>i.period){const t=n.shift();h-=t,o-=t*t}let a=null,l=null,r=null;if(n.length===i.period){a=h/i.period;const t=e?i.period-1:i.period,s=o/t-a*a*i.period/t,n=Math.sqrt(Math.max(0,s));l=a+i.multiplier*n,r=a-i.multiplier*n}return{middle:a,upper:l,lower:r,state:{period:i.period,multiplier:i.multiplier,window:n,sum:h,sumSquares:o}}}function f(t,i,s,e=!1){let n=0,h=0;null===s.prevHigh||null===s.prevLow||e?null!==s.prevHigh&&null!==s.prevLow&&e&&(n=Math.max(0,t-s.prevHigh),h=Math.max(0,s.prevLow-i)):(n=Math.max(0,t-s.prevHigh),h=Math.max(0,s.prevLow-i));let o={value:null,state:s.demaxSMA},a={value:null,state:s.deminSMA};null!==s.prevHigh&&null!==s.prevLow&&(o=c(n,s.demaxSMA,e),a=c(h,s.deminSMA,e));let l=null;if(null!==o.value&&null!==a.value){const t=o.value+a.value;l=0!==t?o.value/t:.5}return{demarker:l,state:{period:s.period,prevHigh:e?s.prevHigh:t,prevLow:e?s.prevLow:i,demaxSMA:o.state,deminSMA:a.state}}}class p{constructor(t){this.type=t,this.points=[],this.style={strokeStyle:"#000000",lineWidth:1,fillStyle:"rgba(0, 0, 0, 0.1)"}}addPoint(t,i){this.points.push({time:t,price:i})}getPixelCoordinates(t,i,s,e,n,h,o=!1){if(!e?.allData||!e.getVisibleData)return null;const a=e.allData,l=e.getVisibleData(),r=a.findIndex(i=>i.time>=t);if(-1===(-1===r?a.length-1:0===r?0:Math.abs(a[r].time-t)<Math.abs(a[r-1].time-t)?r:r-1))return null;const c=s.width/e.visibleCount,d=l.findIndex(i=>i.time>=t),u=-1===d?l.length-1:0===d?0:Math.abs(l[d].time-t)<Math.abs(l[d-1].time-t)?d:d-1,f=s.x+u*c+c/2;return(i<n||i>h)&&!o?null:{x:f,y:s.y+(h-i)/(h-n)*s.height}}draw(t,i,s,e,n){}toJSON(){return{type:this.type,points:this.points,style:this.style}}fromJSON(t){this.points=t.points,this.style=t.style}}class m extends p{constructor(t,i="line"){super(i),this.barWidth=t,this.constraint="horizontal-line"===i?"horizontal":"vertical-line"===i?"vertical":"line"}draw(t,i,s,e,n,h,o){if(this.points.length<2)return;const a=this.getPixelCoordinates(this.points[0].time,this.points[0].price,i,s,e,n);let l=this.points[1].time,r=this.points[1].price;"horizontal"===this.constraint?r=this.points[0].price:"vertical"===this.constraint&&(l=this.points[0].time);const c=this.getPixelCoordinates(l,r,i,s,e,n);a&&c&&(t.beginPath(),t.strokeStyle=this.style.strokeStyle,t.lineWidth=this.style.lineWidth,t.moveTo(a.x,a.y),t.lineTo(c.x,c.y),t.stroke())}}class b extends p{constructor(){super("rectangle")}draw(t,i,s,e,n,h){if(this.points.length<2)return;const o=this.getPixelCoordinates(this.points[0].time,this.points[0].price,i,s,e,n),a=this.getPixelCoordinates(this.points[1].time,this.points[1].price,i,s,e,n);o&&a&&(t.beginPath(),t.fillStyle=this.style.fillStyle,t.strokeStyle=this.style.strokeStyle,t.lineWidth=this.style.lineWidth,t.rect(Math.min(o.x,a.x),Math.min(o.y,a.y),Math.abs(a.x-o.x),Math.abs(a.y-o.y)),t.fill(),t.stroke())}}class g extends p{constructor(t){super("fibonacci"),this.levels=[0,.236,.382,.618,1,1.618,2,2.618,3.618,4.236],this.style.strokeStyle="dark"===t?"#FFFFFF":"#000000"}draw(t,i,s,e,n,h,o){if(this.points.length<2)return;const a=!0,l=this.getPixelCoordinates(this.points[0].time,this.points[0].price,i,s,e,n,a),r=this.getPixelCoordinates(this.points[1].time,this.points[1].price,i,s,e,n,a);if(!l||!r)return;const c=Math.abs(this.points[1].price-this.points[0].price),d=this.points[1].price>this.points[0].price,u=this.points[0].price;let f=null,p=null;if(o){f=o.startTime,p=o.endTime;const t=Math.min(this.points[0].time,this.points[1].time),i=Math.max(this.points[0].time,this.points[1].time);if(t>p||i<f)return}t.lineWidth=this.style.lineWidth,t.strokeStyle=h.textColor,t.fillStyle=h.textColor,t.setLineDash([5,5]),t.textAlign="left",t.font="12px Arial",this.levels.forEach(h=>{const o=d?u+c*h:u-c*h,a=this.getPixelCoordinates(this.points[1].time,o,i,s,e,n);if(!a)return;t.beginPath(),t.moveTo(l.x,a.y),t.lineTo(r.x,a.y),t.stroke();const f=(100*h).toFixed(1)+"%";t.fillText(f,r.x+5,a.y)}),t.setLineDash([])}}class v extends p{constructor(t,i){super("fibonacci-zoon"),this.theme=t,this.halfBarWidth=i/2}fibSequence(t=8){if(t<0)return[];if(0===t)return[0];if(1===t)return[1];const i=[0,1,1];for(;i.length<t;){const t=i.length;i.push(i[t-1]+i[t-2])}return i}draw(t,i,s,e,n,h,o){if(this.points.length<2)return;this.getPixelCoordinates(this.points[0].time,this.points[0].price,i,s,e,n),this.getPixelCoordinates(this.points[1].time,this.points[1].price,i,s,e,n);let a=null,l=null;o&&(a=o.startTime,l=o.endTime);const r=s.allData;if(!r||r.length<2)return;const c=r.findIndex(t=>t.time>=this.points[0].time),d=r.findIndex(t=>t.time>=this.points[1].time);if(-1===c||-1===d)return;const u=Math.abs(d-c),f=d>c?1:-1,p=Math.max(1,u),m=this.fibSequence(10),b=m.map(t=>{const i=c+f*t*p,s=r.length-1;return i>0&&i<=s?r[i].time:i<0?r[0].time-1:r[s].time+1}),g=h.textColor;this.style.strokeStyle=g,this.style.fillStyle=g,t.lineWidth=this.style.lineWidth,t.strokeStyle=g,t.fillStyle=g,t.setLineDash([5,5]),t.textAlign="center",t.font="12px Arial",b.forEach((h,o)=>{const r=s.allData[s.allData.length-1]?.time;if(r&&h>r)return;if(a&&h<a)return;if(l&&h>l)return;const c=this.getPixelCoordinates(h,this.points[0].price,i,s,e,n);if(!c)return;t.beginPath(),t.moveTo(c.x,i.y),t.lineTo(c.x,i.y+i.height-20),t.stroke();const d=m[o].toString();t.fillText(d,c.x,i.height-15),t.measureText(d).width;const u=m[o]*p;if(u>0){const s=`(${u})`,e=t.measureText(s).width;t.fillText(s,c.x+e,i.height-15)}}),t.setLineDash([])}}class y{constructor(t){this.stockChart=t,this.drawings=[],this.activeTool=null,this.currentDrawing=null,this.isDrawing=!1,this.selectedDrawing=null,this.selectedPoint=null,this.isEditing=!1,this.t=!1,this.editPlotId=null,this.handleMouseDownBound=this.handleMouseDown.bind(this),this.handleMouseMoveBound=this.continueDrawing.bind(this),this.handleMouseUpBound=this.completeDrawing.bind(this),this.handleTouchStartBound=this.handleTouchStart.bind(this),this.handleTouchMoveBound=this.handleTouchMove.bind(this),this.handleTouchEndBound=this.handleTouchEnd.bind(this),this.stockChart.canvas.addEventListener("mousedown",this.handleMouseDownBound),this.stockChart.canvas.addEventListener("mousemove",this.handleMouseMoveBound),this.stockChart.canvas.addEventListener("mouseup",this.handleMouseUpBound),this.stockChart.canvas.addEventListener("touchstart",this.handleTouchStartBound,{passive:!1}),this.stockChart.canvas.addEventListener("touchmove",this.handleTouchMoveBound,{passive:!1}),this.stockChart.canvas.addEventListener("touchend",this.handleTouchEndBound),this.touchStartTime=0,this.touchTimeout=null,this.touchDistance=0,this.touchMoved=!1,this.defaultStyles={line:{strokeStyle:"#ff6b35",lineWidth:2,fillStyle:"rgba(255, 107, 53, 0.1)"},"horizontal-line":{strokeStyle:"#ff6b35",lineWidth:2,fillStyle:"rgba(255, 107, 53, 0.1)"},"vertical-line":{strokeStyle:"#ff6b35",lineWidth:2,fillStyle:"rgba(255, 107, 53, 0.1)"},rectangle:{strokeStyle:"#ff6b35",lineWidth:2,fillStyle:"rgba(255, 107, 53, 0.1)"},fibonacci:{strokeStyle:"dark"===t.options.theme?"#ffffffc5":"#000000a9",lineWidth:1,fillStyle:"rgba(76, 175, 80, 0.1)"},"fibonacci-zoon":{strokeStyle:"dark"===t.options.theme?"#ffffffc5":"#000000a9",lineWidth:1,fillStyle:"rgba(76, 175, 80, 0.1)"}}}get isChartFrozen(){return this.t}i(){return window.innerWidth<=768}h(t){const i=this.stockChart.canvas.getBoundingClientRect();return{x:t.clientX-i.left,y:t.clientY-i.top}}setActiveTool(t){"settings"!==t?(this.activeTool===t?this.activeTool=null:this.activeTool=t,this.currentDrawing=null,this.isDrawing=!1,this.selectedDrawing=null,this.selectedPoint=null,this.isEditing=!1):this.showIndicatorSettings()}startDrawing(t,i,s){if(this.activeTool&&this.activeTool!==t)return;this.isDrawing=!0,this.t=!0;const e=this.stockChart.plotLayoutManager.getPlotLayout("main");if(!e)return;const n=i-e.x,h=e.width/this.stockChart.dataViewport.visibleCount,o=Math.floor(n/h),a=this.stockChart.dataViewport.startIndex+o;if(!this.stockChart.dataViewport?.allData)return;const l=this.stockChart.dataViewport.allData;if(!Array.isArray(l)||a<0||a>=l.length)return;const r=l[a];if(!r||"number"!=typeof r.time)return;const c=r.time;if(!this.stockChart.options?.plots)return;const d=this.stockChart.options.plots.find(t=>"main"===t.id);if(!d)return;const u=this.stockChart.calculatePriceRange(d,this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport);if(!u)return;const{minPrice:f,maxPrice:p}=u,y=p-(s-e.y)/e.height*(p-f);switch(t){case"line":this.currentDrawing=new m(h);break;case"rectangle":this.currentDrawing=new b;break;case"fibonacci":this.currentDrawing=new g(this.stockChart.options.theme);break;case"fibonacci-zoon":this.currentDrawing=new v(this.stockChart.options.theme,h);break;case"horizontal-line":this.currentDrawing=new m(h,"horizontal-line");break;case"vertical-line":this.currentDrawing=new m(h,"vertical-line");break;default:return}this.defaultStyles[t]&&Object.assign(this.currentDrawing.style,this.defaultStyles[t]),this.currentDrawing.addPoint(c,y)}continueDrawing(t){const i=this.stockChart.canvas.getBoundingClientRect(),s=t.clientX-i.left,e=t.clientY-i.top,n=this.stockChart.plotLayoutManager.getPlotLayout("main");if(n&&s>=n.x&&s<=n.x+n.width&&e>=n.y&&e<=n.y+n.height){const t=s-n.x,i=n.width/this.stockChart.dataViewport.visibleCount,h=Math.floor(t/i),o=this.stockChart.dataViewport.startIndex+h;if(!this.stockChart.dataViewport?.allData||o<0||o>=this.stockChart.dataViewport.allData.length)return;const a=this.stockChart.dataViewport.allData[o];if(!a||"number"!=typeof a.time)return;const l=a.time,r=this.stockChart.options.plots.find(t=>"main"===t.id);if(!r)return;const c=this.stockChart.calculatePriceRange(r,this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport);if(!c)return;const d=e-n.y,{minPrice:u,maxPrice:f}=c,p=f-d/n.height*(f-u);let m=!1;if(this.isEditing&&this.selectedDrawing&&null!==this.selectedPoint){const t={...this.selectedDrawing.points[this.selectedPoint]},i={time:l,price:p};t.time===i.time&&t.price===i.price||(this.selectedDrawing.points[this.selectedPoint]=i,m=!0)}else if(this.isDrawing&&this.currentDrawing)if(1===this.currentDrawing.points.length){const t={time:l,price:p};if(["horizontal-line","vertical-line"].includes(this.activeTool)){const i=this.currentDrawing.points[0];"horizontal-line"===this.activeTool?t.price=i.price:"vertical-line"===this.activeTool&&(t.time=i.time)}this.currentDrawing.addPoint(t.time,t.price),m=!0}else if(2===this.currentDrawing.points.length){const t={...this.currentDrawing.points[1]},i=this.currentDrawing.points[0];let s={time:l,price:p};["horizontal-line","vertical-line"].includes(this.activeTool)&&("horizontal-line"===this.activeTool?s.price=i.price:"vertical-line"===this.activeTool&&(s.time=i.time)),t.time===s.time&&t.price===s.price||(this.currentDrawing.points[1]=s,m=!0)}m&&this.stockChart.render()}}completeDrawing(){this.isEditing?this.selectedPoint=null:this.isDrawing&&this.currentDrawing&&this.currentDrawing.points.length>=2&&(this.drawings.push(this.currentDrawing),this.isDrawing=!1,this.selectedDrawing=null,this.currentDrawing=null,this.isEditing=!1,this.t=!1,this.setActiveTool(null),this.stockChart&&"function"==typeof this.stockChart.setDrawingTool&&this.stockChart.setDrawingTool(null))}handleMouseDown(t){const i=this.stockChart.canvas.getBoundingClientRect(),s=t.clientX-i.left,e=t.clientY-i.top;if(this.activeTool)this.startDrawing(this.activeTool,s,e);else{if(this.trySelectPoint(s,e))return this.isEditing=!0,void(this.t=!0);this.selectedDrawing&&this.isNearDrawing(s,e,this.selectedDrawing)||(this.isEditing=!1,this.selectedDrawing=null,this.selectedPoint=null,this.t=!1)}}handleTouchStart(t){if(t.preventDefault(),t.stopPropagation(),this.touchStartTime=Date.now(),this.touchMoved=!1,this.touchDistance=0,1===t.touches.length){const i=t.touches[0],{x:s,y:e}=this.h(i);if(this.activeTool)this.startDrawing(this.activeTool,s,e);else{this.touchTimeout&&(clearTimeout(this.touchTimeout),this.touchTimeout=null);const t=this.i()?20:10;if(this.trySelectPoint(s,e,t))return this.isEditing=!0,this.t=!0,void(window.navigator&&window.navigator.vibrate&&window.navigator.vibrate(50));this.selectedDrawing&&this.isNearDrawing(s,e,this.selectedDrawing)||(this.touchTimeout=setTimeout(()=>{this.touchMoved||(this.isEditing=!1,this.selectedDrawing=null,this.selectedPoint=null,this.t=!1,this.stockChart.render())},300))}this.lastTouchX=s,this.lastTouchY=e}else 2===t.touches.length&&this.isEditing&&this.completeDrawing()}handleTouchMove(t){if(t.preventDefault(),t.stopPropagation(),1===t.touches.length){const i=t.touches[0],{x:s,y:e}=this.h(i),n=s-this.lastTouchX,h=e-this.lastTouchY;this.touchDistance+=Math.sqrt(n*n+h*h),this.touchDistance>10&&(this.touchMoved=!0,this.touchTimeout&&(clearTimeout(this.touchTimeout),this.touchTimeout=null)),(this.isDrawing&&this.currentDrawing||this.isEditing&&this.selectedDrawing)&&requestAnimationFrame(()=>{this.continueDrawing({clientX:i.clientX,clientY:i.clientY})}),this.lastTouchX=s,this.lastTouchY=e}else 2===t.touches.length&&(this.isDrawing||this.isEditing)&&(this.touchMoved=!0)}handleTouchEnd(t){t.preventDefault(),t.stopPropagation();const i=Date.now()-this.touchStartTime;this.touchTimeout&&(clearTimeout(this.touchTimeout),this.touchTimeout=null),!this.touchMoved&&i<300?this.isEditing&&(this.completeDrawing(),window.navigator&&window.navigator.vibrate&&window.navigator.vibrate(50)):(this.isDrawing||this.isEditing)&&this.completeDrawing(),this.touchStartTime=0,this.touchMoved=!1,this.touchDistance=0}clearDrawings(){this.drawings=[],this.currentDrawing=null,this.isDrawing=!1,this.selectedDrawing=null}render(t){this.stockChart.plotLayoutManager&&this.stockChart.plotLayoutManager.getPlotLayout("main")&&(t.save(),t.lineJoin="round",t.lineCap="round",this.drawings.forEach(i=>{this.renderDrawing(t,i)}),this.currentDrawing&&this.isDrawing&&this.renderDrawing(t,this.currentDrawing),this.selectedDrawing&&this.renderControlPoints(t,this.selectedDrawing),t.restore())}renderControlPoints(t,i){const s=this.stockChart.plotLayoutManager.getPlotLayout("main");if(!s)return;const e=this.stockChart.options.plots.find(t=>"main"===t.id);if(!e)return;const n=this.stockChart.calculatePriceRange(e,this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport);n&&i.points.forEach((e,h)=>{const o=(new p).getPixelCoordinates(e.time,e.price,s,this.stockChart.dataViewport,n.minPrice,n.maxPrice);if(o){if("fibonacci-zoon"===i.type){const t=s.width/this.stockChart.dataViewport.visibleCount;o.x+=t/2}const e=this.i(),n=e?8:4,a=e?2:1;e&&(t.beginPath(),t.arc(o.x,o.y,n+6,0,2*Math.PI),t.fillStyle="rgba(255, 255, 255, 0.2)",t.fill()),t.beginPath(),t.arc(o.x,o.y,n,0,2*Math.PI),t.fillStyle=h===this.selectedPoint?"#ff0000":"#ffffff",t.strokeStyle="#000000",t.lineWidth=a,t.fill(),t.stroke(),e&&(t.beginPath(),t.arc(o.x,o.y,2,0,2*Math.PI),t.fillStyle="#000000",t.fill())}})}renderDrawing(t,i){const{type:s,points:e,style:n}=i;switch(t.save(),s){case"line":case"horizontal-line":case"vertical-line":this.renderLine(t,e,n,s);break;case"rectangle":this.renderRectangle(t,e,n);break;case"fibonacci":case"fibonacci-zoon":const h=this.stockChart.dataViewport.getVisibleStartEndTime();i.draw(t,this.stockChart.plotLayoutManager.getPlotLayout("main"),this.stockChart.dataViewport,this.stockChart.calculatePriceRange(this.stockChart.options.plots.find(t=>"main"===t.id),this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport).minPrice,this.stockChart.calculatePriceRange(this.stockChart.options.plots.find(t=>"main"===t.id),this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport).maxPrice,this.stockChart.currentTheme,h)}t.restore()}renderLine(t,i,s,e){if(i.length<2)return;const n=this.stockChart.plotLayoutManager.getPlotLayout("main");if(!n)return;const h=this.stockChart.options.plots.find(t=>"main"===t.id);if(!h)return;const o=this.stockChart.calculatePriceRange(h,this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport);if(!o)return;const a=new p,l=a.getPixelCoordinates(i[0].time,i[0].price,n,{...this.stockChart.dataViewport,getVisibleData:()=>this.stockChart.dataViewport.getVisibleData()},o.minPrice,o.maxPrice),r=a.getPixelCoordinates(i[1].time,i[1].price,n,{...this.stockChart.dataViewport,getVisibleData:()=>this.stockChart.dataViewport.getVisibleData()},o.minPrice,o.maxPrice),c=this.stockChart.dataViewport.getVisibleStartEndTime();if(c){const{startTime:t,endTime:s}=c;if(i[0].time<t&&i[1].time<t)return;if(i[0].time>s&&i[1].time>s)return}l&&r&&(t.strokeStyle=s.strokeStyle,t.lineWidth=s.lineWidth,t.beginPath(),t.moveTo(l.x,l.y),t.lineTo(r.x,r.y),t.stroke())}renderRectangle(t,i,s){if(i.length<2)return;const e=this.stockChart.plotLayoutManager.getPlotLayout("main");if(!e)return;const n=this.stockChart.options.plots.find(t=>"main"===t.id);if(!n)return;const h=this.stockChart.calculatePriceRange(n,this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport);if(!h)return;const o=(new p).getPixelCoordinates(i[0].time,i[0].price,e,this.stockChart.dataViewport,h.minPrice,h.maxPrice),a=(new p).getPixelCoordinates(i[1].time,i[1].price,e,this.stockChart.dataViewport,h.minPrice,h.maxPrice),l=this.stockChart.dataViewport.getVisibleStartEndTime();if(l){const{startTime:t,endTime:s}=l;if(i[0].time<t&&i[1].time<t)return;if(i[0].time>s&&i[1].time>s)return}if(!o&&!a)return;const r=Math.min(o.x,a.x),c=Math.min(o.y,a.y),d=Math.abs(a.x-o.x),u=Math.abs(a.y-o.y);s.fillStyle&&(t.fillStyle=s.fillStyle,t.fillRect(r,c,d,u)),t.strokeStyle=s.strokeStyle,t.lineWidth=s.lineWidth,t.strokeRect(r,c,d,u)}isNearDrawing(t,i,s){const e=this.stockChart.plotLayoutManager.getPlotLayout("main");if(!e)return!1;const n=this.stockChart.options.plots.find(t=>"main"===t.id);if(!n)return!1;const h=this.stockChart.calculatePriceRange(n,this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport);if(!h)return!1;const o=s.points.map(t=>(new p).getPixelCoordinates(t.time,t.price,e,this.stockChart.dataViewport,h.minPrice,h.maxPrice)).filter(Boolean);if(o.length<2)return!1;switch(s.type){case"line":case"fibonacci":return this.isPointNearLine(t,i,o,10);case"rectangle":return this.isPointInRectangle(t,i,o);default:return!1}}isPointNearLine(t,i,s,e){if(s.length<2)return!1;const[n,h]=s;return this.pointToLineDistance(t,i,n.x,n.y,h.x,h.y)<=e}isPointInRectangle(t,i,s){if(s.length<2)return!1;const e=Math.min(s[0].x,s[1].x),n=Math.min(s[0].y,s[1].y),h=Math.abs(s[1].x-s[0].x),o=Math.abs(s[1].y-s[0].y);return t>=e&&t<=e+h&&i>=n&&i<=n+o}pointToLineDistance(t,i,s,e,n,h){const o=n-s,a=h-e,l=o*o+a*a;let r,c,d=-1;0!==l&&(d=((t-s)*o+(i-e)*a)/l),d<0?(r=s,c=e):d>1?(r=n,c=h):(r=s+d*o,c=e+d*a);const u=t-r,f=i-c;return Math.sqrt(u*u+f*f)}isNearDrawingPoint(t,i,s,e=10){const n=this.stockChart.plotLayoutManager.getPlotLayout("main");if(!n)return null;const h=this.stockChart.options.plots.find(t=>"main"===t.id);if(!h)return null;const o=this.stockChart.calculatePriceRange(h,this.stockChart.dataViewport.getVisibleData(),this.stockChart.dataViewport);if(!o)return null;for(let h=0;h<s.points.length;h++){const a=s.points[h],l=(new p).getPixelCoordinates(a.time,a.price,n,this.stockChart.dataViewport,o.minPrice,o.maxPrice);if(l){let n;if(n="fibonacci-zoon"===s.type?Math.abs(t-l.x):Math.sqrt(Math.pow(t-l.x,2)+Math.pow(i-l.y,2)),n<=e)return{pointIndex:h,drawing:s}}}return null}trySelectPoint(t,i,s=10){for(const e of this.drawings){const n=this.isNearDrawingPoint(t,i,e,s);if(n)return this.selectedDrawing=n.drawing,this.selectedPoint=n.pointIndex,this.isEditing=!0,!0}return!1}exportDrawings(){return JSON.stringify(this.drawings)}importDrawings(t){try{const i=JSON.parse(t);this.drawings=i}catch(t){}}showIndicatorSettings(t={}){const{indicatorId:i,settings:s,plotId:e}=t;this.indicators=[{name:"SMA",id:"sma",settings:[{key:"period",label:"Period",type:"number",default:20,min:1,max:200},{key:"priceType",label:"Price Type",type:"select",default:"close",options:[{value:"close",label:"Close"},{value:"hlc3",label:"HLC/3"},{value:"ohlc4",label:"OHLC/4"},{value:"hlcc4",label:"HLCC/4"}]},{key:"lineColor",label:"Line Color",type:"color",default:"#2196F3"}]},{name:"EMA",id:"ema",settings:[{key:"period",label:"Period",type:"number",default:20,min:1,max:200},{key:"priceType",label:"Price Type",type:"select",default:"close",options:[{value:"close",label:"Close"},{value:"hlc3",label:"HLC/3"},{value:"ohlc4",label:"OHLC/4"}]},{key:"lineColor",label:"Line Color",type:"color",default:"#FF9800"}]},{name:"RSI",id:"rsi",settings:[{key:"period",label:"Period",type:"number",default:14,min:2,max:100},{key:"priceType",label:"Price Type",type:"select",default:"close",options:[{value:"close",label:"Close"},{value:"hlc3",label:"HLC/3"},{value:"ohlc4",label:"OHLC/4"}]},{key:"overbought",label:"Overbought Level",type:"number",default:70,min:50,max:90},{key:"oversold",label:"Oversold Level",type:"number",default:30,min:10,max:50},{key:"lineColor",label:"RSI Line Color",type:"color",default:"#9C27B0"},{key:"overboughtColor",label:"Overbought Color",type:"color",default:"#F44336"},{key:"oversoldColor",label:"Oversold Color",type:"color",default:"#4CAF50"}]},{id:"macd",name:"MACD",plots:[{name:"MACD Line",type:"line"},{name:"Signal Line",type:"line"},{name:"Histogram",type:"histogram"}],settings:[{key:"fastPeriod",label:"Fast Period",type:"number",default:12,min:1,max:50},{key:"slowPeriod",label:"Slow Period",type:"number",default:26,min:1,max:100},{key:"signalPeriod",label:"Signal Period",type:"number",default:9,min:1,max:50},{key:"priceType",label:"Price Type",type:"select",default:"close",options:[{value:"close",label:"Close"},{value:"hlc3",label:"HLC/3"},{value:"ohlc4",label:"OHLC/4"}]},{key:"macdColor",label:"MACD Line Color",type:"color",default:"#2196F3"},{key:"signalColor",label:"Signal Line Color",type:"color",default:"#FF5722"},{key:"histogramColor",label:"Histogram Color",type:"color",default:"#795548"}]},{name:"Bollinger Bands",id:"bollinger",settings:[{key:"period",label:"Period",type:"number",default:20,min:2,max:100},{key:"stdDev",label:"Standard Deviation",type:"number",default:2,min:.1,max:5,step:.001},{key:"priceType",label:"Price Type",type:"select",default:"close",options:[{value:"close",label:"Close"},{value:"hlc3",label:"HLC/3"},{value:"ohlc4",label:"OHLC/4"}]},{key:"upperBandColor",label:"Upper Band Color",type:"color",default:"#E91E63"},{key:"middleBandColor",label:"Middle Band Color",type:"color",default:"#9C27B0"},{key:"lowerBandColor",label:"Lower Band Color",type:"color",default:"#3F51B5"},{key:"fillColor",label:"Fill Color",type:"color",default:"#E1F5FE",opacity:!0}]},{name:"DeMarker",id:"demarker",settings:[{key:"period",label:"Period",type:"number",default:14,min:1,max:100},{key:"overbought",label:"Overbought Level",type:"number",default:.7,min:.5,max:.9,step:.01},{key:"oversold",label:"Oversold Level",type:"number",default:.3,min:.1,max:.5,step:.01},{key:"lineColor",label:"DeMarker Line Color",type:"color",default:"#607D8B"},{key:"overboughtColor",label:"Overbought Color",type:"color",default:"#F44336"},{key:"oversoldColor",label:"Oversold Color",type:"color",default:"#4CAF50"}]}];const n=document.createElement("div");n.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n ";const h=document.createElement("div");h.style.cssText="\n background-color: white;\n border-radius: 8px;\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);\n width: 650px;\n max-width: 90vw;\n max-height: 80vh;\n overflow: hidden;\n font-family: Arial, sans-serif;\n ",h.innerHTML=`\n <div class="modal-header" style="padding: 20px; border-bottom: 1px solid #e0e0e0; background-color: #f8f9fa;">\n <h3 style="margin: 0; color: #333; font-size: 18px;">Technical Indicators</h3>\n </div>\n \n <div class="tab-container" style="display: flex; border-bottom: 1px solid #e0e0e0; background-color: #f8f9fa; overflow-x: auto;">\n ${this.indicators.map((t,i)=>`\n <button class="tab-btn ${0===i?"active":""}" data-tab="${t.id}" style="\n flex: 0 0 auto;\n padding: 12px 16px;\n border: none;\n background: ${0===i?"white":"transparent"};\n color: ${0===i?"#333":"#666"};\n cursor: pointer;\n border-bottom: 2px solid ${0===i?"#007bff":"transparent"};\n font-weight: 500;\n white-space: nowrap;\n min-width: 80px;\n ">${t.name}</button>\n `).join("")}\n </div>\n \n <div class="tab-content" style="padding: 20px; max-height: 450px; overflow-y: auto;">\n ${this.indicators.map((t,i)=>`\n <div id="${t.id}-tab" class="tab-pane ${0===i?"active":""}" style="display: ${0===i?"block":"none"};">\n <div class="indicator-settings">\n <h4 style="margin: 0 0 20px 0; color: #333; font-size: 16px;">${t.name} Settings</h4>\n \n <form class="settings-form" data-indicator="${t.id}">\n ${t.settings.map(i=>"select"===i.type?`\n <div class="form-group" style="margin-bottom: 16px;">\n <label style="display: block; margin-bottom: 6px; color: #333; font-weight: 500; font-size: 14px;">${i.label}</label>\n <select name="${i.key}" style="\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 14px;\n background: white;\n ">\n ${i.options.map(t=>`\n <option value="${t.value}" ${t.value===i.default?"selected":""}>${t.label}</option>\n `).join("")}\n </select>\n </div>\n `:"color"===i.type?`\n <div class="form-group" style="margin-bottom: 16px;">\n <label style="display: block; margin-bottom: 6px; color: #333; font-weight: 500; font-size: 14px;">${i.label}</label>\n <div style="display: flex; align-items: center; gap: 10px;">\n <input \n type="color" \n name="${i.key}" \n value="${i.default}" \n id="${t.id}-${i.key}"\n style="\n width: 50px;\n height: 35px;\n border: 1px solid #ddd;\n border-radius: 4px;\n cursor: pointer;\n background: white;\n padding: 2px;\n "\n />\n <input \n type="text" \n name="${i.key}_hex"\n value="${i.default}" \n placeholder="#RRGGBB"\n maxlength="7"\n style="\n flex: 1;\n padding: 8px 12px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 14px;\n font-family: monospace;\n box-sizing: border-box;\n "\n />\n ${i.opacity?`\n <div style="display: flex; align-items: center; gap: 5px; min-width: 80px;">\n <label style="font-size: 12px; color: #666;">Opacity:</label>\n <input \n type="range" \n name="${i.key}_opacity"\n min="0" \n max="1" \n step="0.1" \n value="0.3"\n style="width: 60px;"\n />\n <span class="opacity-value" style="font-size: 11px; color: #666; min-width: 25px;">0.3</span>\n </div>\n `:""}\n </div>\n </div>\n `:`\n <div class="form-group" style="margin-bottom: 16px;">\n <label style="display: block; margin-bottom: 6px; color: #333; font-weight: 500; font-size: 14px;">${i.label}</label>\n <input \n type="${i.type}" \n name="${i.key}" \n value="${i.default}" \n id="${t.id}-${i.key}"\n ${i.min?`min="${i.min}"`:""}\n ${i.max?`max="${i.max}"`:""}\n ${i.step?`step="${i.step}"`:""}\n style="\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 14px;\n box-sizing: border-box;\n "\n />\n </div>\n `).join("")}\n \n <div class="form-actions" style="margin-top: 24px; display: flex; gap: 12px;">\n <button type="submit" class="add-indicator-btn" id="add-indicator-btn" style="\n background: #00c2ff;\n color: white;\n border: none;\n padding: 10px 20px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n ">${this.editPlotId?"Update Indicator":"➕ Add Indicator"}</button>\n </div>\n </form>\n \n <div class="active-instances" style="margin-top: 24px; padding-top: 20px; border-top: 1px solid #e0e0e0;">\n <h5 style="margin: 0 0 12px 0; color: #333; font-size: 14px;">Active ${t.name} Instances</h5>\n <div id="${t.id}-instances" class="instances-list">\n \x3c!-- Active instances will be populated here --\x3e\n </div>\n </div>\n </div>\n </div>\n `).join("")}\n </div>\n \n <div class="modal-footer" style="\n padding: 16px 20px;\n border-top: 1px solid #e0e0e0;\n background-color: #f8f9fa;\n display: flex;\n justify-content: space-between;\n align-items: center;\n ">\n <div style="color: #666; font-size: 12px;">\n Configure settings and colors, then click "Add Indicator" to apply\n </div>\n <button id="close-indicator-settings" style="\n background: #6c757d;\n color: white;\n border: none;\n padding: 8px 20px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n ">Close</button>\n </div>\n `,n.appendChild(h),document.body.appendChild(n),this.initializeIndicatorDialog(n,this.indicators,{editIndicatorId:i,editSettings:s,editPlotId:e})}getMainPlotStockData(){return this.stockChart.options.plots.find(t=>"main"===t.id).data}initializeIndicatorDialog(t,i,s={}){const{editIndicatorId:e,editSettings:n}=s,h=t.querySelector("div");this.setupColorPickerSync(h);const o=h.querySelectorAll(".tab-btn"),a=h.querySelectorAll(".tab-pane");if(o.forEach(t=>{t.addEventListener("click",()=>{o.forEach(t=>{t.classList.remove("active"),t.style.background="transparent",t.style.color="#666",t.style.borderBottomColor="transparent"}),a.forEach(t=>{t.classList.remove("active"),t.style.display="none"}),t.classList.add("active"),t.style.background="white",t.style.color="#333",t.style.borderBottomColor="#007bff";const i=h.querySelector(`#${t.dataset.tab}-tab`);i.classList.add("active"),i.style.display="block",this.updateInstancesList(t.dataset.tab)})}),h.addEventListener("submit",t=>{t.preventDefault();const s=t.target,e=s.dataset.indicator,n=new FormData(s),h={};for(let[t,s]of n.entries()){if(t.endsWith("_hex"))continue;const n=i.find(t=>t.id===e).settings.find(i=>i.key===t);n&&"number"===n.type?h[t]=parseFloat(s):h[t]=s}this.addIndicatorWithSettings(e,h),this.updateInstancesList(e);const o=s.querySelector(".add-indicator-btn");o.textContent="✓ Successfully",o.style.background="#28a745",setTimeout(()=>{o.textContent="➕ Add Indicator",o.style.background="#00c2ff"},2e3)}),h.addEventListener("click",t=>{if(t.target.classList.contains("remove-instance-btn")){const i=t.target.dataset.plotId,s=t.target.dataset.indicatorId;this.removeIndicator(i),this.updateInstancesList(s)}if(t.target.classList.contains("edit-instance-btn")){const i=t.target.dataset.plotId;this.editIndicatorInstance(i);const s=document.getElementById("add-indicator-btn");s&&(s.textContent="✏️ Update Indicator")}}),h.querySelector("#close-indicator-settings").addEventListener("click",()=>{document.body.removeChild(t)}),t.addEventListener("click",i=>{i.target===t&&document.body.removeChild(t)}),i.forEach(t=>{this.updateInstancesList(t.id)}),e&&n){const t=h.querySelector(`.tab-btn[data-tab="${e}"]`);t&&t.click();const i=h.querySelector(`#${e}-tab .settings-form`);if(i)for(const t in n){const s=i.querySelector(`[name="${t}"]`);if(s){s.value=n[t];const e=i.querySelector(`[name="${t}_hex"]`);e&&(e.value=n[t])}}}}setupColorPickerSync(t){t.addEventListener("input",i=>{const s=i.target;if("color"===s.type){const i=t.querySelector(`[name="${s.name}_hex"]`);i&&(i.value=s.value.toUpperCase())}if(s.name&&s.name.endsWith("_hex")){const i=s.name.replace("_hex",""),e=t.querySelector(`[name="${i}"]`);e&&this.isValidHex(s.value)&&(e.value=s.value)}if("range"===s.type&&s.name.includes("opacity")){const t=s.parentElement.querySelector(".opacity-value");t&&(t.textContent=s.value)}}),t.addEventListener("blur",t=>{const i=t.target;i.name&&i.name.endsWith("_hex")&&(this.isValidHex(i.value)?(i.style.borderColor="#ddd",i.title=""):(i.style.borderColor="#dc3545",i.title="Invalid hex color format. Use #RRGGBB"))},!0)}isValidHex(t){return/^#[0-9A-Fa-f]{6}$/.test(t)}updateInstancesList(t){const i=document.querySelector(`#${t}-instances`);if(!i)return;const s=this.getIndicatorInstances(t);if(this.indicators.find(i=>i.id===t),0===s.length)i.innerHTML=`\n <div style="color: #666; font-style: italic; padding: 12px; text-align: center; border: 1px dashed #ddd; border-radius: 4px;">\n No ${t.toUpperCase()} instances added yet\n </div>\n `;else{const e=[];new Set(s.map(t=>t.name)).forEach(t=>{const i=s.find(i=>i.name===t);i&&e.push(i)}),i.innerHTML=e.map(i=>`\n <div class="instance-item" style="\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n margin-bottom: 8px;\n background: #f9f9f9;\n ">\n <div style="flex: 1;">\n <div style="font-weight: 500; color: #333; font-size: 13px; display: flex; align-items: center; gap: 8px;">\n ${i.name}\n ${this.getColorIndicatorsForInstance(i)}\n </div>\n <div style="color: #666; font-size: 11px;">${this.formatInstances(i)}</div>\n </div>\n <div style="display: flex; gap: 6px;">\n <button class="edit-instance-btn" data-plot-id="${i.plotId}" style="\n background: #ffc107;\n color: #212529;\n border: none;\n padding: 4px 8px;\n border-radius: 3px;\n cursor: pointer;\n font-size: 11px;\n ">Edit</button>\n <button class="remove-instance-btn" data-plot-id="${i.plotId}" data-indicator-id="${t}" style="\n background: #dc3545;\n color: white;\n border: none;\n padding: 4px 8px;\n border-radius: 3px;\n cursor: pointer;\n font-size: 11px;\n ">Remove</button>\n </div>\n </div>\n `).join("")}}getColorIndicatorsForInstance(t){return Object.entries(t.settings).filter(([t,i])=>t.toLowerCase().includes("color")&&"string"==typeof i&&i.startsWith("#")).map(([t,i])=>`\n <div style="\n width: 12px; \n height: 12px; \n background-color: ${i}; \n border: 1px solid #ccc; \n border-radius: 2px;\n display: inline-block;\n margin-right: 2px;\n title: '${t}: ${i}'\n "></div>\n `).join("")}formatInstances({settings:t}){return Object.entries(t).filter(([t,i])=>!t.toLowerCase().includes("color")&&!t.includes("opacity")).map(([t,i])=>`${t}: ${i}`).join(", ")}getIndicatorInstances(t){return this.stockChart&&this.stockChart.options&&this.stockChart.options.plots?this.stockChart.options.plots.filter(i=>i.indicator&&i.indicator.id===t).map(t=>({name:t.indicator.name,plotId:t.id,settings:t.indicator.settings||{}})):[]}addIndicatorWithSettings(t,i){this.editPlotId&&this.removeIndicator(this.editPlotId);let s=this.getIndicatorDisplayName(t);i.period&&(s+=` (${i.period})`),this.stockChart.options.plots||(this.stockChart.options.plots=[]);const e=this.getValueSelector(i.priceType||"close");let n=[];const h=this.getMainPlotStockData();switch(t){case"sma":n=function(t,i,s=t=>t.close){let e=r(i);const n=[];for(const i of t){const t=c(s(i),e);n.push({time:i.time,value:t.value}),e=t.state}return n}(h,i.period,e);break;case"bollinger":n=function(t,i,s=2,e=t=>t.close){let n=function(t=20,i=2){return{period:t,multiplier:i,window:[],sum:0,sumSquares:0}}(i,s),h=r(i);const o=[];for(const i of t){const t=u(e(i),n),s=c(e(i),h);o.push({time:i.time,upper:t.upper,lower:t.lower,middle:s.value}),n=t.state,h=s.state}return o}(h,i.period,i.standardDeviationMultiplier,e);break;case"demarker":n=function(t,i=14){let s=function(t=14){return{period:t,prevHigh:null,prevLow:null,demaxSMA:r(t),deminSMA:r(t)}}(i);const e=[];for(const i of t){const t=f(i.high,i.low,s);e.push({time:i.time,value:t.demarker}),s=t.state}return e}(h,i.period);break;case"macd":n=function(t,i=12,s=26,e=9,n=t=>t.close){let h=function(t=12,i=26,s=9){return{fast:o(t),slow:o(i),signal:o(s)}}(i,s,e);const a=[];for(const i of t){const t=l(n(i),h);a.push({time:i.time,macd:t.macdLine,signal:t.signalLine,histogram:t.histogram}),h=t.state}return a}(h,i.fastPeriod,i.slowPeriod,i.signalPeriod,e);break;case"ema":n=function(t,i,s=t=>t.close){let e=o(i);const n=[];for(const i of t){const t=a(s(i),e);n.push({time:i.time,value:t.value}),e=t.state}return n}(h,i.period,e);break;case"rsi":n=function(t,i=14,s=t=>t.close){let e=function(t=14){return{period:t,gains:[],losses:[],avgGain:0,avgLoss:0,lastPrice:null}}(i);const n=[];for(const i of t){const t=d(s(i),e);n.push({time:i.time,value:t.value}),e=t.state}return n}(h,i.period,e)}const p=this.stockChart.options.plots?.length||1;this.getPlotsByIndicatorId(t,n,p,i).forEach((e,n)=>{e.indicator={id:t,settings:i,name:s},this.stockChart.options.plots.push(e),"main"===e.id||this.stockChart.plotLayoutManager.updatePlotIndicator(e,"add")}),this.stockChart.render();const m=JSON.parse(localStorage.getItem("asv-chart-indicator-settings"))||[];let b;b=["sma","ema"].includes(t)?m.findIndex(s=>s.id===t&&s.settings.period===i.period):m.findIndex(i=>i.id===t),b>-1?m[b].settings=i:m.push({id:t,settings:i}),localStorage.setItem("asv-chart-indicator-settings",JSON.stringify(m)),this.editPlotId=null}getValueSelector(t){switch(t){case"hlc/3":return t=>(t.high+t.low+t.close)/3;case"ohlc/4":return t=>(t.open+t.high+t.low+t.close)/4;case"hlcc/4":return t=>(t.high+t.low+2*t.close)/4;default:return t=>t.c