//+------------------------------------------------------------------+ //| TkzCodec.mqh | //| TKZ - context-mixing compression of tick history | //| (time_msc, bid, ask). Lossless, bit-exact round trip. | //| | //| One "chunk" = a self-contained compressed block of ticks: | //| 88-byte header + arithmetic-coded payload. See docs/FORMAT.md. | //+------------------------------------------------------------------+ #ifndef TKZ_CODEC_MQH #define TKZ_CODEC_MQH #define TKZ_CHUNK_MAGIC 0x315A4B54 // bytes "TKZ1" #define TKZ_HEADER_SIZE 88 #define TKZ_FORMAT_VERSION 1 #define TKZ_MODEL_VERSION 1 #define TKZ_MAXIN 12 // max contexts per decision #define TKZ_NW 13 // weights per mixer set (inputs + bias) #define TKZ_NCLASS 336 // decision classes #define TKZ_NSEL 5376 // TKZ_NCLASS * 16 mixer-1 weight sets #define TKZ_W2SETS 65536 // mixer-2 weight sets (hashed) #define TKZ_A2SETS 65536 // APM-2 contexts (hashed) #define TKZ_LIMIT 255 // adaptive counter limit #define TKZ_MAX_DIGITS 12 #define TKZ_POW10_SIZE 13 // TKZ_MAX_DIGITS + 1 // decision classes: groups of 80 classes for magnitudes #define TKZ_G_DT 0 #define TKZ_G_BMAG 1 #define TKZ_G_AMAG 2 #define TKZ_G_SMAG 3 #define TKZ_C_TYPE 320 #define TKZ_C_BRES 321 #define TKZ_C_ARES 322 #define TKZ_C_BSGNZ 323 #define TKZ_C_BSGN 324 #define TKZ_C_ASGNZ 325 #define TKZ_C_ASGN 326 #define TKZ_C_SSGNZ 327 #define TKZ_C_SSGN 328 #define TKZ_C_ESC 329 #define TKZ_C_RAW 330 #define TKZ_ESC_BID 1 #define TKZ_ESC_ASK 2 //--- bit cast helper union UTkzBits { double d; long l; }; //--- chunk header as seen by readers struct STkzChunkInfo { int tick_count; int payload_size; int total_size; // header + payload long first_msc; long last_msc; long min_msc; long max_msc; int digits; int modulus; int table_bits; int flags; uint checksum; long time_step; long price_step; long bid_base; long ask_base; }; //+------------------------------------------------------------------+ //| Codec | //+------------------------------------------------------------------+ class CTkzCodec { private: //--- model tables int m_tbits; uint m_tmask; uint m_t[]; // counters, blocks of 16 (slot 0 = check) int m_w1[]; // mixer 1 weights, selected by decision int m_w2[]; // mixer 2 weights, selected by hash int m_wf[]; // final mixer (2 inputs + bias) ushort m_a1[]; // APM 1 ushort m_a2[]; // APM 2 int m_rate[]; // 1024 counter adaptation rates int m_str[]; // 4096 entries: stretch(p) = ln(p/(1-p)) int m_sq[33]; int m_sqt[]; // 4095 entries: Squash(d) for d in [-2047, 2047] double m_pow10[TKZ_POW10_SIZE]; //--- current decision contexts int m_nctx; uint m_ctx[TKZ_MAXIN]; uint m_blk[TKZ_MAXIN]; uint m_ix[TKZ_MAXIN]; int m_x[TKZ_NW]; uint m_sel2; //--- binary arithmetic coder bool m_enc; uint m_x1; uint m_x2; uint m_xv; uchar m_buf[]; int m_bpos; int m_bend; int m_bcap; bool m_overrun; // decoder needed bytes past the payload //--- stream parameters long m_tstep; int m_mod; bool m_hasesc; //--- model state long m_pt; long m_pb; long m_pa; ulong m_dt1; ulong m_dt2; int m_k1; int m_k2; int m_ty1; int m_ty2; int m_ty3; long m_mb1; long m_mb2; long m_ma1; long m_ma2; long m_ms1; int m_emak; int m_emak2; int m_emab; long m_spavg16; //--- current tick (input when encoding, output when decoding) long m_ct; long m_cb; long m_ca; int m_cescb; int m_cesca; long m_crawb; long m_crawa; //--- work arrays long m_wb[]; long m_wa[]; uchar m_wesc[]; long m_it[]; double m_ib[]; double m_ia[]; //--- decoder output long m_ot[]; double m_obid[]; double m_oask[]; uchar m_oflg[]; int m_ocount; //--- statistics / diagnostics string m_error; //--- hashing uint H2(const uint a,const uint b) { uint h=(uint)(a*(uint)0x9E3779B1)^b; h^=h>>16; h=(uint)(h*(uint)0x85EBCA6B); h^=h>>13; h=(uint)(h*(uint)0xC2B2AE35); h^=h>>16; return h; } uint H3(const uint a,const uint b,const uint c) { return H2(H2(a,b),c); } uint H4(const uint a,const uint b,const uint c,const uint d) { return H2(H2(H2(a,b),c),d); } //--- small helpers int BitLen(ulong v) { int n=0; if((v>>32)!=0) { v>>=32; n+=32; } if((v>>16)!=0) { v>>=16; n+=16; } if((v>>8)!=0) { v>>=8; n+=8; } if((v>>4)!=0) { v>>=4; n+=4; } if((v>>2)!=0) { v>>=2; n+=2; } if((v>>1)!=0) { v>>=1; n+=1; } return n+(int)v; } long AbsL(const long v) { return v<0 ? -v : v; } long Gcd(long a,long b) { while(b!=0) { long r=a%b; a=b; b=r; } return a; } long FloorMod(const long a,const long m) { long r=a%m; if(r<0) r+=m; return r; } uint QLog(const long v) { if(v<0) return (uint)(64-BitLen((ulong)(-v))); return (uint)(64+BitLen((ulong)v)); } int QSigned(const long v) { if(v==0) return 15; long m=v<0 ? -v : v; int q=7; if(m<4) q=(int)m; else if(m<8) q=4; else if(m<16) q=5; else if(m<32) q=6; else q=7; return v<0 ? 15-q : 15+q; } void Split(const long p,long &q,int &r) { if(m_mod<=1) { q=p; r=0; return; } long rr=p%m_mod; if(rr<0) rr+=m_mod; r=(int)rr; q=(p-rr)/m_mod; } int Squash(const int d) { if(d>2047) return 4095; if(d<-2047) return 1; int w=d&127; int i=(d>>7)+16; return (m_sq[i]*(128-w)+m_sq[i+1]*w+64)>>7; } int Clamp2047(const long v) { if(v>2047) return 2047; if(v<-2047) return -2047; return (int)v; } long DoubleBits(const double v) { UTkzBits u; u.d=v; return u.l; } double BitsDouble(const long v) { UTkzBits u; u.l=v; return u.d; } uint TickHash(uint h,const long t,const long b,const long a) { h=H2(h^(uint)t,(uint)(t>>32)); h=H2(h^(uint)b,(uint)(b>>32)); h=H2(h^(uint)a,(uint)(a>>32)); return h; } //--- byte I/O (little endian) void PutU8(uchar &dst[],const int pos,const int v) { dst[pos]=(uchar)(v&255); } void PutU32(uchar &dst[],const int pos,const uint v) { dst[pos]=(uchar)(v&255); dst[pos+1]=(uchar)((v>>8)&255); dst[pos+2]=(uchar)((v>>16)&255); dst[pos+3]=(uchar)((v>>24)&255); } void PutI64(uchar &dst[],const int pos,const long v) { ulong u=(ulong)v; for(int i=0; i<8; i++) dst[pos+i]=(uchar)((u>>(8*i))&255); } uint GetU32(const uchar &src[],const int pos) { return (uint)src[pos]|((uint)src[pos+1]<<8)|((uint)src[pos+2]<<16)|((uint)src[pos+3]<<24); } long GetI64(const uchar &src[],const int pos) { ulong u=0; for(int i=7; i>=0; i--) u=(u<<8)|(ulong)src[pos+i]; return (long)u; } //--- model initialisation bool InitModel(const int tbits) { int i,j; m_tbits=tbits; int tsize=1<>4)-1); if(ArrayResize(m_w1,TKZ_NSEL*TKZ_NW)!=TKZ_NSEL*TKZ_NW || ArrayResize(m_w2,TKZ_W2SETS*TKZ_NW)!=TKZ_W2SETS*TKZ_NW || ArrayResize(m_wf,TKZ_NCLASS*3)!=TKZ_NCLASS*3 || ArrayResize(m_a1,TKZ_NSEL*33)!=TKZ_NSEL*33 || ArrayResize(m_a2,TKZ_A2SETS*33)!=TKZ_A2SETS*33) { m_error="out of memory (mixers)"; return false; } ArrayInitialize(m_w1,16384); ArrayInitialize(m_w2,16384); for(i=0; i=m_bcap) { m_bcap=m_bcap*2+65536; ArrayResize(m_buf,m_bcap); } m_buf[m_bpos]=c; m_bpos++; } uint GetByte(void) { if(m_bpos>8)|(uint)0x80000000; int b0=(int)(b<<4); int b1=(int)((b^1)<<4); int base; if(m_t[b0]==chk) base=b0; else if(m_t[b1]==chk) base=b1; else { uint n0=(m_t[b0]==0) ? (uint)0 : (m_t[b0+1]&1023); uint n1=(m_t[b1]==0) ? (uint)0 : (m_t[b1+1]&1023); base=(n0<=n1) ? b0 : b1; m_t[base]=chk; for(int i=1; i<16; i++) m_t[base+i]=(uint)0x80000000; } m_blk[j]=(uint)base; } void SetBlocks(const uint key) { for(int j=0; j>20)]; } m_x[nm]=256; int wb1=sel*TKZ_NW; int wb2=(int)(H2(m_sel2,(uint)sel)&0xFFFF)*TKZ_NW; long dot1=0; long dot2=0; for(j=0; j<=nm; j++) { dot1+=(long)m_x[j]*m_w1[wb1+j]; dot2+=(long)m_x[j]*m_w2[wb2+j]; } int d1=Clamp2047(dot1>>16); int d2=Clamp2047(dot2>>16); int wf=(sel>>4)*3; long df=(long)d1*m_wf[wf]+(long)d2*m_wf[wf+1]+(long)256*m_wf[wf+2]; int d=Clamp2047(df>>16); int pfin=m_sqt[d+2047]; //--- APM refinement int s=(d+2048)*32; int lo=s>>12; int wt=s&4095; int c1=sel*33+lo; int c2=(int)(apm2&0xFFFF)*33+lo; int p1=((int)m_a1[c1]*(4096-wt)+(int)m_a1[c1+1]*wt)>>16; int p2=((int)m_a2[c2]*(4096-wt)+(int)m_a2[c2+1]*wt)>>16; int pr=(pfin*2+p1+p2+2)>>2; if(pr<1) pr=1; if(pr>4095) pr=4095; //--- arithmetic coding, P(bit==1)=pr/4096 uint xmid=m_x1+(uint)(((ulong)(m_x2-m_x1)*(ulong)pr)>>12); if(m_enc) { if(bit!=0) m_x2=xmid; else m_x1=xmid+1; while(((m_x1^m_x2)>>24)==0) { PutByte((uchar)(m_x2>>24)); m_x1=m_x1<<8; m_x2=(m_x2<<8)|255; } } else { if(m_xv<=xmid) { bit=1; m_x2=xmid; } else { bit=0; m_x1=xmid+1; } while(((m_x1^m_x2)>>24)==0) { m_x1=m_x1<<8; m_x2=(m_x2<<8)|255; m_xv=(m_xv<<8)|GetByte(); } } //--- update mixers int e1=((bit<<12)-m_sqt[d1+2047])*8; int e2=((bit<<12)-m_sqt[d2+2047])*8; for(j=0; j<=nm; j++) { m_w1[wb1+j]+=(m_x[j]*e1)>>14; m_w2[wb2+j]+=(m_x[j]*e2)>>14; } int ef=((bit<<12)-pfin)*6; m_wf[wf]+=(d1*ef)>>14; m_wf[wf+1]+=(d2*ef)>>14; m_wf[wf+2]+=(256*ef)>>14; //--- update counters int target=(bit!=0) ? 4194303 : 0; for(j=0; j>10); p22+=(int)(((long)(target-p22)*m_rate[n])>>16); if(n>11); int i2=c2+(wt>>11); m_a1[i1]=(ushort)((int)m_a1[i1]+((g-(int)m_a1[i1])>>7)); m_a2[i2]=(ushort)((int)m_a2[i2]+((g-(int)m_a2[i2])>>7)); return bit; } //--- code nb (1..4) bits of v as a binary tree segment uint CodeSeg(const uint v,const int nb,const uint key,const int cls,const uint apmsel) { SetBlocks(key); uint node=1; for(int i=nb-1; i>=0; i--) { int bit=(int)((v>>i)&1); bit=CodeBit(bit,node,cls*16+(int)node,H2(apmsel,node)); node=node*2+(uint)bit; } return node-((uint)1<=30 ulong CodeMag(const ulong v,const int grp) { int base=grp*80; int k=0; if(m_enc) k=BitLen(v); uint u=CodeSeg((uint)(k<15 ? k : 15),4,0x100,base,H2((uint)base,1)); if(u==15) { uint u2=CodeSeg((uint)(k-15<15 ? k-15 : 15),4,0x200,base+1,H2((uint)base,2)); if(u2==15) { ulong r=0; for(int i=15; i>=0; i--) { uint nib=CodeSeg((uint)((v>>(i*4))&15),4,(uint)(0x300+i),base+2,H2((uint)base,3)); r=(r<<4)|(ulong)nib; } return r; } k=15+(int)u2; } else k=(int)u; if(k<=1) return (ulong)k; int nb=k-1; uint prefix=1; int si=0; int kq=k<15 ? k : 15; while(nb>0) { int take=nb>=4 ? 4 : nb; uint bits=(uint)((v>>(nb-take))&(ulong)((1<=0; i--) { uint nib=CodeSeg((uint)((v>>(i*4))&15),4,(uint)(0x300+i),TKZ_C_RAW,H2(98,(uint)i)); r=(r<<4)|(ulong)nib; } return r; } //--- code one tick (both directions share this path) void CodeTick(void) { //=== time long dtv=0; if(m_enc) dtv=(m_ct-m_pt)/m_tstep; int hour=(int)(((m_pt/3600000)%24+24)%24); uint dt1c=(m_dt1<(ulong)4096) ? (uint)m_dt1 : (uint)(4096+m_k1); uint dt2c=(m_dt2<(ulong)4096) ? (uint)m_dt2 : (uint)(4096+m_k2); long lastmv=((m_ty1&1)!=0) ? m_mb1 : m_ma1; m_sel2=H2((uint)(1000+m_k1),(uint)m_ty1); m_nctx=0; AddCtx(H2(1,0)); AddCtx(H2(2,(uint)m_k1)); AddCtx(H3(3,(uint)m_k1,(uint)m_k2)); AddCtx(H2(4,dt1c)); AddCtx(H3(5,(uint)m_ty1,(uint)m_k1)); AddCtx(H2(6,(uint)(m_emak>>2))); AddCtx(H3(7,(uint)m_k1,dt2c)); AddCtx(H3(8,(uint)(m_emak2>>3),(uint)hour)); AddCtx(H4(91,QLog(lastmv),(uint)m_ty1,(uint)m_k1)); dtv=(long)CodeMag((ulong)dtv,TKZ_G_DT); if(!m_enc) m_ct=m_pt+dtv*m_tstep; ulong m=(dtv<0) ? (ulong)0 : (ulong)dtv; int k=BitLen(m); //=== split current prices long qb=0,qa=0; int rb=0,ra=0; Split(m_pb,qb,rb); Split(m_pa,qa,ra); long sq=qa-qb; int qs=QSigned(sq*4-m_spavg16/4); int qsa=(sq<0) ? 0 : ((sq<30) ? (int)sq+1 : 31); uint mc=(m<(ulong)1024) ? (uint)m : (uint)(1024+k); //=== tick type: bid changed, ask changed, same move int cb=0,ca=0,same=0; if(m_enc) { cb=(m_cb!=m_pb) ? 1 : 0; ca=(m_ca!=m_pa) ? 1 : 0; if(cb==1 && ca==1 && (m_cb-m_pb)==(m_ca-m_pa)) same=1; } m_sel2=H3(2000,(uint)k,(uint)qs); m_nctx=0; AddCtx(H2(10,0)); AddCtx(H3(11,(uint)m_ty1,(uint)m_ty2)); AddCtx(H4(12,(uint)m_ty1,(uint)m_ty2,(uint)m_ty3)); AddCtx(H3(13,(uint)k,(uint)m_ty1)); AddCtx(H3(14,(uint)qs,(uint)m_ty1)); AddCtx(H3(15,mc,0)); AddCtx(H4(16,(uint)rb,(uint)ra,(uint)m_ty1)); AddCtx(H4(17,(uint)qsa,(uint)qs,(uint)k)); AddCtx(H4(18,(uint)m_ty1,(uint)((m_mb1<0 ? 2 : 0)+(m_ma1<0 ? 1 : 0)),(uint)qsa)); AddCtx(H4(19,(uint)m_ty1,(uint)m_ty2,(uint)qsa)); SetBlocks(0x77); uint tapm=(uint)(m_ty1*64+qs); cb=CodeBit(cb,1,TKZ_C_TYPE*16+1,H3(41,1,tapm)); uint node=(uint)(2+cb); ca=CodeBit(ca,node,TKZ_C_TYPE*16+(int)node,H3(41,node,tapm)); if(cb==1 && ca==1) same=CodeBit(same,7,TKZ_C_TYPE*16+7,H3(41,7,tapm)); else same=0; int ty=cb+ca*2+same; //=== escapes (only in chunks that contain irregular prices) int escb=0,esca=0; if(m_hasesc) { if(m_enc) { escb=m_cescb; esca=m_cesca; } escb=(cb==0) ? CodeFlag(escb,0) : 0; esca=(ca==0) ? CodeFlag(esca,1) : 0; if(escb!=0) m_crawb=(long)CodeRaw64((ulong)m_crawb,0); if(esca!=0) m_crawa=(long)CodeRaw64((ulong)m_crawa,1); } m_cescb=escb; m_cesca=esca; //=== new split states long nqb=qb,nqa=qa; int nrb=rb,nra=ra; long dqb=0,dqa=0; if(m_enc) { Split(m_cb,nqb,nrb); Split(m_ca,nqa,nra); dqb=nqb-qb; dqa=nqa-qa; } //=== bid move if(cb==1) { if(m_mod>1) { m_nctx=0; AddCtx(H3(20,(uint)rb,(uint)ty)); AddCtx(H4(21,(uint)rb,(uint)ra,(uint)ty)); AddCtx(H4(22,(uint)rb,(uint)ty,(uint)(m_mb1<0 ? 1 : 0))); AddCtx(H4(23,(uint)rb,(uint)m_ty1,(uint)ty)); nrb=(int)CodeSeg((uint)nrb,4,0x10,TKZ_C_BRES,H2(50,(uint)rb)); } m_sel2=H3((uint)(3000+ty),(uint)k,(uint)qs); uint rr=(uint)(nrb*16+rb); m_nctx=0; AddCtx(H3(30,(uint)ty,rr)); AddCtx(H4(31,(uint)ty,rr,QLog(m_mb1))); AddCtx(H4(32,(uint)ty,QLog(m_mb1),QLog(m_ma1))); AddCtx(H4(33,(uint)ty,(uint)qs,rr)); AddCtx(H4(34,(uint)ty,(uint)m_mb1,(uint)m_mb2)); AddCtx(H4(35,(uint)ty,(uint)qs,(uint)k)); AddCtx(H4(36,(uint)ty,(uint)(m_emab>>3),(uint)k)); AddCtx(H4(37,(uint)ty,(uint)qsa,(uint)m_ty1)); dqb=CodeSigned(dqb,(m_mod>1 && nrb!=rb),TKZ_C_BSGNZ,TKZ_C_BSGN,TKZ_G_BMAG); nqb=qb+dqb; if(!m_enc) m_cb=nqb*m_mod+nrb; } else { if(!m_enc) m_cb=m_pb; nqb=qb; nrb=rb; dqb=0; } //=== ask move if(ca==1 && same==0) { m_sel2=H3((uint)(4000+ty),(uint)k,(uint)qs); if(m_mod>1) { m_nctx=0; AddCtx(H3(60,(uint)ra,(uint)ty)); AddCtx(H4(61,(uint)ra,(uint)nrb,(uint)ty)); AddCtx(H4(62,(uint)ra,(uint)ty,(uint)(m_ma1<0 ? 1 : 0))); AddCtx(H4(63,(uint)ra,(uint)(rb*16+nrb),(uint)ty)); nra=(int)CodeSeg((uint)nra,4,0x10,TKZ_C_ARES,H2(70,(uint)ra)); } if(cb==0) { uint rr2=(uint)(nra*16+ra); m_nctx=0; AddCtx(H3(70,(uint)ty,rr2)); AddCtx(H4(71,(uint)ty,rr2,QLog(m_ma1))); AddCtx(H4(72,(uint)ty,QLog(m_ma1),QLog(m_mb1))); AddCtx(H4(73,(uint)ty,(uint)qs,rr2)); AddCtx(H4(74,(uint)ty,(uint)m_ma1,(uint)m_ma2)); AddCtx(H4(75,(uint)ty,(uint)qs,(uint)k)); AddCtx(H4(76,(uint)ty,(uint)(m_emab>>3),(uint)k)); AddCtx(H4(77,(uint)ty,(uint)qsa,(uint)m_ty1)); dqa=CodeSigned(dqa,(m_mod>1 && nra!=ra),TKZ_C_ASGNZ,TKZ_C_ASGN,TKZ_G_AMAG); } else { long dsq=dqa-dqb; bool zok=(m_mod>1 && (nra-ra)!=(nrb-rb)); uint r3=(uint)((nra*16+ra)*16+nrb); m_nctx=0; AddCtx(H3(80,(uint)ty,r3)); AddCtx(H4(81,(uint)ty,(uint)qs,QLog(dqb))); AddCtx(H4(82,(uint)ty,(uint)dqb,(uint)qs)); AddCtx(H4(83,(uint)ty,(uint)qsa,QLog(dqb))); AddCtx(H4(84,(uint)ty,(uint)m_ms1,(uint)qs)); AddCtx(H4(85,(uint)ty,(uint)qs,(uint)k)); AddCtx(H4(86,(uint)ty,(uint)dqb,r3)); AddCtx(H4(87,(uint)ty,(uint)qsa,(uint)m_ty1)); dsq=CodeSigned(dsq,zok,TKZ_C_SSGNZ,TKZ_C_SSGN,TKZ_G_SMAG); dqa=dqb+dsq; m_ms1=dsq; } nqa=qa+dqa; if(!m_enc) m_ca=nqa*m_mod+nra; } else if(same==1) { if(!m_enc) m_ca=m_pa+(m_cb-m_pb); Split(m_ca,nqa,nra); dqa=nqa-qa; } else { if(!m_enc) m_ca=m_pa; nqa=qa; nra=ra; dqa=0; } //=== state update m_dt2=m_dt1; m_dt1=m; m_k2=m_k1; m_k1=k; m_ty3=m_ty2; m_ty2=m_ty1; m_ty1=ty; if(cb==1) { m_mb2=m_mb1; m_mb1=dqb; } if(ca==1) { m_ma2=m_ma1; m_ma1=dqa; } m_emak+=(k*16-m_emak)>>3; m_emak2+=(k*16-m_emak2)>>6; if(cb==1) m_emab+=(BitLen((ulong)(dqb<0 ? -dqb : dqb))*16-m_emab)>>4; m_pt=m_ct; m_pb=m_cb; m_pa=m_ca; m_spavg16+=((nqa-nqb)*16-m_spavg16)/16; } //--- price -> integer in 10^-digits units, exact round trip required bool ToInt(const double p,const double scale,long &v) { if(!MathIsValidNumber(p)) return false; double x=p*scale; if(x>4.0e15 || x<-4.0e15) return false; v=(long)MathRound(x); double back=(double)v/scale; return DoubleBits(back)==DoubleBits(p); } int DetectDigits(const double &bid[],const double &ask[],const int start,const int n) { int best=-1; long bestfails=-1; for(int d=0; d<=TKZ_MAX_DIGITS; d++) { double scale=m_pow10[d]; long fails=0; long v=0; for(int i=0; i=0 && fails>=bestfails) break; } if(bestfails<0 || fails0) { double p=(double)cnt[r]/(double)total; h-=p*MathLog(p)/MathLog(2.0); } return (h<2.0) ? 10 : 1; } public: CTkzCodec(void) { int i; m_tbits=0; m_tmask=0; m_nctx=0; m_sel2=0; m_enc=true; m_x1=0; m_x2=0; m_xv=0; m_bpos=0; m_bend=0; m_bcap=0; m_overrun=false; m_tstep=1; m_mod=1; m_hasesc=false; m_ocount=0; m_error=""; m_ct=0; m_cb=0; m_ca=0; m_cescb=0; m_cesca=0; m_crawb=0; m_crawa=0; //--- logistic tables (integer only, platform independent) m_sq[0]=1; m_sq[1]=2; m_sq[2]=3; m_sq[3]=6; m_sq[4]=10; m_sq[5]=16; m_sq[6]=27; m_sq[7]=45; m_sq[8]=73; m_sq[9]=120; m_sq[10]=194; m_sq[11]=310; m_sq[12]=488; m_sq[13]=747; m_sq[14]=1101; m_sq[15]=1546; m_sq[16]=2047; m_sq[17]=2549; m_sq[18]=2994; m_sq[19]=3348; m_sq[20]=3607; m_sq[21]=3785; m_sq[22]=3901; m_sq[23]=3975; m_sq[24]=4022; m_sq[25]=4050; m_sq[26]=4068; m_sq[27]=4079; m_sq[28]=4085; m_sq[29]=4089; m_sq[30]=4092; m_sq[31]=4093; m_sq[32]=4094; ArrayResize(m_str,4096); ArrayResize(m_sqt,4095); ArrayResize(m_rate,1024); int pi=0; for(int x=-2047; x<=2047; x++) { int v=Squash(x); for(int jj=pi; jj<=v; jj++) m_str[jj]=x; pi=v+1; } for(i=pi; i<4096; i++) m_str[i]=2047; for(i=0; i<4095; i++) m_sqt[i]=Squash(i-2047); for(i=0; i<1024; i++) m_rate[i]=131072/(2*i+3); m_pow10[0]=1.0; for(i=1; i<=TKZ_MAX_DIGITS; i++) m_pow10[i]=m_pow10[i-1]*10.0; } ~CTkzCodec(void) { Release(); } //--- free model memory (tables are re-allocated on the next call) void Release(void) { ArrayFree(m_t); ArrayFree(m_w1); ArrayFree(m_w2); ArrayFree(m_wf); ArrayFree(m_a1); ArrayFree(m_a2); ArrayFree(m_buf); m_bcap=0; ArrayFree(m_wb); ArrayFree(m_wa); ArrayFree(m_wesc); ArrayFree(m_it); ArrayFree(m_ib); ArrayFree(m_ia); ArrayFree(m_ot); ArrayFree(m_obid); ArrayFree(m_oask); ArrayFree(m_oflg); } string LastError(void) { return m_error; } //--- bitwise equality of two doubles (distinguishes -0.0, NaN payloads) bool SameBits(const double x,const double y) { return DoubleBits(x)==DoubleBits(y); } //+---------------------------------------------------------------+ //| Encode ticks [start, start+count) from parallel arrays. | //| The chunk (header + payload) is appended to out[]. | //| Returns the chunk size in bytes or -1 on error. | //+---------------------------------------------------------------+ int Encode(const long &time_msc[],const double &bid[],const double &ask[], const int start,const int count,uchar &out[]) { m_error=""; int n=count; if(n<0 || start<0 || start+n>ArraySize(time_msc) || start+n>ArraySize(bid) || start+n>ArraySize(ask)) { m_error="bad arguments"; return -1; } int i; //--- 1. decimal digits that make prices exact integers int digits=DetectDigits(bid,ask,start,n); double scale=m_pow10[digits]; //--- 2. integer prices, irregular prices are escaped if(ArrayResize(m_wb,n+1)<0 || ArrayResize(m_wa,n+1)<0 || ArrayResize(m_wesc,n+1)<0) { m_error="out of memory"; return -1; } bool hasesc=false; long bbase=0,abase=0; bool bfound=false,afound=false; long v=0; for(i=0; i0) ? time_msc[start] : (long)0; ResetState(t0,(bbase-offb)/g,(abase-offa)/g); uint crc=0; long tmin=t0,tmax=t0; for(i=0; itmax) tmax=m_ct; m_cb=m_wb[i]; m_ca=m_wa[i]; m_cescb=((m_wesc[i]&TKZ_ESC_BID)!=0) ? 1 : 0; m_cesca=((m_wesc[i]&TKZ_ESC_ASK)!=0) ? 1 : 0; long bbits=DoubleBits(bid[start+i]); long abits=DoubleBits(ask[start+i]); m_crawb=bbits; m_crawa=abits; crc=TickHash(crc,m_ct,bbits,abits); CodeTick(); } //--- flush coder PutByte((uchar)(m_x1>>24)); PutByte((uchar)((m_x1>>16)&255)); PutByte((uchar)((m_x1>>8)&255)); PutByte((uchar)(m_x1&255)); int payload=m_bpos; //--- 7. header + payload int pos=ArraySize(out); int total=TKZ_HEADER_SIZE+payload; if(ArrayResize(out,pos+total)!=pos+total) { m_error="out of memory (output)"; return -1; } PutU32(out,pos,(uint)TKZ_CHUNK_MAGIC); PutU8(out,pos+4,TKZ_FORMAT_VERSION); PutU8(out,pos+5,TKZ_MODEL_VERSION); PutU8(out,pos+6,tbits); PutU8(out,pos+7,digits); PutU8(out,pos+8,mod); PutU8(out,pos+9,hasesc ? 1 : 0); PutU8(out,pos+10,0); PutU8(out,pos+11,0); PutU32(out,pos+12,(uint)n); PutU32(out,pos+16,(uint)payload); PutU32(out,pos+20,crc); PutI64(out,pos+24,t0); PutI64(out,pos+32,(n>0) ? time_msc[start+n-1] : (long)0); PutI64(out,pos+40,tg); PutI64(out,pos+48,g); PutI64(out,pos+56,bbase); PutI64(out,pos+64,abase); PutI64(out,pos+72,tmin); PutI64(out,pos+80,tmax); ArrayCopy(out,m_buf,pos+TKZ_HEADER_SIZE,0,payload); return total; } //--- encode from MqlTick array (only time_msc, bid and ask are stored) int EncodeTicks(const MqlTick &ticks[],const int start,const int count,uchar &out[]) { if(count<0 || start<0 || start+count>ArraySize(ticks)) { m_error="bad arguments"; return -1; } if(ArrayResize(m_it,count+1)<0 || ArrayResize(m_ib,count+1)<0 || ArrayResize(m_ia,count+1)<0) { m_error="out of memory"; return -1; } for(int i=0; iArraySize(src)) { m_error="truncated header"; return false; } if(GetU32(src,pos)!=(uint)TKZ_CHUNK_MAGIC) { m_error="bad chunk magic"; return false; } if(src[pos+4]!=TKZ_FORMAT_VERSION || src[pos+5]!=TKZ_MODEL_VERSION) { m_error="unsupported chunk version"; return false; } info.table_bits=src[pos+6]; info.digits=src[pos+7]; info.modulus=src[pos+8]; info.flags=src[pos+9]; uint cnt=GetU32(src,pos+12); uint psz=GetU32(src,pos+16); info.checksum=GetU32(src,pos+20); info.first_msc=GetI64(src,pos+24); info.last_msc=GetI64(src,pos+32); info.time_step=GetI64(src,pos+40); info.price_step=GetI64(src,pos+48); info.bid_base=GetI64(src,pos+56); info.ask_base=GetI64(src,pos+64); info.min_msc=GetI64(src,pos+72); info.max_msc=GetI64(src,pos+80); //--- every tick costs at least 6 decisions of >= 1/2840 bit, so a payload //--- byte can never hold more than ~3800 ticks: bound with a 2x margin if(cnt>(uint)0x7FFFFFF0 || psz>(uint)0x7FFFFFF0 || info.table_bits<16 || info.table_bits>24 || info.digits>TKZ_MAX_DIGITS || info.modulus<1 || info.modulus>16 || info.time_step<1 || info.price_step<1 || (long)cnt>((long)psz+16)*8192) { m_error="corrupt chunk header"; return false; } info.tick_count=(int)cnt; info.payload_size=(int)psz; info.total_size=TKZ_HEADER_SIZE+(int)psz; return true; } //+---------------------------------------------------------------+ //| Decode the chunk at src[pos] into the internal buffer. | //| Returns the tick count or -1 on error. | //+---------------------------------------------------------------+ int DecodeChunk(const uchar &src[],const int pos) { STkzChunkInfo info; m_ocount=0; if(!ReadInfo(src,pos,info)) return -1; if(pos+info.total_size>ArraySize(src)) { m_error="truncated chunk"; return -1; } int n=info.tick_count; if(ArrayResize(m_ot,n+1)<0 || ArrayResize(m_obid,n+1)<0 || ArrayResize(m_oask,n+1)<0 || ArrayResize(m_oflg,n+1)<0) { m_error="out of memory"; return -1; } if(!InitModel(info.table_bits)) return -1; //--- payload if(m_bcap0) ArrayCopy(m_buf,src,0,pos+TKZ_HEADER_SIZE,info.payload_size); m_bpos=0; m_bend=info.payload_size; m_overrun=false; m_enc=false; m_x1=0; m_x2=(uint)0xFFFFFFFF; m_xv=0; for(int b=0; b<4; b++) m_xv=(m_xv<<8)|GetByte(); //--- parameters m_tstep=info.time_step; m_mod=info.modulus; m_hasesc=(info.flags&1)!=0; long g=info.price_step; long offb=FloorMod(info.bid_base,g); long offa=FloorMod(info.ask_base,g); double scale=m_pow10[info.digits]; ResetState(info.first_msc,(info.bid_base-offb)/g,(info.ask_base-offa)/g); uint crc=0; double pbid=0.0,pask=0.0; for(int i=0; i0) { ArrayCopy(time_msc,m_ot,0,0,n); ArrayCopy(bid,m_obid,0,0,n); ArrayCopy(ask,m_oask,0,0,n); } return n; } //--- decode into MqlTick array; ticks are appended when append=true int DecodeTicks(const uchar &src[],const int pos,MqlTick &ticks[],const bool append=false) { int n=DecodeChunk(src,pos); if(n<0) return -1; int base=append ? ArraySize(ticks) : 0; if(ArrayResize(ticks,base+n)!=base+n) { m_error="out of memory"; return -1; } for(int i=0; i