UNPKG

openalgo-charts

Version:

From-scratch, dependency-free canvas charting engine for OpenAlgo: professional-grade interactive rendering, advanced on-chart trading, and a one-call widget tier that adds the toolbar, drawing rail, dialogs and shortcuts.

3 lines (2 loc) 15.6 kB
import{registerChartType as e}from"./openalgo-charts.mjs";function t(e,t,i){const o=e.boxSize;return"number"==typeof o&&o>0?o:void 0!==t.boxSize&&t.boxSize>0?t.boxSize:i}function i(e,i,o,r,s,n,h=1/0){if(0===i.length)return;const l=function(e){let t=1/0;for(const{bar:i}of e){const e=i.high-i.low;e>0&&e<t&&(t=e)}return Number.isFinite(t)?t:0}(i),a=Math.max(3,Math.floor(r*s*.7))/2,u=h===1/0?1/0:h*s;e.save(),e.lineWidth=Math.max(1,Math.floor(s));for(const{x:r,bar:h}of i){const i=t(h,n,l);if(!(i>0))continue;const c=Math.round(h.low/i),_=Math.round(h.high/i)-1;if(_<c)continue;if(_-c+1>4e3)continue;const b=h.close>=h.open;e.strokeStyle=b?n.upColor??"#26a69a":n.downColor??"#ef5350";const f=r*s;for(let t=c;t<=_;t++){const r=o((t+1)*i)*s,n=o(t*i)*s;n<0||r>u||(e.beginPath(),b?(e.moveTo(f-a,r),e.lineTo(f+a,n),e.moveTo(f+a,r),e.lineTo(f-a,n)):e.ellipse(f,(r+n)/2,a,Math.abs(n-r)/2,0,0,2*Math.PI),e.stroke())}}e.restore()}function o(e){let t=-1/0;const i=[];for(const o of e){let e=o.time;e<=t&&(e=t+1),t=e,i.push(e===o.time?o:{...o,time:e})}return i}function r(e,t){e.reset();const i=[];for(const o of t)i.push(...e.push(o));return e.flush&&i.push(...e.flush()),o(i)}class s{constructor(){Object.defineProperty(this,"_prevOpen",{enumerable:!0,configurable:!0,writable:!0,value:NaN}),Object.defineProperty(this,"_prevClose",{enumerable:!0,configurable:!0,writable:!0,value:NaN})}reset(){this._prevOpen=NaN,this._prevClose=NaN}push(e){const t=(e.open+e.high+e.low+e.close)/4,i=Number.isNaN(this._prevOpen)?(e.open+e.close)/2:(this._prevOpen+this._prevClose)/2,o=Math.max(e.high,i,t),r=Math.min(e.low,i,t);return this._prevOpen=i,this._prevClose=t,[{time:e.time,open:i,high:o,low:r,close:t,volume:e.volume}]}}class n{constructor(e){if(Object.defineProperty(this,"_box",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_edge",{enumerable:!0,configurable:!0,writable:!0,value:NaN}),e.boxSize<=0)throw new Error("openalgo-charts: Renko boxSize must be > 0");this._box=e.boxSize}reset(){this._edge=NaN}push(e){const t=e.close,i=[];if(Number.isNaN(this._edge))return this._edge=Math.floor(t/this._box)*this._box,i;for(;t>=this._edge+this._box;){const t=this._edge,o=this._edge+this._box;i.push({time:e.time,open:t,high:o,low:t,close:o}),this._edge=o}for(;t<=this._edge-this._box;){const t=this._edge,o=this._edge-this._box;i.push({time:e.time,open:t,high:t,low:o,close:o}),this._edge=o}return i}}class h{constructor(e){if(Object.defineProperty(this,"_range",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_cur",{enumerable:!0,configurable:!0,writable:!0,value:null}),e.range<=0)throw new Error("openalgo-charts: range must be > 0");this._range=e.range}reset(){this._cur=null}push(e){const t=e.close,i=[];return null===this._cur?this._cur={time:e.time,open:t,high:t,low:t,close:t}:(this._cur.high=Math.max(this._cur.high,t),this._cur.low=Math.min(this._cur.low,t),this._cur.close=t,this._cur.time=e.time),this._cur.high-this._cur.low>=this._range&&(i.push(this._cur),this._cur=null),i}flush(){if(null===this._cur)return[];const e=this._cur;return this._cur=null,[e]}}class l{constructor(e={lines:3}){Object.defineProperty(this,"_n",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_lines",{enumerable:!0,configurable:!0,writable:!0,value:[]}),this._n=Math.max(1,e.lines)}reset(){this._lines=[]}push(e){const t=e.close;if(0===this._lines.length)return this._lines.push({open:t,close:t}),[];const i=this._lines.slice(-this._n);let o=-1/0,r=1/0;for(const e of i)o=Math.max(o,e.open,e.close),r=Math.min(r,e.open,e.close);const s=this._lines[this._lines.length-1];let n=null;return(t>o||t<r)&&(n={open:s.close,close:t}),null===n?[]:(this._lines.push(n),[{time:e.time,open:n.open,high:Math.max(n.open,n.close),low:Math.min(n.open,n.close),close:n.close}])}}class a{constructor(e){Object.defineProperty(this,"_period",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_prevClose",{enumerable:!0,configurable:!0,writable:!0,value:NaN}),Object.defineProperty(this,"_sum",{enumerable:!0,configurable:!0,writable:!0,value:0}),Object.defineProperty(this,"_n",{enumerable:!0,configurable:!0,writable:!0,value:0}),Object.defineProperty(this,"_atr",{enumerable:!0,configurable:!0,writable:!0,value:NaN}),this._period=Math.max(1,Math.floor(e))}reset(){this._prevClose=NaN,this._sum=0,this._n=0,this._atr=NaN}push(e){const t=Number.isNaN(this._prevClose)?e.high-e.low:Math.max(e.high-e.low,Math.abs(e.high-this._prevClose),Math.abs(e.low-this._prevClose));this._prevClose=e.close,this._n<this._period?(this._sum+=t,this._n+=1,this._atr=this._sum/this._n):this._atr=(this._atr*(this._period-1)+t)/this._period}value(){return this._atr}}class u{constructor(e){Object.defineProperty(this,"_reversal",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_method",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_mode",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_fixedBox",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_percent",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_atrMult",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_atr",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_box",{enumerable:!0,configurable:!0,writable:!0,value:NaN}),Object.defineProperty(this,"_dir",{enumerable:!0,configurable:!0,writable:!0,value:0}),Object.defineProperty(this,"_top",{enumerable:!0,configurable:!0,writable:!0,value:NaN}),Object.defineProperty(this,"_bot",{enumerable:!0,configurable:!0,writable:!0,value:NaN}),Object.defineProperty(this,"_time",{enumerable:!0,configurable:!0,writable:!0,value:0});const t=e.mode??"fixed";if(this._mode=t,this._reversal=Math.max(1,Math.floor(e.reversal??3)),this._method=e.method??"hl",this._fixedBox=e.boxSize??NaN,this._percent=e.percent??NaN,this._atrMult=e.atrMultiplier??1,this._atr=new a(e.atrPeriod??14),"fixed"===t&&!(this._fixedBox>0))throw new Error("openalgo-charts: P&F boxSize must be > 0");if("percent"===t&&!(this._percent>0))throw new Error("openalgo-charts: P&F percent must be > 0")}get boxSize(){return this._fixedBox}reset(){this._box=NaN,this._dir=0,this._top=NaN,this._bot=NaN,this._time=0,this._atr.reset()}_resolveBox(e){let t=NaN;if(t="fixed"===this._mode?this._fixedBox:"percent"===this._mode?Math.abs(e)*this._percent/100:this._atr.value()*this._atrMult,t>0&&Number.isFinite(t))return t;if(this._box>0)return this._box;const i=.01*Math.abs(e);return i>0?i:1}_column(){const e=this._box,t=this._bot*e,i=(this._top+1)*e,o=this._dir>=0;return{time:this._time,open:o?t:i,close:o?i:t,high:i,low:t,boxSize:e,boxes:this._top-this._bot+1}}push(e){"atr"===this._mode&&this._atr.push(e);const t="hl"===this._method,i=t?e.high:e.close,o=t?e.low:e.close;if(Number.isNaN(this._top)){this._box=this._resolveBox(e.close);const t=Math.floor(e.close/this._box);return this._top=t,this._bot=t,this._dir=0,this._time=e.time,[]}const r=Math.floor(i/this._box),s=Math.floor(o/this._box);if(0===this._dir){const t=r-this._top,i=this._bot-s;return t<=0&&i<=0||(t>=i?(this._top=r,this._dir=1):(this._bot=s,this._dir=-1),this._time=e.time),[]}const n=[];if(this._dir>0){if(r>this._top)this._top=r;else if(this._top-s>=this._reversal){const t=this._top*this._box;n.push(this._column()),this._box=this._resolveBox(o),this._top=Math.ceil(t/this._box)-1,this._bot=Math.floor(o/this._box),this._dir=-1,this._time=e.time}}else if(s<this._bot)this._bot=s;else if(r-this._bot>=this._reversal){const t=(this._bot+1)*this._box;n.push(this._column()),this._box=this._resolveBox(i),this._bot=Math.floor(t/this._box),this._top=Math.floor(i/this._box),this._dir=1,this._time=e.time}return n}flush(){return 0===this._dir||Number.isNaN(this._top)?[]:[this._column()]}}function c(e,t,i){return{time:e,open:t,high:t,low:t,close:t,volume:i?1:0}}class _{constructor(e){if(Object.defineProperty(this,"_reversal",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_dir",{enumerable:!0,configurable:!0,writable:!0,value:0}),Object.defineProperty(this,"_ext",{enumerable:!0,configurable:!0,writable:!0,value:NaN}),Object.defineProperty(this,"_prevShoulder",{enumerable:!0,configurable:!0,writable:!0,value:-1/0}),Object.defineProperty(this,"_prevWaist",{enumerable:!0,configurable:!0,writable:!0,value:1/0}),Object.defineProperty(this,"_thick",{enumerable:!0,configurable:!0,writable:!0,value:!1}),e.reversal<=0)throw new Error("openalgo-charts: Kagi reversal must be > 0");this._reversal=e.reversal}reset(){this._dir=0,this._ext=NaN,this._prevShoulder=-1/0,this._prevWaist=1/0,this._thick=!1}push(e){const t=e.close;if(Number.isNaN(this._ext))return this._ext=t,[];const i=[];return this._dir>=0?t>this._ext?(this._ext=t,t>this._prevShoulder&&(this._thick=!0)):this._ext-t>=this._reversal&&(i.push(c(e.time,this._ext,this._thick)),this._prevShoulder=this._ext,this._dir=-1,this._ext=t):t<this._ext?(this._ext=t,t<this._prevWaist&&(this._thick=!1)):t-this._ext>=this._reversal&&(i.push(c(e.time,this._ext,this._thick)),this._prevWaist=this._ext,this._dir=1,this._ext=t),i}flush(){return Number.isNaN(this._ext)?[]:[c(0,this._ext,this._thick)]}}const b={abs:1,sqrt:1,ln:1,log:1,log10:1,exp:1,min:2,max:2,pow:2};class f extends Error{constructor(e,t){super(`openalgo-charts: ${e}`),Object.defineProperty(this,"position",{enumerable:!0,configurable:!0,writable:!0,value:t}),this.name="ExpressionError"}}const p=/[A-Za-z_]/,m=/[A-Za-z0-9_.:!#&]/;const d={"+":1,"-":1,"*":2,"/":2,"^":3};function v(e,t){let i=0;const o=()=>e[i];function r(){const e=o();if(void 0===e)throw new f("expression ended early",t.length);if("num"===e.t)return i++,{k:"num",v:e.v};if("sym"===e.t){i++;const r=e.v.toLowerCase();if(r in b&&"op"===o()?.t&&"("===o()?.v){i++;const s=[];if("op"!==o()?.t||")"!==o()?.v)for(;s.push(n(0)),"op"===o()?.t&&","===o()?.v;)i++;if("op"!==o()?.t||")"!==o()?.v)throw new f(`${r}( is not closed`,o()?.i??t.length);i++;const h=b[r];if(s.length!==h)throw new f(`${r}() takes ${h} argument${1===h?"":"s"}, got ${s.length}`,e.i);return{k:"fn",name:r,args:s}}return{k:"sym",name:e.v}}if("("===e.v){i++;const t=n(0);if("op"!==o()?.t||")"!==o()?.v)throw new f("( is not closed",e.i);return i++,t}if("-"===e.v)return i++,{k:"neg",a:s()};if("+"===e.v)return i++,s();throw new f(`unexpected "${e.v}"`,e.i)}function s(){return r()}function n(e){let t=s();for(;;){const r=o();if(void 0===r||"op"!==r.t||!(r.v in d))break;const s=d[r.v];if(s<e)break;i++;const h=n("^"===r.v?s:s+1);t={k:"bin",op:r.v,a:t,b:h}}return t}const h=n(0);if(i<e.length)throw new f(`unexpected "${e[i].v}"`,e[i].i);return h}function g(e,t){if("sym"!==e.k)if("neg"!==e.k){if("bin"===e.k)return g(e.a,t),void g(e.b,t);if("fn"===e.k)for(const i of e.args)g(i,t)}else g(e.a,t);else t.includes(e.name)||t.push(e.name)}function x(e){const t=function(e){const t=[];let i=0;for(;i<e.length;){const o=e[i];if(" "!==o&&"\t"!==o&&"\n"!==o&&"\r"!==o){if("'"===o||'"'===o){const r=e.indexOf(o,i+1);if(-1===r)throw new f(`unclosed ${o} in expression`,i);const s=e.slice(i+1,r);if(""===s)throw new f("empty quoted symbol",i);t.push({t:"sym",v:s,i:i}),i=r+1;continue}if(/[0-9]/.test(o)||"."===o&&/[0-9]/.test(e[i+1]??"")){let o=i;for(;o<e.length&&/[0-9.]/.test(e[o]);)o++;const r=e.slice(i,o),s=Number(r);if(!Number.isFinite(s))throw new f(`"${r}" is not a number`,i);t.push({t:"num",v:s,i:i}),i=o;continue}if(p.test(o)){let o=i;for(;o<e.length&&m.test(e[o]);)o++;let r=e.slice(i,o);for(;r.endsWith(":");)r=r.slice(0,-1),o--;t.push({t:"sym",v:r,i:i}),i=o;continue}if("+-*/^(),".includes(o))t.push({t:"op",v:o,i:i}),i++;else if("×"!==o)if("÷"!==o){if("−"!==o)throw new f(`unexpected "${o}" in expression`,i);t.push({t:"op",v:"-",i:i}),i++}else t.push({t:"op",v:"/",i:i}),i++;else t.push({t:"op",v:"*",i:i}),i++}else i++}return t}(e);if(0===t.length)throw new f("expression is empty",0);const i=v(t,e),o=[];if(g(i,o),0===o.length)throw new f("expression names no symbol",0);return{source:e,symbols:o,ast:i}}function w(e){try{return"sym"===x(e).ast.k}catch{return!1}}const N={lo:NaN,hi:NaN},M=e=>!Number.isFinite(e.lo)||!Number.isFinite(e.hi),y=e=>({lo:Math.min(...e),hi:Math.max(...e)});function P(e,t){if(t.lo!==t.hi)return N;const i=t.lo,o=[Math.pow(e.lo,i),Math.pow(e.hi,i)];return e.lo<0&&e.hi>0&&Number.isInteger(i)&&i%2==0&&o.push(0),y(o)}function O(e,t){return y([t(e.lo),t(e.hi)])}function j(e,t){switch(e.k){case"num":return{lo:e.v,hi:e.v};case"sym":return t(e.name)??N;case"neg":{const i=j(e.a,t);return M(i)?N:{lo:-i.hi,hi:-i.lo}}case"bin":{const i=j(e.a,t),o=j(e.b,t);if(M(i)||M(o))return N;switch(e.op){case"+":return{lo:i.lo+o.lo,hi:i.hi+o.hi};case"-":return{lo:i.lo-o.hi,hi:i.hi-o.lo};case"*":return function(e,t){return y([e.lo*t.lo,e.lo*t.hi,e.hi*t.lo,e.hi*t.hi])}(i,o);case"/":return function(e,t){return t.lo<=0&&t.hi>=0?N:y([e.lo/t.lo,e.lo/t.hi,e.hi/t.lo,e.hi/t.hi])}(i,o);case"^":return P(i,o)}return N}case"fn":{const i=e.args.map(e=>j(e,t));if(i.some(M))return N;const[o,r]=i;switch(e.name){case"abs":return function(e){return e.lo>=0?e:e.hi<=0?{lo:-e.hi,hi:-e.lo}:{lo:0,hi:Math.max(-e.lo,e.hi)}}(o);case"sqrt":return o.lo<0?N:O(o,Math.sqrt);case"ln":case"log":return o.lo<=0?N:O(o,Math.log);case"log10":return o.lo<=0?N:O(o,Math.log10);case"exp":return O(o,Math.exp);case"min":return{lo:Math.min(o.lo,r.lo),hi:Math.min(o.hi,r.hi)};case"max":return{lo:Math.max(o.lo,r.lo),hi:Math.max(o.hi,r.hi)};case"pow":return P(o,r)}return N}}}function k(e,t,i={}){const o="interval"===i.ohlc,r=i.primary??e.symbols[0],s=t[r];if(void 0===s||0===s.length)return[];const n=new Map;for(const i of e.symbols){if(i===r)continue;const e=t[i];if(void 0===e)return[];const o=new Map;for(const t of e)o.set(t.time,t);n.set(i,o)}const h=[],l=new Map;for(const t of s){l.clear(),l.set(r,t);let i=!0;for(const[e,o]of n){const r=o.get(t.time);if(void 0===r){i=!1;break}l.set(e,r)}if(!i)continue;const s=e=>t=>{const i=l.get(t);return void 0===i?null:{lo:i[e],hi:i[e]}},a=j(e.ast,s("open")),u=j(e.ast,s("close"));if(M(a)||M(u))continue;let c=Math.max(a.hi,u.hi),_=Math.min(a.lo,u.lo);if(o){const t=j(e.ast,e=>{const t=l.get(e);return void 0===t?null:{lo:t.low,hi:t.high}});if(M(t))continue;c=Math.max(c,t.hi),_=Math.min(_,t.lo)}h.push({time:t.time,open:a.lo,high:c,low:_,close:u.lo})}return h}const S="transform";let C=!1;function z(){C||(C=!0,e("point-figure",{defaultStyle:{},isPriceSeries:!0,draw:(e,t,o,r,s,n,h)=>i(e,t,o,r,s,n,h.plotHeight),extents:e=>({min:e.low,max:e.high})}),e("kagi",{defaultStyle:{thickColor:"#26a69a",thinColor:"#ef5350"},isPriceSeries:!0,draw:(e,t,i,o,r,s)=>function(e,t,i,o,r){if(t.length<2)return;const s=r.thickColor??"#26a69a",n=r.thinColor??"#ef5350",h=Math.max(2,Math.round(2.5*o)),l=Math.max(1,Math.round(o));e.save();for(let r=1;r<t.length;r++){const a=t[r-1],u=t[r],c=(u.bar.volume??0)>=1;e.strokeStyle=c?s:n,e.lineWidth=c?h:l;const _=a.x*o,b=u.x*o,f=i(a.bar.close)*o,p=i(u.bar.close)*o;e.beginPath(),e.moveTo(_,f),e.lineTo(b,f),e.lineTo(b,p),e.stroke()}e.restore()}(e,t,i,r,s),extents:e=>({min:e.close,max:e.close})}))}z();export{f as ExpressionError,s as HeikinAshiTransform,_ as KagiTransform,l as LineBreakTransform,u as PointFigureTransform,h as RangeBarsTransform,n as RenkoTransform,S as TRANSFORM_TIER,o as ensureIncreasingTimes,k as evaluateExpression,w as isPlainSymbol,x as parseExpression,z as registerTransformChartTypes,r as runTransform}; //# sourceMappingURL=openalgo-charts.transform.mjs.map