-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathTriggerMatrixV2.cpp
More file actions
2524 lines (2390 loc) · 99 KB
/
Copy pathTriggerMatrixV2.cpp
File metadata and controls
2524 lines (2390 loc) · 99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "sierrachart.h"
SCDLLName("TriggerMatrixV2")
// =============================================================================
// TriggerMatrixV2.cpp
// Sierra Chart ACSIL Custom Study — Trigger Matrix V2 Self-Contained v2.4
//
// Self-contained study: computes ALL role-relevant data internally from the
// current chart native OHLC, bid/ask volume (SC_ASKVOL/SC_BIDVOL) and Volume
// at Price (sc.VolumeAtPriceForBars). No GetStudyArrayFromChartUsingID, no
// external-study selection. Evaluates the 16 ready primary V2 detector
// families from the pinned research catalog and publishes 32 stable
// primary Bull/Bear output subgraphs (SG0..SG31) plus HTML v2 withContext
// Long Trigger / T BUY / R BUY (SG32..SG37). One instance per source-chart
// role (Range / Renko 6t / Renko 8t); outputs of non-selected roles remain
// exactly zero.
//
// Canonical catalog: v2-formula-catalog.json
// version 2.0.0-research-2026-09-09
// SHA-256 ea562e4789cc16bae2f3529882a9832d1ba8d1a0dd9d95fbb02088f8f5a08618
// Display names match TriggerMatrix V1 / HTML sourceName. Predicates are
// HTML v2 formulas, not V1. withContext arms for ordinals 16/17/18 publish
// as Long Trigger / T BUY / R BUY on SG32..37 (cores stay byte-identical).
// Family-9 NYSE TICK overlay and blocked families (ordinals 3, 12, 13) are
// NOT implemented. Legacy TriggerMatrix.cpp predicates are unused here.
//
// INTERNAL PRODUCERS (v2.0, fixed canonical math, no inputs):
// Bar delta = native AskVol - BidVol per bar, valid only when classified
// volume is consistent with chart total volume (SC_VOLUME):
// positive chart volume with zero AV+BV reads as missing
// data, never as a valid zero delta.
// Bands = 20-period SMA +/- 0.9 * population SD over bar deltas;
// a band value is valid only with a complete 20-bar window
// of valid deltas (fail closed until ready).
// Per-bar VPOC from the bar's own VAP rows: max total-volume row; ties
// resolve to the row closest to the profile price midpoint
// ((minTick+maxTick)/2), equidistant ties take the lower row. This is
// the documented Sierra Volume-POC rule; Range VPOC A and Range VPOC B
// are the SAME documented no-input per-bar producer, so one internal
// calculation serves both (VpA == VpB by construction).
// Per-bar 68% value area from the same VAP rows: start at POC, expand one
// stored row at a time taking the greater-volume side, including BOTH
// rows on equal volumes (documented Sierra Volume-VA rule; expansion
// steps over consecutive stored rows, so sparse profiles pair across
// tick gaps — same consecutive-index iteration the installed Sierra
// diagonal-ratio reference uses). VVAH/VVAL are the included-edge row
// prices.
// Renko diagonal counts: per consecutive stored VAP-row pair (lower-row
// bid vs next-higher-row ask, gaps included per the installed Sierra
// reference) the SIGNED ROUNDED diagonal ratio (sc.Round half away
// from zero, so 299.5 -> 300 counts): ask-dominant ratio =
// round(askUp/bidLo*100), count ask when > 0 and >= +300%,
// bid-dominant = round(bidLo/askUp*-100), count bid when < 0 and
// <= -300% (meets-or-exceeds on the rounded value).
// Zero denominators are skipped (Sierra zero-compares disabled, the
// catalog ord14 pin), both rows need total volume >= 20 (catalog ord14
// pin), missing rows skip the pair (never a valid zero).
// Family-5 VAP rows: exact-tick VAP(L+k*t)/VAP(H-k*t) lookups, gated on
// actual sc.VolumeAtPriceMultiplier == 1.
// Completeness: every profile use reconciles stored-row volume against
// the bar's classified AV+BV — shortfalls mean partially published VAP
// (late data) and yield no POC/VA/count signals. No price-range
// coverage assumption: Renko OHLC endpoints are synthetic, not traded
// ticks.
//
// SUBGRAPHS (even=Bull below low, odd=Bear above high). Names = V1 / HTML
// sourceName. Formulas = HTML v2 (not TriggerMatrix.cpp).
// 0/1 Fading MOMO Below/Above (TEXT) ord 1 Range
// 2/3 Delta Rise / Delta Drop (ARROW) ord 2 Range
// 4/5 EXH+ / EXH- (TEXT) ord 4 Range
// 6/7 VOL SEQ Bull/Bear (SQUARE) ord 5 Range
// 8/9 VA Long / VA Short (TEXT) ord 6 Range
// 10/11 Slingshot Buy/Sell (TEXT) ord 7 Range
// 12/13 POC Delta Bull/Bear (TRIANGLE) ord 8 Range
// 14/15 MPOC+ / MPOC- (TEXT) ord 9 Range
// 16/17 Delta Trap Bull/Bear (TEXT) ord 10 Range
// 18/19 POCL Long / POCS Short (SQUARE) ord 11 Range
// 20/21 OF Long / OF Short (TRIANGLE) ord 14 Renko 6t
// 22/23 FA+ / FA- (TRIANGLE) ord 15 Renko 6t
// 24/25 Long/Short Trigger Core (TEXT) ord 16 Renko 8t
// 26/27 T BUY/T SELL Core (TEXT) ord 17 Renko 8t
// 28/29 R BUY/R SELL Core (TEXT) ord 18 Renko 8t
// 30/31 POC Wave Bull/Bear (TEXT) ord 19 Range
// 32/33 Long Trigger / Short Trigger (HTML withContext) ord 20 Renko 8t
// 34/35 T BUY / T SELL (HTML withContext) ord 21 Renko 8t
// 36/37 R BUY / R SELL (HTML withContext) ord 22 Renko 8t
// 38 Long Level 1 / 39 Long Level 2 (ARROW, V1 BLOCK Z)
// 40 Short Level 1 / 41 Short Level 2
// 50..56 hidden indicator workspace (DRAWSTYLE_IGNORE)
//
// Producer index convention (catalog pin only; no external studies):
// catalog Spreadsheet notation ID{...}.SGn is ONE-based; ACSIL
// SubgraphIndex is ZERO-based (index = n - 1): SG1->0, SG2->1, SG3->2,
// SG4->3, SG59->58. TMV2_SG_* constants keep those pins for tests.
// INPUTS (v2.1, V1-style: no study-ID pickers. REMOVE AND RE-ADD the
// study — Sierra saved settings are slot-indexed and the 11 retired
// source-ID slots plus opt-in/revision are gone):
// In:0 Chart Role (Range=0, Renko 6t=1, Renko 8t=2)
// In:1 Bull Base Offset (ticks, display only)
// In:2 Bear Base Offset (ticks, display only)
// In:3 Stack Step (ticks, display only)
// Family 5 (VGD) enables when sc.VolumeAtPriceMultiplier == 1.
//
// PERSISTENT SLOTS:
// Int 1 : late-VAP retry mark (-1 = none).
// Int 4,5 : 64-bit structural fingerprint (lo, hi).
//
// STATUS (honest): native_sierra_compile=false, sierra_runtime_parity=false
// until user-side Sierra F5 compile and on-chart parity pass.
// Cross-compile against real Sierra headers is build verification only.
// =============================================================================
#include <cmath>
#include <cstdio>
#include <cstring>
#include <new>
// ---------------------------------------------------------------------------
// Portable core: pure helpers shared by the ACSIL body and the Linux tests.
// No Sierra headers, no STL containers, no static mutable state.
// Indexing convention: offset 0 = current completed bar, k = k bars prior.
// ---------------------------------------------------------------------------
#define TMV2_VERSION "2.4-self-contained"
#define TMV2_CATALOG_VERSION "2.0.0-research-2026-09-09"
#define TMV2_CATALOG_SHA "ea562e4789cc16bae2f3529882a9832d1ba8d1a0dd9d95fbb02088f8f5a08618"
#define TMV2_SCHEMA_VERSION 6
#define TMV2_ROLE_RANGE 0
#define TMV2_ROLE_RENKO6 1
#define TMV2_ROLE_RENKO8 2
// Producer subgraph indices (test-visible, portable seam).
// Catalog Spreadsheet notation ID{...}.SGn is ONE-based; ACSIL
// SubgraphIndex is ZERO-based, so index = n - 1:
// SG1->0, SG2->1, SG3->2, SG4->3, SG59->58.
#define TMV2_SG_DELTA 3
#define TMV2_SG_BAND_UP 0
#define TMV2_SG_BAND_LO 2
#define TMV2_SG_VPOC 0
#define TMV2_SG_VVAH 0
#define TMV2_SG_VVAL 1
#define TMV2_SG_DIAG 58
#define TMV2_FNV_OFFSET_BASIS 14695981039346656037ULL
#define TMV2_N_PRIMARY 32
#define TMV2_N_OUT 38
#define TMV2_SG_LONG_LV1 38
#define TMV2_SG_LONG_LV2 39
#define TMV2_SG_SHORT_LV1 40
#define TMV2_SG_SHORT_LV2 41
#define TMV2_ORD_LTR 20
#define TMV2_ORD_TBY 21
#define TMV2_ORD_RBY 22
static int Tmv2_CatalogSgToIndex(int sgOneBased)
{
return sgOneBased - 1;
}
static unsigned long long Tmv2_FnvOffsetBasis()
{
return TMV2_FNV_OFFSET_BASIS;
}
struct Tmv2Window
{
double tick; // chart tick size; predicates require tick > 0
double O[6], H[6], L[6], C[6];
double AV[6], BV[6]; // native ask/bid volume per bar
double D[6]; int dOk[6]; // role-local bar delta (producer SG4)
double Up[6]; int upOk[6]; // role-local delta upper band (SG1)
double Lo[6]; int loOk[6]; // role-local delta lower band (SG3)
double VpA[6]; int vpaOk[6]; // Range VPOC instance A (SG1)
double VpB[6]; int vpbOk[6]; // Range VPOC instance B (SG1)
double VAH[6]; int vahOk[6]; // Range 68% VVAH (SG1)
double VAL[6]; int valOk[6]; // Range 68% VVAL (SG2)
double Vp6[2]; int vp6Ok[2]; // Renko 6t VPOC (SG1), [0]=cur [1]=prior
double Vp8[2]; int vp8Ok[2]; // Renko 8t VPOC (SG1)
double AskD[2]; int askOk[2];// Renko 6t ask-diagonal SG59 count
double BidD[2]; int bidOk[2];// Renko 6t bid-diagonal SG59 count
int vapMultIs1; // family-5 precondition flag
double vapTot[5]; // exact-tick VAP total volume rows
double vapSide[5]; // AVAP rows (bull) or BVAP rows (bear)
int vapOk[5]; // all five rows present with total > 0
// HTML v2 withContext indicators (Renko 8t). Offset 0 = current, 1 = prior.
double Macd[2]; int macdOk[2]; // MACD line (SG1); zero-line compare is vs 0
double Ema50[2]; double Ema200[2]; int emaOk[2];
double Adx[2]; int adxOk[2];
double Smi[2]; int smiOk[2];
double StochK[2]; double StochD[2]; int stochOk[2];
double Rsi[2]; int rsiOk[2];
double BbUp[2]; double BbLo[2]; int bbOk[2];
};
static void Tmv2_ClearWindow(Tmv2Window* w)
{
std::memset(w, 0, sizeof(*w));
}
static const char* Tmv2_CatalogVersion() { return TMV2_CATALOG_VERSION; }
static const char* Tmv2_CatalogSha() { return TMV2_CATALOG_SHA; }
static int Tmv2_ReadyFamilyCount() { return 16; }
static int Tmv2_OutputCount() { return TMV2_N_OUT; }
static const int* Tmv2_ReadyOrdinals()
{
static const int k[16] = {1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18, 19};
return k;
}
static int Tmv2_IsBlocked(int ord)
{
return (ord == 3 || ord == 12 || ord == 13) ? 1 : 0;
}
// Chart role of a catalog ordinal: 0 Range, 1 Renko 6t, 2 Renko 8t, -1 none.
static int Tmv2_FamilyRole(int ord)
{
switch (ord)
{
case 1: case 2: case 4: case 5: case 6: case 7:
case 8: case 9: case 10: case 11: case 19:
return TMV2_ROLE_RANGE;
case 14: case 15:
return TMV2_ROLE_RENKO6;
case 16: case 17: case 18:
case 20: case 21: case 22:
return TMV2_ROLE_RENKO8;
default:
return -1;
}
}
// Even (Bull) output SG for an ordinal, or -1 when absent.
static int Tmv2_FamilySgBull(int ord)
{
switch (ord)
{
case 1: return 0; case 2: return 2; case 4: return 4;
case 5: return 6; case 6: return 8; case 7: return 10;
case 8: return 12; case 9: return 14; case 10: return 16;
case 11: return 18; case 14: return 20; case 15: return 22;
case 16: return 24; case 17: return 26; case 18: return 28;
case 19: return 30;
case 20: return 32; case 21: return 34; case 22: return 36;
default: return -1;
}
}
static int Tmv2_FamilySgBear(int ord)
{
int b = Tmv2_FamilySgBull(ord);
return (b < 0) ? -1 : b + 1;
}
// Short family code for the structural summary log (e.g. ord 7 -> "DRP").
static const char* Tmv2_FamilyCode(int ord)
{
switch (ord)
{
case 1: return "OED";
case 2: return "DES";
case 4: return "EEF";
case 5: return "VGD";
case 6: return "WDM";
case 7: return "DRP";
case 8: return "PDR";
case 9: return "FVR";
case 10: return "ERM";
case 11: return "PBM";
case 14: return "OFR";
case 15: return "DVR";
case 16: return "VXC";
case 17: return "PEV";
case 18: return "R8F";
case 19: return "FPR";
case 20: return "LTR";
case 21: return "TBY";
case 22: return "RBY";
default: return "?";
}
}
// Self-contained structural disabled set: no external source studies.
// The only structural gate is family-5 (VGD) requiring actual chart
// VolumeAtPriceMultiplier == 1; when off, ordinal 5 is disabled under
// Range. Returns count and fills disabled[] in ascending catalog order.
static int Tmv2_SelfDisabled(int role, int vapGate, int disabled[16])
{
if (disabled == 0) return 0;
int n = 0;
if (role == TMV2_ROLE_RANGE && !vapGate) disabled[n++] = 5;
return n;
}
// Maximum direct formula footprint in bars (current + priors).
static int Tmv2_MaxDirectFootprint(int ord)
{
switch (ord)
{
case 5: return 1;
case 6: case 8: return 2;
case 4: case 9: case 16: case 18: case 19: return 3;
case 1: case 2: case 7: case 10: case 14: case 15: return 4;
case 11: return 5;
case 17: return 6;
case 20: return 3;
case 21: return 6;
case 22: return 3;
default: return -1;
}
}
static int Tmv2_IsFinite(double x)
{
return std::isfinite(x) ? 1 : 0;
}
// Flow primitive validity: AV/BV must be finite and non-negative.
// (Availability itself is enforced by the ACSIL layer via ok flags and
// array-size checks; zeros are structurally valid and left to the veto.)
static int Tmv2_FlowValid(double av, double bv)
{
if (!Tmv2_IsFinite(av) || !Tmv2_IsFinite(bv)) return 0;
if (av < 0.0 || bv < 0.0) return 0;
return 1;
}
static double Tmv2_Total(double av, double bv) { return av + bv; }
static double Tmv2_Norm(double av, double bv)
{
double t = av + bv;
double denom = (t > 1.0) ? t : 1.0; // MAX(1,AV+BV), mechanical
return (av - bv) / denom;
}
static int Tmv2_TickOk(const Tmv2Window* w)
{
return (w->tick > 0.0 && Tmv2_IsFinite(w->tick)) ? 1 : 0;
}
static int Tmv2_FlowAt(const Tmv2Window* w, int k)
{
return Tmv2_FlowValid(w->AV[k], w->BV[k]);
}
// Four-bar normalized least-squares slope, mechanical:
// (-3*n3 - n2 + n1 + 3*n0)/10 over offsets 3..0.
static double Tmv2_Slope4(const Tmv2Window* w)
{
double n0 = Tmv2_Norm(w->AV[0], w->BV[0]);
double n1 = Tmv2_Norm(w->AV[1], w->BV[1]);
double n2 = Tmv2_Norm(w->AV[2], w->BV[2]);
double n3 = Tmv2_Norm(w->AV[3], w->BV[3]);
return (-3.0 * n3 - n2 + n1 + 3.0 * n0) / 10.0;
}
// ---------------------------------------------------------------------------
// Self-contained internal producers (v2.0): delta bands, per-bar volume
// profile VPOC / 68% value area, diagonal ratio counts, exact-tick VAP rows.
// Pure helpers over plain arrays; the ACSIL body copies native chart data
// (OHLC, SC_ASKVOL/SC_BIDVOL, VolumeAtPrice rows) into these shapes once
// per evaluated bar. No Sierra headers, no heap, no static mutable state.
// Fixed canonical math (no inputs): bands 20-period SMA +/- 0.9 population
// SD; VPOC max-volume with Sierra midpoint/lower tie rule; VA 68% with
// Sierra greater-side/equal-includes-both expansion; diagonals +/-300%
// signed ratio with zero-denominator skip and 20-contract both-row minimum.
// ---------------------------------------------------------------------------
#define TMV2_BAND_LEN 20
#define TMV2_BAND_MULT 0.9
#define TMV2_VA_PCT 0.68
#define TMV2_DIAG_PCT 300.0
#define TMV2_DIAG_MIN_TOTAL 20.0
#define TMV2_MAX_VAP_ROWS 2048
struct Tmv2_VapRow
{
int tick; // PriceInTicks
double vol; // total volume at the row
double ask; // ask volume at the row
double bid; // bid volume at the row
};
static int Tmv2_TickSizeOk(double ts)
{
return (ts > 0.0 && Tmv2_IsFinite(ts)) ? 1 : 0;
}
// Guarded price-to-tick: rejects bad tick size, non-finite prices, and
// quotients outside int range with margin for the family-5 anchor steps
// (targetTick +/- 4 must not overflow).
static int Tmv2_PriceToTickSafe(double price, double tickSize, int* outTick)
{
if (outTick == 0) return 0;
if (!Tmv2_TickSizeOk(tickSize) || !Tmv2_IsFinite(price)) return 0;
double q = price / tickSize;
if (!Tmv2_IsFinite(q)) return 0;
double r = floor(q + 0.5);
const double kLim = 2147483640.0; // INT_MAX - 7, margin for +/-4 anchors
if (r > kLim || r < -kLim) return 0;
*outTick = (int)r;
return 1;
}
// sc.Round-compatible rounding (half away from zero): positive fractions
// >= 0.5 round up, negative fractions <= -0.5 round down (more negative).
// Verified against sierrachart.h sc.Round (truncation + half-away adjust).
static double Tmv2_RoundHalfAway(double x)
{
if (!Tmv2_IsFinite(x)) return x;
return (x >= 0.0) ? floor(x + 0.5) : ceil(x - 0.5);
}
// 20-period SMA +/- mult * population SD over d[end-19..end]. All 20 ok
// flags must be set and every sample finite; otherwise fail closed (0).
// Pure: production passes a small stack history copied from native deltas.
static int Tmv2_BandAt(const double* d, const int* ok, int n, int end,
double mult, double* up, double* lo)
{
if (d == 0 || ok == 0 || up == 0 || lo == 0) return 0;
if (n < TMV2_BAND_LEN || end < TMV2_BAND_LEN - 1 || end >= n) return 0;
if (!(mult >= 0.0) || !Tmv2_IsFinite(mult)) return 0;
double sum = 0.0;
for (int t = end - TMV2_BAND_LEN + 1; t <= end; t++)
{
if (!ok[t]) return 0;
if (!Tmv2_IsFinite(d[t])) return 0;
sum += d[t];
}
double mean = sum / (double)TMV2_BAND_LEN;
double var = 0.0;
for (int t = end - TMV2_BAND_LEN + 1; t <= end; t++)
{
double dev = d[t] - mean;
var += dev * dev;
}
var /= (double)TMV2_BAND_LEN; // population (catalog 20/0.9 SMA)
double sd = sqrt(var);
if (!Tmv2_IsFinite(sd)) return 0;
*up = mean + mult * sd;
*lo = mean - mult * sd;
return 1;
}
// Profile shape gate: 1..cap rows, strictly ascending ticks, finite and
// non-negative volumes. Anything else is malformed -> invalid profile.
static int Tmv2_VapRowsValid(const Tmv2_VapRow* rows, int n)
{
if (rows == 0 || n <= 0 || n > TMV2_MAX_VAP_ROWS) return 0;
for (int i = 0; i < n; i++)
{
if (!Tmv2_IsFinite(rows[i].vol) ||
!Tmv2_IsFinite(rows[i].ask) ||
!Tmv2_IsFinite(rows[i].bid)) return 0;
if (rows[i].vol < 0.0 || rows[i].ask < 0.0 || rows[i].bid < 0.0) return 0;
if (i > 0 && rows[i].tick <= rows[i - 1].tick) return 0;
}
return 1;
}
// Per-bar VPOC: max total-volume row. Ties resolve to the row closest to
// the profile price midpoint ((minTick+maxTick)/2); equidistant ties take
// the lower row. Documented Sierra Volume-POC rule. All-zero profiles have
// no POC (invalid, never a valid zero).
static int Tmv2_ProfilePoc(const Tmv2_VapRow* rows, int n, int* pocIdx)
{
if (pocIdx == 0 || !Tmv2_VapRowsValid(rows, n)) return 0;
int best = 0;
double mid = ((double)rows[0].tick + (double)rows[n - 1].tick) / 2.0;
for (int i = 1; i < n; i++)
{
if (rows[i].vol > rows[best].vol) { best = i; continue; }
if (rows[i].vol < rows[best].vol) continue;
double di = fabs((double)rows[i].tick - mid);
double db = fabs((double)rows[best].tick - mid);
if (di < db || (di == db && rows[i].tick < rows[best].tick)) best = i;
}
if (!(rows[best].vol > 0.0)) return 0;
*pocIdx = best;
return 1;
}
// Per-bar value area: start at POC, expand one row at a time taking the
// greater-volume side; equal volumes include BOTH rows then continue one
// row out on each side. Stop once included volume reaches pct of profile
// total. Documented Sierra Volume-VA rule. pct is 0.68 (catalog VA68 pin).
static int Tmv2_ProfileVa(const Tmv2_VapRow* rows, int n, int pocIdx,
double pct, int* hiTick, int* loTick)
{
if (hiTick == 0 || loTick == 0) return 0;
if (!Tmv2_VapRowsValid(rows, n)) return 0;
if (pocIdx < 0 || pocIdx >= n) return 0;
if (!(pct > 0.0) || !(pct <= 1.0) || !Tmv2_IsFinite(pct)) return 0;
if (!(rows[pocIdx].vol > 0.0)) return 0;
double total = 0.0;
for (int i = 0; i < n; i++) total += rows[i].vol;
if (!(total > 0.0) || !Tmv2_IsFinite(total)) return 0;
double target = total * pct;
double incl = rows[pocIdx].vol;
int hi = pocIdx, lo = pocIdx;
int up = pocIdx + 1, dn = pocIdx - 1;
while (incl < target)
{
int hasUp = (up < n) ? 1 : 0;
int hasDn = (dn >= 0) ? 1 : 0;
if (!hasUp && !hasDn) break;
if (hasUp && hasDn)
{
if (rows[up].vol > rows[dn].vol) { incl += rows[up].vol; hi = up; up++; }
else if (rows[dn].vol > rows[up].vol) { incl += rows[dn].vol; lo = dn; dn--; }
else { incl += rows[up].vol + rows[dn].vol; hi = up; lo = dn; up++; dn--; }
}
else if (hasUp) { incl += rows[up].vol; hi = up; up++; }
else { incl += rows[dn].vol; lo = dn; dn--; }
}
*hiTick = rows[hi].tick;
*loTick = rows[lo].tick;
return 1;
}
// Diagonal qualifying-level counts over consecutive stored-row pairs:
// lower-row bid vs next-higher-row ask. This matches the installed Sierra
// reference (studies8 diagonal-ratio branch iterates PriceIndex and
// PriceIndex+1 over stored VAP elements, gaps included). Ask-dominant
// (askUp >= bidLo): ratio = sc.Round(askUp/bidLo*100), counts when > 0 and
// >= +pctThr. Bid-dominant: ratio = sc.Round(bidLo/askUp*-100), counts when
// < 0 and <= -pctThr (Sierra compares the ROUNDED value, so 299.5 -> 300
// counts). Zero denominators are skipped (Sierra zero-compares disabled =
// catalog ord14 pin, hard-coded); both rows need total volume >= minTotal
// (catalog ord14 20-contract pin); other pairs skip silently.
// Missing/whole-invalid profile -> invalid (never valid 0).
static int Tmv2_DiagCounts(const Tmv2_VapRow* rows, int n, double pctThr,
double minTotal, int* askCount, int* bidCount)
{
if (askCount == 0 || bidCount == 0) return 0;
if (!Tmv2_VapRowsValid(rows, n)) return 0;
if (!(pctThr > 0.0) || !Tmv2_IsFinite(pctThr)) return 0;
if (!(minTotal >= 0.0) || !Tmv2_IsFinite(minTotal)) return 0;
int ask = 0, bid = 0;
for (int i = 0; i + 1 < n; i++)
{
double bidLo = rows[i].bid;
double askUp = rows[i + 1].ask;
if (!(rows[i].vol >= minTotal)) continue;
if (!(rows[i + 1].vol >= minTotal)) continue;
if (!(bidLo > 0.0) || !(askUp > 0.0)) continue;
if (askUp >= bidLo)
{
double ratio = Tmv2_RoundHalfAway(askUp / bidLo * 100.0);
if (Tmv2_IsFinite(ratio) && ratio > 0.0 && ratio >= pctThr) ask++;
}
else
{
double ratio = Tmv2_RoundHalfAway(bidLo / askUp * -100.0);
if (Tmv2_IsFinite(ratio) && ratio < 0.0 && ratio <= -pctThr) bid++;
}
}
*askCount = ask;
*bidCount = bid;
return 1;
}
// Exact-tick VAP row lookup for family 5: row at targetTick must exist with
// positive total; no nearest-row substitution. bullSide=1 reads ask,
// bullSide=0 reads bid.
static int Tmv2_VapSideRow(const Tmv2_VapRow* rows, int n, int targetTick,
double* tot, double* side, int bullSide)
{
if (tot == 0 || side == 0) return 0;
if (!Tmv2_VapRowsValid(rows, n)) return 0;
for (int i = 0; i < n; i++)
{
if (rows[i].tick == targetTick)
{
if (!(rows[i].vol > 0.0)) return 0;
*tot = rows[i].vol;
*side = bullSide ? rows[i].ask : rows[i].bid;
if (!Tmv2_IsFinite(*side) || *side < 0.0) return 0;
return 1;
}
}
return 0;
}
// VAP-vs-chart volume reconciliation: the stored rows must cover the bar's
// classified bid/ask volume. A shortfall means partially published VAP
// (late data) and the profile must not yield POC/VA/count signals. Excess
// stored volume (unclassified trades) is allowed. A zero chart total with
// stored volume (or vice versa) is inconsistent -> invalid. Precondition:
// rows are shape-valid (Tmv2_VapRowsValid); only the totals reconcile here.
// Note: no price-range coverage check — Renko OHLC endpoints are synthetic
// and must not be assumed to be traded ticks.
static int Tmv2_ProfileVolumeOk(const Tmv2_VapRow* rows, int n,
double av, double bv)
{
if (rows == 0 || n <= 0) return 0;
if (!Tmv2_FlowValid(av, bv)) return 0;
double chartTotal = av + bv;
if (!(chartTotal > 0.0)) return 0;
double vapSum = 0.0;
for (int i = 0; i < n; i++) vapSum += rows[i].vol;
if (!Tmv2_IsFinite(vapSum)) return 0;
if (vapSum < chartTotal - 0.001) return 0; // partial publication
return 1;
}
// Native delta availability: flow must be valid AND classified volume must
// be present when the chart reports traded total volume. A positive chart
// total (SC_VOLUME) with zero AV+BV means missing bid/ask history, which
// must not read as a valid zero delta. Unknown totals (no volume array)
// fall back to flow validity.
static int Tmv2_DeltaOk(double av, double bv, double totalVol, int hasVol)
{
if (!Tmv2_FlowValid(av, bv)) return 0;
if (hasVol)
{
if (!Tmv2_IsFinite(totalVol) || totalVol < 0.0) return 1;
if (totalVol > 0.0 && (av + bv) <= 0.0) return 0;
}
return 1;
}
// Depth of native history consumed per evaluated bar (covers the 20-bar
// band window plus the 5-bar formula footprint with margin).
#define TMV2_NATIVE_DEPTH 31
// Late-VAP retry bound: unresolved profiles older than this many bars
// behind the edge are forgotten (genuine history corrections arrive via
// rewound UpdateStartIndex and are honored fully).
#define TMV2_RETRY_LOOKBACK 128
// Update planner (production-called): derives [first, lastClosed] from the
// chart size, Sierra's UpdateStartIndex, rebuild flags, and the stored
// late-VAP retry mark. Returns 1 when at least the edge bar needs work.
// Tiny charts (ArraySize < 2) report no work; the caller still zeroes the
// forming bar.
static int Tmv2_PlanUpdate(int arraySize, int updateStartIndex,
int isFullRecalc, int fingerprintChanged,
int storedRetry, int* first, int* lastClosed)
{
if (first == 0 || lastClosed == 0) return 0;
int lc = arraySize - 2;
*lastClosed = lc;
*first = 0;
if (lc < 0) return 0;
int f = (isFullRecalc || updateStartIndex <= 0 || fingerprintChanged)
? 0 : updateStartIndex - 32;
if (f < 0) f = 0;
if (storedRetry >= 0 && storedRetry <= lc && storedRetry < f) f = storedRetry;
if (f > lc) f = lc;
*first = f;
return 1;
}
// Native window fill (production-called): OHLC/flow/delta plus internal
// 20/0.9 bands from plain histories. Arrays hold n entries with the
// current bar at index end; TV/hasVol carry chart total volume (SC_VOLUME)
// or NULL/0 when unavailable. Caller clears the window first; profile
// fields are left for Tmv2_FillProfileOffset.
static void Tmv2_FillNative(Tmv2Window* w,
const double* O, const double* H,
const double* L, const double* C,
const double* AV, const double* BV,
const double* TV, int hasVol,
int n, int end, double tick)
{
Tmv2_ClearWindow(w);
w->tick = tick;
if (O == 0 || H == 0 || L == 0 || C == 0 || AV == 0 || BV == 0) return;
if (n <= 0 || end < 0 || end >= n) return;
for (int k = 0; k < 6; k++)
{
int j = end - k;
if (j < 0) break;
w->O[k] = O[j]; w->H[k] = H[j]; w->L[k] = L[j]; w->C[k] = C[j];
w->AV[k] = AV[j]; w->BV[k] = BV[j];
w->D[k] = AV[j] - BV[j];
w->dOk[k] = Tmv2_DeltaOk(AV[j], BV[j], (hasVol && TV != 0) ? TV[j] : 0.0, hasVol);
}
double dh[25];
int okh[25];
for (int t = 0; t < 25; t++)
{
int j = end - 24 + t;
if (j < 0 || j >= n)
{
okh[t] = 0; dh[t] = 0.0;
}
else if (!Tmv2_DeltaOk(AV[j], BV[j], (hasVol && TV != 0) ? TV[j] : 0.0, hasVol))
{
okh[t] = 0; dh[t] = 0.0;
}
else
{
okh[t] = 1; dh[t] = AV[j] - BV[j];
}
}
for (int k = 0; k < 6; k++)
{
if (Tmv2_BandAt(dh, okh, 25, 24 - k, TMV2_BAND_MULT, &w->Up[k], &w->Lo[k]))
{
w->upOk[k] = 1;
w->loOk[k] = 1;
}
}
}
// Profile offset fill (production-called): reconciles, then computes POC,
// 68% VA and diagonal counts for lag offset k (0..4) into the window.
// Range VPOC A and B share the one calculation. Clears the offset on any
// failure. Returns 1 when the offset profile resolved (retry tracking).
static int Tmv2_FillProfileOffset(Tmv2Window* w, const Tmv2_VapRow* rows,
int n, double av, double bv,
int k, double tickSize)
{
if (w == 0 || k < 0 || k > 4) return 0;
w->VpA[k] = 0.0; w->vpaOk[k] = 0;
if (k < 3) { w->VpB[k] = 0.0; w->vpbOk[k] = 0; }
if (k < 2)
{
w->VAH[k] = 0.0; w->vahOk[k] = 0;
w->VAL[k] = 0.0; w->valOk[k] = 0;
w->Vp6[k] = 0.0; w->vp6Ok[k] = 0;
w->Vp8[k] = 0.0; w->vp8Ok[k] = 0;
w->AskD[k] = 0.0; w->askOk[k] = 0;
w->BidD[k] = 0.0; w->bidOk[k] = 0;
}
if (!Tmv2_TickSizeOk(tickSize)) return 0;
if (!Tmv2_VapRowsValid(rows, n)) return 0;
if (!Tmv2_ProfileVolumeOk(rows, n, av, bv)) return 0;
int pi = -1;
if (!Tmv2_ProfilePoc(rows, n, &pi)) return 0;
// Row price = tick * tick size (== sc.TicksToPriceValue semantics).
double pocP = (double)rows[pi].tick * tickSize;
w->VpA[k] = pocP; w->vpaOk[k] = 1;
if (k < 3) { w->VpB[k] = pocP; w->vpbOk[k] = 1; }
if (k < 2)
{
int hiT = 0, loT = 0;
if (Tmv2_ProfileVa(rows, n, pi, TMV2_VA_PCT, &hiT, &loT))
{
w->VAH[k] = (double)hiT * tickSize; w->vahOk[k] = 1;
w->VAL[k] = (double)loT * tickSize; w->valOk[k] = 1;
}
w->Vp6[k] = pocP; w->vp6Ok[k] = 1;
w->Vp8[k] = pocP; w->vp8Ok[k] = 1;
int aq = 0, bq = 0;
if (Tmv2_DiagCounts(rows, n, TMV2_DIAG_PCT, TMV2_DIAG_MIN_TOTAL, &aq, &bq))
{
w->AskD[k] = (double)aq; w->askOk[k] = 1;
w->BidD[k] = (double)bq; w->bidOk[k] = 1;
}
}
return 1;
}
// Family-5 side fill (production-called): exact-tick AVAP rows at
// lowTick+k (bull) and BVAP rows at highTick-k (bear), all-or-nothing per
// side after volume reconciliation. vapGate=0 leaves both sides cleared.
// Returns 1 when both sides resolved.
static int Tmv2_FillVapSides(Tmv2Window* wBull, Tmv2Window* wBear,
const Tmv2_VapRow* rows, int n,
double av, double bv, int vapGate,
int lowTick, int highTick)
{
if (wBull == 0 || wBear == 0) return 0;
if (!vapGate) return 0;
if (!Tmv2_VapRowsValid(rows, n)) return 0;
if (!Tmv2_ProfileVolumeOk(rows, n, av, bv)) return 0;
int okB = 1, okR = 1;
double totB[5], sdB[5], totR[5], sdR[5];
for (int k = 0; k < 5; k++)
{
double tot = 0.0, sd = 0.0;
if (!Tmv2_VapSideRow(rows, n, lowTick + k, &tot, &sd, 1)) okB = 0;
else { totB[k] = tot; sdB[k] = sd; }
if (!Tmv2_VapSideRow(rows, n, highTick - k, &tot, &sd, 0)) okR = 0;
else { totR[k] = tot; sdR[k] = sd; }
}
if (okB) for (int k = 0; k < 5; k++)
{
wBull->vapTot[k] = totB[k]; wBull->vapSide[k] = sdB[k]; wBull->vapOk[k] = 1;
}
if (okR) for (int k = 0; k < 5; k++)
{
wBear->vapTot[k] = totR[k]; wBear->vapSide[k] = sdR[k]; wBear->vapOk[k] = 1;
}
return (okB && okR) ? 1 : 0;
}
// ---- Ordinal 1: Opposing Effort Decay v2 (Range, 4 bars) ----
static int Tmv2_F01Bull(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
for (int k = 0; k < 4; k++)
{
if (!Tmv2_FlowAt(w, k)) return 0;
if (Tmv2_Total(w->AV[k], w->BV[k]) < 20.0) return 0;
}
double n0 = Tmv2_Norm(w->AV[0], w->BV[0]);
double n1 = Tmv2_Norm(w->AV[1], w->BV[1]);
double n2 = Tmv2_Norm(w->AV[2], w->BV[2]);
double n3 = Tmv2_Norm(w->AV[3], w->BV[3]);
if (!(n0 < 0.0 && n1 < 0.0 && n2 < 0.0 && n3 < 0.0)) return 0;
if (!(Tmv2_Slope4(w) >= 0.05)) return 0;
if (!((n0 - n3) >= 0.20)) return 0;
if (!(w->H[0] > w->L[0])) return 0;
if (!(w->C[0] >= w->C[3] - 2.0 * w->tick)) return 0;
return 1;
}
static int Tmv2_F01Bear(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
for (int k = 0; k < 4; k++)
{
if (!Tmv2_FlowAt(w, k)) return 0;
if (Tmv2_Total(w->AV[k], w->BV[k]) < 20.0) return 0;
}
double n0 = Tmv2_Norm(w->AV[0], w->BV[0]);
double n1 = Tmv2_Norm(w->AV[1], w->BV[1]);
double n2 = Tmv2_Norm(w->AV[2], w->BV[2]);
double n3 = Tmv2_Norm(w->AV[3], w->BV[3]);
if (!(n0 > 0.0 && n1 > 0.0 && n2 > 0.0 && n3 > 0.0)) return 0;
if (!(Tmv2_Slope4(w) <= -0.05)) return 0;
if (!((n3 - n0) >= 0.20)) return 0;
if (!(w->H[0] > w->L[0])) return 0;
if (!(w->C[0] <= w->C[3] + 2.0 * w->tick)) return 0;
return 1;
}
// ---- Ordinal 2: Directional Effort Slope Response v2 (Range, 4 bars) ----
static int Tmv2_F02Bull(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
for (int k = 0; k < 4; k++)
{
if (!Tmv2_FlowAt(w, k)) return 0;
if (Tmv2_Total(w->AV[k], w->BV[k]) < 20.0) return 0;
}
double n0 = Tmv2_Norm(w->AV[0], w->BV[0]);
double n1 = Tmv2_Norm(w->AV[1], w->BV[1]);
double n2 = Tmv2_Norm(w->AV[2], w->BV[2]);
double n3 = Tmv2_Norm(w->AV[3], w->BV[3]);
if (!(n0 >= 0.10)) return 0;
if (!(Tmv2_Slope4(w) >= 0.05)) return 0;
if (!((n0 - n3) >= 0.25)) return 0;
int steps = ((n2 > n3 && n1 > n2) || (n2 > n3 && n0 > n1) || (n1 > n2 && n0 > n1)) ? 1 : 0;
if (!steps) return 0;
if (!(w->H[0] > w->L[0])) return 0;
if (!(w->C[0] >= w->C[3] + 2.0 * w->tick)) return 0;
if (!(w->C[0] >= w->H[0] - 0.25 * (w->H[0] - w->L[0]))) return 0;
return 1;
}
static int Tmv2_F02Bear(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
for (int k = 0; k < 4; k++)
{
if (!Tmv2_FlowAt(w, k)) return 0;
if (Tmv2_Total(w->AV[k], w->BV[k]) < 20.0) return 0;
}
double n0 = Tmv2_Norm(w->AV[0], w->BV[0]);
double n1 = Tmv2_Norm(w->AV[1], w->BV[1]);
double n2 = Tmv2_Norm(w->AV[2], w->BV[2]);
double n3 = Tmv2_Norm(w->AV[3], w->BV[3]);
if (!(n0 <= -0.10)) return 0;
if (!(Tmv2_Slope4(w) <= -0.05)) return 0;
if (!((n3 - n0) >= 0.25)) return 0;
int steps = ((n2 < n3 && n1 < n2) || (n2 < n3 && n0 < n1) || (n1 < n2 && n0 < n1)) ? 1 : 0;
if (!steps) return 0;
if (!(w->H[0] > w->L[0])) return 0;
if (!(w->C[0] <= w->C[3] - 2.0 * w->tick)) return 0;
if (!(w->C[0] <= w->L[0] + 0.25 * (w->H[0] - w->L[0]))) return 0;
return 1;
}
// ---- Ordinal 4: Extreme Effort Failure (Range, 3 bars) ----
static int Tmv2_F04Bull(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
if (!Tmv2_FlowAt(w, 0) || !Tmv2_FlowAt(w, 1) || !Tmv2_FlowAt(w, 2)) return 0;
double t0 = Tmv2_Total(w->AV[0], w->BV[0]);
double t1 = Tmv2_Total(w->AV[1], w->BV[1]);
double t2 = Tmv2_Total(w->AV[2], w->BV[2]);
if (!(t0 >= 20.0)) return 0;
if (!(t1 > 0.0 && t2 > 0.0)) return 0;
if (!(w->BV[0] >= 1.5 * w->AV[0])) return 0;
if (!(t0 >= 1.25 * ((t1 + t2) / 2.0))) return 0;
if (!(w->H[0] > w->L[0])) return 0;
if (!(w->L[0] <= w->L[1] - w->tick)) return 0;
if (!(w->C[0] >= w->L[1] + w->tick)) return 0;
if (!(w->C[0] >= w->H[0] - 0.25 * (w->H[0] - w->L[0]))) return 0;
return 1;
}
static int Tmv2_F04Bear(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
if (!Tmv2_FlowAt(w, 0) || !Tmv2_FlowAt(w, 1) || !Tmv2_FlowAt(w, 2)) return 0;
double t0 = Tmv2_Total(w->AV[0], w->BV[0]);
double t1 = Tmv2_Total(w->AV[1], w->BV[1]);
double t2 = Tmv2_Total(w->AV[2], w->BV[2]);
if (!(t0 >= 20.0)) return 0;
if (!(t1 > 0.0 && t2 > 0.0)) return 0;
if (!(w->AV[0] >= 1.5 * w->BV[0])) return 0;
if (!(t0 >= 1.25 * ((t1 + t2) / 2.0))) return 0;
if (!(w->H[0] > w->L[0])) return 0;
if (!(w->H[0] >= w->H[1] + w->tick)) return 0;
if (!(w->C[0] <= w->H[1] - w->tick)) return 0;
if (!(w->C[0] <= w->L[0] + 0.25 * (w->H[0] - w->L[0]))) return 0;
return 1;
}
// ---- Ordinal 5: VAP Gradient Divergence v2 Direct (Range, single bar) ----
static int Tmv2_F05VapRowsOk(const Tmv2Window* w)
{
if (!w->vapMultIs1) return 0;
for (int k = 0; k < 5; k++)
{
if (!w->vapOk[k]) return 0;
if (!(w->vapTot[k] > 0.0)) return 0;
if (!Tmv2_IsFinite(w->vapTot[k]) || !Tmv2_IsFinite(w->vapSide[k])) return 0;
}
return 1;
}
static int Tmv2_F05Bull(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
if (!Tmv2_FlowAt(w, 0)) return 0;
if (!(w->L[0] > 0.0)) return 0;
double t0 = Tmv2_Total(w->AV[0], w->BV[0]);
if (!(t0 >= 20.0)) return 0;
if (!((w->H[0] - w->L[0]) >= 4.0 * w->tick)) return 0;
if (!Tmv2_F05VapRowsOk(w)) return 0;
double sideSum = 0.0;
for (int k = 0; k < 5; k++) sideSum += w->vapSide[k];
if (!(sideSum >= 20.0)) return 0;
if (!((w->AV[0] - w->BV[0]) <= -0.10 * t0)) return 0;
if (!(w->C[0] > w->O[0])) return 0;
if (!(w->C[0] >= w->H[0] - 0.25 * (w->H[0] - w->L[0]))) return 0;
const double* a = w->vapSide; // AVAP rows L .. L+4t
if (!(a[4] >= 2.0 * a[0] + 10.0)) return 0;
int grad = ((a[1] > a[0] && a[2] > a[1] && a[3] > a[2]) ||
(a[1] > a[0] && a[2] > a[1] && a[4] > a[3]) ||
(a[1] > a[0] && a[3] > a[2] && a[4] > a[3]) ||
(a[2] > a[1] && a[3] > a[2] && a[4] > a[3])) ? 1 : 0;
if (!grad) return 0;
return 1;
}
static int Tmv2_F05Bear(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
if (!Tmv2_FlowAt(w, 0)) return 0;
if (!((w->H[0] - 4.0 * w->tick) > 0.0)) return 0;
double t0 = Tmv2_Total(w->AV[0], w->BV[0]);
if (!(t0 >= 20.0)) return 0;
if (!((w->H[0] - w->L[0]) >= 4.0 * w->tick)) return 0;
if (!Tmv2_F05VapRowsOk(w)) return 0;
double sideSum = 0.0;
for (int k = 0; k < 5; k++) sideSum += w->vapSide[k];
if (!(sideSum >= 20.0)) return 0;
if (!((w->AV[0] - w->BV[0]) >= 0.10 * t0)) return 0;
if (!(w->C[0] < w->O[0])) return 0;
if (!(w->C[0] <= w->L[0] + 0.25 * (w->H[0] - w->L[0]))) return 0;
const double* b = w->vapSide; // BVAP rows H .. H-4t
if (!(b[4] >= 2.0 * b[0] + 10.0)) return 0;
int grad = ((b[1] > b[0] && b[2] > b[1] && b[3] > b[2]) ||
(b[1] > b[0] && b[2] > b[1] && b[4] > b[3]) ||
(b[1] > b[0] && b[3] > b[2] && b[4] > b[3]) ||
(b[2] > b[1] && b[3] > b[2] && b[4] > b[3])) ? 1 : 0;
if (!grad) return 0;
return 1;
}
// ---- Ordinal 6: Whole-Distribution Migration (Range, 2 bars) ----
static int Tmv2_F06Bull(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
if (!w->vpaOk[0] || !w->vpaOk[1]) return 0;
if (!w->vahOk[0] || !w->valOk[0] || !w->vahOk[1] || !w->valOk[1]) return 0;
if (!(w->VpA[0] > 0.0 && w->VpA[1] > 0.0)) return 0;
if (!(w->VAH[0] > 0.0 && w->VAL[0] > 0.0 && w->VAH[0] >= w->VAL[0])) return 0;
if (!(w->VAH[1] > 0.0 && w->VAL[1] > 0.0 && w->VAH[1] >= w->VAL[1])) return 0;
if (!(w->VpA[0] >= w->VpA[1] + w->tick)) return 0;
if (!(w->VAH[0] >= w->VAH[1] + w->tick)) return 0;
if (!(w->VAL[0] >= w->VAL[1] + w->tick)) return 0;
if (!(w->C[0] >= w->VAH[0])) return 0;
if (!(w->C[0] > w->C[1])) return 0;
return 1;
}
static int Tmv2_F06Bear(const Tmv2Window* w)
{
if (!Tmv2_TickOk(w)) return 0;
if (!w->vpaOk[0] || !w->vpaOk[1]) return 0;
if (!w->vahOk[0] || !w->valOk[0] || !w->vahOk[1] || !w->valOk[1]) return 0;