D04-F04-A01 / Complete engineering topic

Zweig Breadth Thrust

A production-minded guide to Zweig Breadth Thrust.

D04 · MARKET BREADTH AND INTERNALS
D04-F04-A01Canonical / Tested / Open
D04 / D04-F04
Key concepts

The governed definitions this build depends on. Read them first if a term is unfamiliar.

The hidden decision

A broad rally reading is easy to spot; proving that participation accelerated fast enough without moving the clock after the fact is the harder job.

Zweig Breadth Thrust answers one bounded question: did smoothed advancing participation accelerate from a depressed state to a broad thrust quickly enough? Its value is not a prediction. Its value is an auditable state whose inputs, time, units, and failure conditions are visible.

By the end, you will be able to calculate the method, reproduce its synthetic case, inspect its implementation trace, and decide when the output must be withheld.

Build the mental model

Convert each session's advancing share of classified issues into a 10-session EMA, arm only below 0.400, and run a visible ten-session clock until a strictly above-0.615 completion or expiry.

Unlike a one-day Advance/Decline Ratio, this detector owns smoothing, an armed state, strict inequalities, and a bounded completion clock.

Where the method sits in Thrust and Pressure

The highlighted family card is a scope boundary. Count breadth, volume composition, recursive pressure, intraday sampling, and causal divergence can all produce lines, but their units and state memories are not interchangeable.

Freeze the contract before calculating

The selected formulation is:

Bt=At/(At+Dt);Zt=EMA10(Bt)B_t = A_t/(A_t+D_t); Z_t = EMA10(B_t)

Arm below 0.400 and complete above 0.615 within 10 later sessions. Publication invariant: The package refreshes the arming clock on each later sub-0.400 observation; equality at either threshold does not cross it.

The input table in the canonical README defines row fields and units. The parameter table distinguishes a sourced method from package choices such as equality behavior, seed, zero policy, sampling grid, normalized breadth scale, and event timestamp.

Advance-Decline Indicators supports this bounded source role: exchange breadth series have explicit universe, update-frequency, and provider lineage; issue and volume breadth must retain series identity. It does not override the limitations recorded in REFERENCES.md, and it does not establish historical performance.

Reproduce the synthetic result

Every observation below is synthetic. The arithmetic is author-derived:

SessionBreadth ratioEMA10StateClock age
2026-01-130.3800.459365idle
2026-01-140.3600.441299idle
2026-01-150.3400.422881idle
2026-01-160.3200.404175idle
2026-01-190.3000.385234arm / refresh0
2026-01-200.4000.387919arm / refresh0
2026-01-210.5000.408297armed1
2026-01-220.6500.452243armed2
2026-01-230.7500.506381armed3
2026-01-260.8000.559766armed4
2026-01-270.8500.612536armed5
2026-01-280.9000.664802thrust6

The latest sub-0.400 EMA occurs on 2026-01-20. The strictly above-0.615 EMA occurs six later sessions afterward on 2026-01-28, so the event is valid and is timestamped on 2026-01-28.

The expected output is:

JSON
{
  "status": "thrust",
  "trigger_date": "2026-01-28",
  "ema": 0.664802116986
}

A thrust is a state transition, not a high breadth reading.

The SVG is not a decorative output card. It isolates the algorithm's actual learning obstacle: the final ema hides the arm, refresh, strict thresholds, and ten-session clock. Notice the labels and dashed or branched boundaries; color is supplementary.

Calculation flow

The Mermaid diagram renders from visuals/mermaid/calculation-flow.md and follows this pseudocode:

Plain text
validate ordered daily count breadth
seed EMA from first advancing share
for each later session:
    update EMA
    if EMA < 0.400: arm or refresh clock
    else if armed and EMA > 0.615 and age <= 10: publish thrust now
    else if armed and age > 10: expire
return armed or no_thrust with trace

Validation comes first so bad evidence cannot mutate later state. That rule is especially important for recursive lines, timestamp grids, arming clocks, and right-side pivot confirmation.

Read the implementation

Python and TypeScript both:

  1. validate finite inputs and the topic's identity/clock rules;
  2. compute the named intermediate values;
  3. preserve the trace needed to audit the result; and
  4. return a numeric value or typed event only when the publication boundary passes.

They share datasets/canonical-fixture.json, including expected output and parameters. The code avoids provider SDKs and hidden network calls so the teaching arithmetic is fully inspectable.

Use the guided lab

Open visuals/animated/playground.html. Its objective is to step across the arm, refresh, completion, equality, and expiry boundaries.

The initial screen is the completed canonical state. Back and Step reveal one observation at a time; Play uses the same transition function; Pause stops it; Reset restores the scenario and parameter. Comparison isolates a nearby outcome, while Failure / boundary demonstrates a reason code rather than a persuasive fake value. Reduced-motion Play advances one deterministic step without starting a timer.

The diagnostic panel and audit table should be read together. A smooth chart alone cannot prove denominator topology, chronological completeness, sampling continuity, or causal confirmation.

Operational interpretation

Use the state trace to audit whether a published thrust was armed, refreshed, expired, or completed on information available that session.

Do not misuse it: Do not call a high EMA a thrust when no earlier sub-0.400 arm exists, and do not backdate the event to the arming session.

Passing the implementation checks proves definition fidelity. It does not prove reversal prediction, causal market explanation, trading profitability, transferability to another universe, or performance after costs.

Edge cases worth testing

  • Exactly 0.400 does not arm; exactly 0.615 does not complete.
  • A later sub-0.400 EMA refreshes the clock under this package-selected convention.
  • A completion on age 10 is valid; the same crossing on age 11 is expired.
  • Duplicate, decreasing, or missing session dates invalidate the state trace.

Provider corrections require recomputing the affected tail or event set. A corrected observation that becomes available later must not leak into an earlier point-in-time result.

Evidence and historical boundary

A named historical reconstruction remains deferred. Publication would require a licensed point-in-time universe, exact classification rules, synchronized observations, session and sampling policy, correction lineage, the selected calculation convention, a knowledge cutoff, and redistribution rights.

Using a famous chart without that bundle would make the story stronger and the evidence weaker. The labelled synthetic fixture is therefore the responsible public example.

Summary and handoff

You can now calculate B_t = A_t/(A_t+D_t); Z_t = EMA10(B_t), interpret ratio from 0 to 1, audit the method-specific boundary, and reproduce matching Python and TypeScript behavior. Review Advance/Decline Ratio for the immediate input concept. Continue with Cumulative TICK in the dependency path.

Calculation Flow — Zweig Breadth Thrust

Purpose: The final EMA hides the arm, refresh, strict thresholds, and ten-session clock.

Rendering system map…

Takeaway: A thrust is a state transition, not a high breadth reading.

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

Claim-to-source map

Source IDWhat it supportsBoundary
STOCKCHARTS-BREADTHExchange breadth series have explicit universe, update-frequency, and provider lineage; issue and volume breadth must retain series identity.Provider symbols and data feeds do not make one universe or classification rule universal.
STOCKCHARTS-ZBTZweig Breadth Thrust uses a 10-day exponential average of A/(A+D) and a move from below 0.40 to above 0.615 within ten days.Attribution and classic parameters do not establish predictive performance; clock refresh and equality details remain package choices.

The formula/concept source and the data-provenance source do different jobs. Exchange product documentation establishes venue fields, versioned feeds, and licensing boundaries; it does not silently define every derived indicator. Package-selected zero, seed, refresh, sampling, scale, and confirmation rules remain implementation choices.

STOCKCHARTS-BREADTH — Advance-Decline Indicators

  • Organization or authors: StockCharts ChartSchool
  • Source type: Maintained provider-method documentation
  • Publication or effective date: Current documentation
  • Version or retrieval state: Exact page or PDF retrieved 2026-07-26
  • URL: https://chartschool.stockcharts.com/table-of-contents/index-and-market-indicator-catalog/advance-decline-indicators
  • Accessed: 2026-07-26
  • Jurisdiction or applicability: Provider series for major U.S. and Canadian exchanges
  • Evidence role: sourced/provider convention
  • Supports: Exchange breadth series have explicit universe, update-frequency, and provider lineage; issue and volume breadth must retain series identity.
  • Limitations: Provider symbols and data feeds do not make one universe or classification rule universal.

STOCKCHARTS-ZBT — Advance Decline Ratio Indicators, Chapter 5

  • Organization or authors: Greg Morris, StockCharts
  • Source type: Maintained institutional technical-method article
  • Publication or effective date: 2015-01-15
  • Version or retrieval state: Exact page or PDF retrieved 2026-07-26
  • URL: https://articles.stockcharts.com/article/articles-dancing-2015-01-advance-decline-ratio-indicators-chapter-5--cgmbi/
  • Accessed: 2026-07-26
  • Jurisdiction or applicability: Classic NYSE breadth convention
  • Evidence role: sourced/provider convention
  • Supports: Zweig Breadth Thrust uses a 10-day exponential average of A/(A+D) and a move from below 0.40 to above 0.615 within ten days.
  • Limitations: Attribution and classic parameters do not establish predictive performance; clock refresh and equality details remain package choices.

Historical-example decision

Deferred. A named reconstruction requires the exact point-in-time universe, classification method, timestamps, correction lineage, session rules, calculation convention, and redistribution rights. This public package publishes labelled synthetic inputs and author-derived arithmetic instead. It makes no historical-association, prediction, causation, or profitability claim.

zweig_breadth_thrust.ts
export type Result = Record<string, any>;
const numberValue = (value: unknown, name: string): number => {
  if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${name} must be a finite number`);
  return value;
};

export function calculate(rows: any[], emaLength=10, lowThreshold=0.4, highThreshold=0.615, maxSessions=10): Result {
  if (!rows.length) return {status:"incomplete", reason:"no observations"};
  if (emaLength<1 || maxSessions<1 || lowThreshold<0 || lowThreshold>=highThreshold || highThreshold>1) throw new Error("invalid thrust parameters");
  const alpha=2/(emaLength+1); let current:number|null=null, armedAt:number|null=null, previousDate:string|null=null; const series:any[]=[];
  for (let index=0;index<rows.length;index++) { const row=rows[index];
    if (row.ready===false) return {status:"incomplete",reason:"unready observation",series};
    if(typeof row.session_date!=="string"||!/^\d{4}-\d{2}-\d{2}$/.test(row.session_date)||Number.isNaN(Date.parse(row.session_date)))return{status:"incomplete",reason:"invalid_session_date",series};
    if(previousDate!==null&&row.session_date<=previousDate)return{status:"incomplete",reason:"session_dates_must_increase",series};
    const advances=numberValue(row.advances,"advances"), declines=numberValue(row.declines,"declines");
    if (advances<0 || declines<0 || advances+declines<=0) return {status:"incomplete",reason:"invalid breadth counts",series};
    const ratio=advances/(advances+declines); current=current===null?ratio:alpha*ratio+(1-alpha)*current; let state="idle";
    if (current<lowThreshold) {armedAt=index;state="armed";}
    else if (armedAt!==null) {const age=index-armedAt;
      if (current>highThreshold && age<=maxSessions) {series.push({date:row.session_date,ratio,ema:current,state:"thrust"});return {status:"thrust",trigger_date:row.session_date,ema:current,sessions:age,series};}
      if (age>maxSessions) {armedAt=null;state="expired";} else state="armed";
    }
    series.push({date:row.session_date,ratio,ema:current,state});
    previousDate=row.session_date;
  }
  return {status:armedAt!==null?"armed":"no_thrust",ema:current,series};
}
Full-height labplaygroundOpen full screen