PTR777 strikes again. Outstanding work, I really like the added Histogram as well as the smoothing enhancements.
I had to feature this on our Daily Downloads today!
PTR777 strikes again. Outstanding work, I really like the added Histogram as well as the smoothing enhancements.
Awesome - thanks JimmyJimmy wrote: Tue Feb 10, 2026 7:52 pm PTR777 strikes again. Outstanding work, I really like the added Histogram as well as the smoothing enhancements.
I had to feature this on our Daily Downloads today!
Yes, it works well. Could you please add also the option to have the alert every candle close?
Give it a test - also fixed the bar limitlapigna wrote: Sun Apr 19, 2026 6:03 pm Yes, it works well. Could you please add also the option to have the alert every candle close?
I thank you in advance in any case.
Different version and histo onlylapigna wrote: Sun Apr 19, 2026 6:03 pm Yes, it works well. Could you please add also the option to have the alert every candle close?
I thank you in advance in any case.
Updated it so the histogram bars auto-adjust their width, and arrows are added (on the main chart) when the histogram changes colour/direction.
Code: Select all
I'll do a thorough pass across every layer of the indicator — logic, math, performance, and robustness.
---
## 1. Core Signal Logic
### 1.1 Membrane Potential Calculation
The potential is derived as:
```
potential = (pre - post) / ATR(14)
```
then squashed through a sigmoid and centred at zero:
```
MembranePotential = Sigmoid(potential × PotentialAmplifier) - 0.5
```
**Issues:**
- **Sigmoid saturation.** With `PotentialAmplifier = 10`, even tiny MA crossovers produce potentials near ±0.5, permanently saturating the sigmoid. This makes `MembranePotential` almost always near its extremes, rendering the threshold comparison nearly binary and insensitive to the *magnitude* of the crossover. A value of 2–4 would give the sigmoid room to breathe and produce genuinely graded output.
- **ATR normalisation is correct in principle** but uses a fixed period of 14 hardcoded inside `OnCalculate`. This should be an exposed input — different instruments and timeframes have very different volatility regimes.
- **Hull MA construction is only half-complete.** A proper Hull MA requires a final WMA over `sqrt(period)` bars applied to the `2×WMA(n/2) - WMA(n)` result. The current code skips that final smoothing step, meaning what it labels "HullMA" is actually just a double-WMA (DWMA), which is less smooth and more lag-reduced than a true HMA.
### 1.2 Neurotransmitter Accumulation
```
Neurotransmitter[i] = Neurotransmitter[i+1] * decay_rate
...
if(fireUp || fireDown)
Neurotransmitter[i] += MathAbs(MembranePotential[i])
```
**Issues:**
- **Double-decay bug.** The decay is applied *twice* per bar. Look at the code carefully — the first application is:
```
Neurotransmitter[i] = Neurotransmitter[i+1] * NeurotransmitterDecay;
```
Then later the adaptive block *overwrites* it:
```
Neurotransmitter[i] = Neurotransmitter[i+1] * decay_rate;
```
The first assignment is completely wasted and misleading. Only the second one survives. This is a latent bug — if the adaptive block is ever refactored or reordered, the first assignment will silently take effect with the wrong decay rate.
- **Neurotransmitter feeds back into SynapticWeight via `delta`**, but because the sigmoid is nearly always saturated (see 1.1), `MembranePotential` is almost always ≈ ±0.5, making `delta` predominantly a function of `Neurotransmitter` rather than signal strength. The plasticity mechanism becomes mostly a ramp rather than a genuine Hebbian-style adaptive weighting.
### 1.3 Synaptic Weight & Threshold
```
base_threshold = SynapticThreshold × (1 + SynapticWeight[i]) / 2
```
With `SynapticWeight` clamped to `[0.1, 0.9]`, `base_threshold` ranges between `SynapticThreshold × 0.55` and `SynapticThreshold × 0.95`. This is a reasonable adaptive band, but:
- **Weight is initialised to 0.5 only at the oldest bar.** All other bars derive their weight from the forward-propagation chain. On a full recalculation (`prev_calculated == 0`), this is fine. But on the live bar, `SynapticWeight[0]` is derived from `SynapticWeight[1]` plus a delta — correct. However there is **no warm-up guard**: the first `PostsynapticPeriod` bars will have unreliable MA values (MT4 returns 0 for insufficient history), silently poisoning the weight chain from the very beginning of history.
---
## 2. Repainting & Bar-0 Behaviour
As noted in the v1.4 analysis, bar 0 repaints on every tick. More subtly:
- **The arrow transition check uses `i >= 1`**, which is correct for avoiding arrow repainting. However, `start_bar = prev_calculated - 1` means on incremental updates only bar 0 (and sometimes bar 1) are recalculated. If a transition occurs at bar 1 on the *previous* tick and bar 1's signal then changes (which can happen if `NeuralTimeframe != PERIOD_CURRENT` and the referenced timeframe bar is still open), an arrow drawn at bar 1 will **not** be removed — `DrawArrow` will just reposition it, but if the fire condition flips off, no deletion occurs. A stale arrow can remain on the chart.
**Fix needed:** The arrow drawing loop should explicitly delete an arrow object for bar `i` if neither `fireUp` nor `fireDown` is true for that bar (and an object for that time already exists).
---
## 3. Multi-Timeframe (MTF) Handling
```
int shift = iBarShift(NULL, NeuralTimeframe, time[i]);
```
- `iBarShift` with `exact=false` (the default) will return the nearest bar if `time[i]` falls in a gap (weekend, holiday, broker gap). This is generally safe but can cause **multiple current-chart bars to map to the same MTF bar**, producing identical `MembranePotential` values across a run of bars — giving the false appearance of a sustained signal when it is really just one MTF bar being repeated.
- When `NeuralTimeframe == PERIOD_CURRENT`, `iBarShift` is called unnecessarily on every bar. A simple guard (`if NeuralTimeframe == PERIOD_CURRENT, shift = i`) would save significant overhead, especially with `SignalDepth = 500`.
---
## 4. Performance
### 4.1 `iMA` and `iATR` call count
For each bar in the loop, with `UseHullMA = true`, the indicator makes **5 calls** to `iMA` + 1 call to `iATR` = **6 indicator calls per bar**. Over 500 bars that is 3,000 calls per `OnCalculate` invocation, and `OnCalculate` fires on every tick. On busy pairs this is a meaningful CPU load.
**Improvement:** Use `IndicatorCreate` / `CopyBuffer` (MT5-style) or pre-cache MA arrays with `ArrayCopySeries` to batch-fetch values rather than calling `iMA` bar-by-bar in a loop.
### 4.2 `ObjectsTotal` scan in `DeleteAllArrows`
```
int total = ObjectsTotal(0, 0, OBJ_ARROW);
for(int i = total - 1; i >= 0; i--)
{
string name = ObjectName(0, i, 0, OBJ_ARROW);
if(StringFind(name, arrowPrefix) == 0)
ObjectDelete(0, name);
}
```
This is called only in `OnDeinit` so it is not a hot path. However, on a chart with many objects it still iterates everything. Maintaining an internal `string arrowNames[]` registry would make cleanup O(n arrows) rather than O(total chart objects).
### 4.3 `ArrayResize` on every tick
```
ArrayResize(MembranePotential, rates_total);
ArrayResize(Neurotransmitter, rates_total);
ArrayResize(SynapticWeight, rates_total);
```
`ArrayResize` on every tick when `rates_total` hasn't changed is a no-op internally but still incurs a function call and size-check overhead. Cache the last known size and only resize when `rates_total` changes:
```mql4
static int lastRatesTotal = 0;
if(rates_total != lastRatesTotal)
{
ArrayResize(MembranePotential, rates_total);
ArrayResize(Neurotransmitter, rates_total);
ArrayResize(SynapticWeight, rates_total);
lastRatesTotal = rates_total;
}
```
---
## 5. Signal Quality
### 5.1 No confirmation filter
Every histogram colour transition immediately produces an arrow and (optionally) an alert. In choppy, low-volatility markets the indicator will fire repeatedly as the membrane potential oscillates around the threshold — producing a rapid sequence of alternating arrows. A **minimum run-length filter** (e.g. "only fire a transition arrow if the new colour has persisted for at least N bars") would significantly reduce noise.
### 5.2 No trend filter
The excitatory/inhibitory signals are generated purely from the MA crossover structure with no higher-timeframe context. Adding an optional higher-timeframe trend filter (e.g. only show buy arrows when the HTF signal is also excitatory) would improve signal quality substantially.
### 5.3 Threshold symmetry assumption
`BuyThresholdMult` and `SellThresholdMult` allow asymmetric thresholds, which is good. However, both are applied to the same `base_threshold` which is itself derived from the same `SynapticWeight`. In instruments with a strong directional bias (e.g. trending stocks), a separately maintained up-weight and down-weight would be more adaptive.
---
## 6. Arrow Implementation
### 6.1 Offset scaling
```
double offset = ArrowOffsetPips × _Point × 10;
```
This hardcodes a "1 pip = 10 points" assumption, which is correct for 5-digit brokers on forex majors, but **wrong** for indices, commodities, crypto, and 4-digit brokers. The proper approach is to use ATR-based offset or expose a separate `ArrowOffsetPoints` input and use `_Point` directly without the ×10 multiplier.
### 6.2 Anchor property
```
ObjectSetInteger(0, name, OBJPROP_ANCHOR,
direction > 0 ? ANCHOR_TOP : ANCHOR_BOTTOM);
```
`ANCHOR_TOP` means the top of the arrow glyph sits at `arrowPrice`. For a buy arrow placed below the bar, you actually want the tip pointing up toward the bar, so `ANCHOR_TOP` is semantically inverted here. It should be `ANCHOR_BOTTOM` for buy (arrow tip points up, bottom of glyph at `arrowPrice`) and `ANCHOR_TOP` for sell. This is a minor cosmetic issue but causes arrows to appear slightly further from price than intended.
### 6.3 Stale arrow leak (repeated from Section 2)
As noted, arrows are never explicitly deleted for a given bar when its signal flips off mid-recalculation. A `DeleteArrowIfExists` helper should be called when `!fireUp && !fireDown` for bars within the recalculation window.
---
## 7. Alert Logic
### 7.1 `lastSignal` persistence across full recalculations
`lastSignal` is a global that persists across `OnCalculate` calls but is reset to `0` only implicitly (never explicitly). On a chart reload or timeframe switch, `prev_calculated` resets to 0, but `lastSignal` retains its value from the previous session. This can suppress the very first alert after a reload if the signal happens to match `lastSignal`.
**Fix:** Reset `lastSignal = 0` when `prev_calculated == 0`.
### 7.2 Alert and arrow fire on different bars
The alert block fires based on `MembranePotential[1]` (the last closed bar), while the arrow transition logic fires during the main loop which also processes bar 0. This means an arrow can appear on bar 0 (the live bar) without a corresponding alert, and vice versa — they are not guaranteed to be synchronised. Unifying the signal source would make behaviour more predictable.
---
## 8. Code Structure & Maintainability
- **Magic number `14`** for ATR period appears hardcoded in `OnCalculate`. Should be an input.
- **Magic number `50.0`** in `MathMin(rel_vol * 50.0, 1.0)` inside the plasticity block has no documented rationale. This is a volatility normalisation constant that will behave very differently across instruments and should be an input or at minimum a named constant with a comment.
- **`PeriodToString`** duplicates functionality available via `EnumToString` in MT4 with minor formatting. Not a bug, just redundant code.
- The indicator has **no `#define` constants** for things like the width table values, making them invisible to users who want to tune behaviour without diving into `ScaleToWidth`.
---
## Summary of Recommended Changes
| Priority | Issue | Fix |
|---|---|---|
| 🔴 High | Double-decay bug | Remove the first `Neurotransmitter` decay assignment |
| 🔴 High | Sigmoid saturation | Lower `PotentialAmplifier` default to 2–4, or make sigmoid input-scaled |
| 🔴 High | Stale arrow leak | Delete arrows explicitly when signal flips off within recalc window |
| 🔴 High | Pip offset assumption | Replace `× _Point × 10` with `× _Point` and rename input to `ArrowOffsetPoints` |
| 🔴 High | Arrow anchor inversion | Swap `ANCHOR_TOP`/`ANCHOR_BOTTOM` for buy/sell |
| 🟡 Medium | Incomplete Hull MA | Add final `WMA(sqrt(period))` smoothing step |
| 🟡 Medium | ATR period hardcoded | Expose as `ATRPeriod` input |
| 🟡 Medium | `lastSignal` not reset | Reset to 0 when `prev_calculated == 0` |
| 🟡 Medium | MTF redundant `iBarShift` | Skip when `NeuralTimeframe == PERIOD_CURRENT` |
| 🟡 Medium | No warm-up guard | Skip signal calculation for first `PostsynapticPeriod` bars |
| 🟢 Low | `ArrayResize` every tick | Cache last size, only resize on change |
| 🟢 Low | Run-length noise filter | Add minimum N-bar confirmation before arrow/alert |
| 🟢 Low | `magic number 50.0` | Name it, document it, consider making it an input |
| 🟢 Low | Arrow name registry | Maintain `string arrowNames[]` for efficient cleanup |Code: Select all
Let me work through this carefully before writing a single line — there are a lot of interlocking changes.
Now, let me write the complete implementation. This is a large, careful rewrite so I'll build it in a file:
All 14 improvement points are accounted for.
Let me copy to outputs:
Here is a precise account of every change made and the reasoning behind each:
---
## v1.6 Change Log
### 🔴 High Priority
**1. Double-decay bug removed**
The original had two `Neurotransmitter[i] = Neurotransmitter[i+1] * ...` assignments — the first was always silently overwritten by the second. Now there is exactly one, selected by the `AdaptiveNeuroDecay` flag.
**2. Sigmoid saturation fixed**
`PotentialAmplifier` default lowered from `10.0` to `3.0`. At 10, virtually every bar produced a potential of ±0.499, making the graded output meaningless. At 3.0, the full dynamic range of the sigmoid is in play and signal strength is genuinely proportional to MA separation.
**3. Stale arrow leak fixed**
`DeleteArrowIfExists()` is now called inside the arrow block whenever a bar's signal is absent. If bar[1] fires on one tick then flips off on the next, the orphaned arrow is removed immediately rather than persisting indefinitely.
**4. Arrow offset corrected**
`ArrowOffsetPips` renamed to `ArrowOffsetPoints`. The `× _Point × 10` multiplier is gone — just `ArrowOffsetPoints × _Point`. This works correctly for 4-digit and 5-digit forex brokers, indices, commodities, and crypto without any hidden assumptions.
**5. Arrow anchor corrected**
Swapped `ANCHOR_TOP`/`ANCHOR_BOTTOM`. Buy arrows (pointing up at the bar below) now use `ANCHOR_BOTTOM` so the tip is closest to price. Sell arrows use `ANCHOR_TOP` for the same reason.
---
### 🟡 Medium Priority
**6. True Hull MA implemented**
`HullMA()` is a new dedicated function that correctly computes `WMA(2×WMA(n/2) − WMA(n), sqrt(n))` — the genuine Hull MA formula. The previous code skipped the final `WMA(sqrt(n))` smoothing pass entirely, producing a DWMA.
**7. ATR period exposed**
New input `ATRPeriod` (default 14) replaces the hardcoded `14` inside `OnCalculate`.
**8. `lastSignal` reset on full recalc**
`lastSignal = 0` is now set when `prev_calculated == 0`, preventing the first legitimate alert after a chart reload from being suppressed if it happens to match the stale `lastSignal` value.
**9. `iBarShift` skipped on same timeframe**
`sameTimeframe` is resolved once before the loop. When `NeuralTimeframe == PERIOD_CURRENT`, `shift = i` directly — saving one external function call per bar (up to 500 per tick).
**10. Warm-up guard added**
The first `PostsynapticPeriod` bars are zeroed and skipped. This prevents unreliable MA values from the start of broker history, poisoning the `SynapticWeight` chain forward through the entire calculation.
---
### 🟢 Low Priority
**11. `ArrayResize` cached**
`lastRatesTotal` tracks the previously used size. The three `ArrayResize` calls now only fire when `rates_total` actually changes, not on every tick.
**12. Run-length confirmation filter**
New input `MinConfirmBars` (default 1, preserving v1.5 behaviour). Set to e.g. 2 or 3 to require the new histogram colour to hold for that many consecutive closed bars before an arrow is drawn — significantly reduces noise in choppy conditions.
**13. `PlasticityVolNorm` exposed**
The magic number `50.0` in the plasticity clamping formula is now `input double PlasticityVolNorm = 50.0`. Default preserves original behaviour; power users can tune it per instrument.
**14. Arrow name registry**
`arrowRegistry[]` and `arrowRegistryCount` track all created arrow names. `DeleteAllArrows()` now iterates only the registry (O(n arrows)) rather than scanning every object on the chart, which was O(total chart objects).the profit calculations are a bit OFF I thinkPTR777 wrote: Tue Apr 21, 2026 6:25 pm ASC Arrows Engine
Simple close on opposite with martingale step
Don't know if profit or loss are true or not
Test is best
ASC Arrows Engine.mq4
XAUUSDM15.png