# ZEC Renko Line Bot — complete build document

Last updated: 2026-11-11. Paper trading only. No API keys, no live orders.
This file is self-contained on purpose: hand it to any AI and it has the whole strategy,
the code map, the real test results and the open tasks.

---

## 1. What this is

A three-chart Renko strategy on ZEC perpetual futures, executed against **one horizontal
line**. Price oscillates on that line; the line is both the entry and the exit. The bot
trades the oscillation on the finest chart while price is touching the line, then holds the
last trade through the run until price comes back to the newest line.

Current state: paper bot + backtester + history analyser, all runnable. Live order
placement is deliberately not built yet.

---

## 2. The three charts

| | Chart 2 | Chart 1 | Chart 3 |
|---|---|---|---|
| What | 1hr Renko | tick Renko | tick Renko |
| Box | 0.1% of price | 0.1% of price | 0.001% of price |
| Source | closed 1h candles | live aggTrades | live aggTrades |
| Job | **the line** | **on/off switch** | **every entry and exit** |
| At ZEC $1,150 | ≈ $1.15 | ≈ $1.15 | ≈ $0.0115 ≈ 1 price tick |

Chart 3 is 100× finer than charts 1 and 2. One 0.1% brick = 100 chart-3 bricks.

**Chart 3's "timeframe":** there isn't one. It is built from every individual trade
(millisecond timestamps), which is the finest granularity that exists — Binance USD-M has
no 1s candles (smallest is 1m), and no chart below individual trades. 0.001% is not a
taste choice either: at ZEC ≈ $1,150 it equals one price tick ($0.01). Anything smaller
rounds to the same tick and batches into the same brick. So 0.001% is the floor, and it is
exactly the right box for one job: detecting "price touched the line" as precisely as the
exchange's price grid allows.

---

## 3. The rules, precisely

State: `line`, `side`, `gate`, `position`, `armed`, `carried`.

1. **Line.** Each closed 1h candle can print a 0.1% brick. The **start price of the newest
   brick is the line**; the brick's direction is `side` (which side price must be on).
2. **Gate on.** Chart 1 (0.1% tick) must **touch** the line *and* its close must be on the
   line's side. That sets `gate = true` and chart 3 starts trading.
3. **Gate stays on** while chart 1's newest brick still spans the line — even if chart 1
   prints red, green, red. Colour does not matter; touching does.
4. **Gate re-opens.** If chart 1 leaves the line and later touches it again *before a new
   1h brick prints*, the gate turns back on and chart 3 trades that same line again.
5. **Flip at the line.** A chart-3 brick whose range contains the line, with a direction
   different from the open position, **closes the old trade and opens one in the brick's
   colour at the line**. No crossing required.
6. **Arming.** If the touching brick is in the *same* direction as the open position, the
   position is armed with `side = -direction` (a reversal in the opposite direction is
   allowed to fire immediately).
7. **The one-brick loss.** An opposite-direction brick printed **without touching the line**
   while armed closes the trade and follows the new brick at market. This is the only
   intentional loss, and it is one 0.1% brick (≈$0.0007 per brick at 75x on $1 — see §4).
8. **Hold.** When the gate turns off (chart 1 left the line) and a position is open, that
   position is `carried = true`: the final direction. Chart 3 stops trading.
9. **Book.** When price returns and touches the **newest** line, the carried trade closes
   at the line and the cycle is booked: the sum of *every* trade since the last booking,
   fees included, chart-3 losses included.
10. **Split.** If the booked net is > 0: 40% stays in capital, 60% moves to savings +
    alert. If ≤ 0: nothing moves out, alert says flat/loss.
11. **Liquidation** is simulated: adverse move ≥ 1/leverage − maintenance margin.

### Edge cases the code handles

- **Gap bricks.** One trade can print several bricks. They print in order; a touching
  brick inside the gap still flips at the line.
- **Reversal needs a full box past the open** (standard percentage Renko), so a brick
  cannot reverse halfway through itself.
- **No trade at the hour boundary.** Hourly bricks are built from Binance's own closed
  candle (REST), not from the last tick, so the line is exact.
- **Fill realism.** `FILL="line"` = the flip fills exactly on the line (best case).
  `FILL="market"` = last trade price + 1 tick. Live market orders can never land exactly
  on the line; the closest real approximation is a resting stop-market order at the line,
  which triggers there but fills at whatever the book gives.

---

## 4. The numbers that decide everything

Position size = cash × leverage × 0.95. Taker fee = 0.05% of **position**, charged on every
market order. One brick of profit = 0.1% of **price** × position.

On $1 at 75x (position $71.25):

| Item | Value |
|---|---|
| One market order | $0.0356 (3.56% of capital) |
| One round trip | $0.0713 (7.1% of capital) |
| One 0.1% brick | $0.0713 (7.5% of capital) |
| One chart-3 brick | $0.00071 |
| One flip | ≈ 100 chart-3 bricks |

**Read that table carefully.** One brick earned ≈ one round trip paid. A cycle that flips
*twice* needs to travel two bricks just to break even, and every neutral flip (price
touching the line and going nowhere, which is most of them) is pure cost.

**The chart-3 losses are genuinely irrelevant** — one chart-3 brick is $0.00071, i.e. 1/100
of one 0.1% brick. Even 50 of them is half a brick. You were right about that; do not spend
attention on it. **Fees are the only thing on this table that matters.**

Leverage changes the units, not the ratio: at 20x one 0.1% brick = 2% of capital and one
round trip = 1.9%. Fees stay ~95% of a brick at any leverage — which is why a zero-fee
venue is not a nice-to-have but the whole difference.

---

## 5. Real results (run 2026-11-11)

### 5a. Six and a half years of 1hr data — `history.py`

ZECUSDT 1h candles, 2020-02-05 → 2026-11-11 (2,471 days, 59,314 hours, 425,662 bricks,
172 bricks/day).

**Runs of same-direction 1h bricks (n = 23,723):**

| length | share |
|---|---|
| 1 | 5.6% |
| 2 | 5.5% |
| 3–4 | 10.4% |
| 5–9 | 22.1% |
| 10–19 | 26.6% |
| 20–49 | 23.4% |
| **50+** | **6.5% (1,544 runs)** |

Longest run: **534 bricks**. Median run: **11 bricks**.
**Your claim was correct** — the 1hr chart regularly prints 20, 50, 100+ bricks in one
direction. It is not a rare tail event.

**Distance from a line back to that line (n = 416,475 cycles):** median 5.6 bricks, mean
9.9, p25 2.3, p75 11.8, p90 22.1, max 376.
Share of cycles worth ≥3 bricks: **69%**; ≥5: **54%**; ≥10: **30%**.

So one cycle is typically worth 5–6 bricks — 40%+ of capital at 75x. That is the real edge,
and it is much bigger than one brick. The problem is entirely how many flips happen *while*
price sits on the line before that run starts.

### 5b. Tick replay — `backtest.py`

Real Binance aggTrades, **2026-11-06 → 2026-11-08** (4,800,172 ticks, the full three days),
59 lines, fills on the line, 20x, start $1. (The first runs of this replay reported
liquidations; that was a bug — a carried trade could never close because chart 3 was skipped
while the gate was off. Fixed; see `PLAN.md` §0.)

| 20x, $1 | chart-1 gate, 0% fee | band gate (no chart 1), 0% fee | 75x, 0.05% fee |
|---|---|---|---|
| cycles | 50 | 138 | 5 (account drained) |
| chart-3 trades | 403 | 1,249 | 37 |
| win / loss / neutral | 10 / 26 / 367 | 27 / 86 / 1,136 | 0 / 6 / 31 |
| net | **+2.72** | **+10.71** | **−0.93** |
| fees paid | 0 | 0 | 0.87 |
| equity from $1 | $3.72 | $11.71 | $0.07 |
| max drawdown | 0.6% | 1.7% | 92.7% |
| bricks held (the profit) | 12 | 33 | 0 |
| net per cycle | +2.08 bricks | +1.70 bricks | −4.54 bricks |

Four conclusions, in order of importance:

1. **No liquidation.** The line always exits first — it is closer than 1/leverage. My earlier
   liquidation warnings were a code bug and are withdrawn (`PLAN.md` §0).
2. **The gross edge is real on real data.** +272% on a $1 account over three days at 20x with
   zero fees and 0.6% max drawdown; +1071% with the band gate.
3. **Fees are the entire question.** At 0.05% the account is gone in five cycles. At 0% it
   compounds. Same trades, same rules.
4. **Chart 1 is a fee filter, and the data says so precisely:** with zero fees, *removing*
   chart 1 made more money (+10.71 vs +2.72) by catching more runs; but it did so with 3×
   the trades, so per cycle it was worse (+1.70 vs +2.08 bricks) — and at any fee above zero
   those 1,249 trades are fatal. Keep chart 1 while fees exist; it is optional only at 0%.

**Most trades are neutral** — 367 of 403 opened and closed at the same line for zero price
movement. That is the mechanism: the line is a magnet, so most flips earn nothing. Only 9 of
50 cycles made a brick or more; the mean (+2.08) is carried by a few long runs (best +33.7).

**Capture is the open problem, not direction.** Only 12 1h bricks were printed while a trade
was held across 50 cycles, because the held trade exits at the *newest line*, which is
usually the same line. A trailing line would capture the long runs (median run 11 bricks,
6.5% are 50+) — that is a rule change and is task 1 in `PLAN.md` §9.

---

## 6. Where to trade it — ZEC perp, verified 2026-11-11

| Venue | Taker / Maker | ZEC max lev | Notes |
|---|---|---|---|
| **Lighter** (Standard) | **0% / 0%** | **20x** | Best fit: ZEC listed, $31.6M/day, $27.8M OI. 300ms taker latency. 0% confirmed live via API, not marketing. TWAP/advanced orders cost 1bp. |
| Paradex (Retail/UI) | **0% / 0%** | 10x | ZEC-USD-PERP exists, min order $10. **API orders are 0.02% taker** — UI only for true zero. |
| Variational | 0% / 0% | n/p | ZEC listed; leverage not published. |
| Aster | 0.04% / 0.005% | 75x | ZECUSDT, 8h funding. Best CEX-style leverage with a zero maker. |
| Extended | 0.025% / 0% | 10x | Cheapest non-zero taker. |
| Hyperliquid | 0.045% / 0.015% | 10x | Deepest book, official SDK, but 10x only on ZEC. |
| Binance | 0.05% / 0.02% | 75x | The only place with 75x on ZEC — and 75x is what liquidates you. |
| dYdX / edgeX / Pacifica | 0.05 / 0.038 / 0.04 taker | 5x / 8x / 10x | Worse on both axes. |
| Non-crypto | — | — | Same strategy works on any chart (futures, stocks, indices) but retail per-contract fees are worse than any of the above. Crypto perps are the cheapest execution on earth. |

**Recommendation:** Lighter at 20x. Zero fees removes the single thing that lost money in
the backtest, and 20x means liquidation needs a 5% move instead of 1.33% — survivable
against a median 5.6-brick (5.6%) cycle. One brick is then 2% of capital and a median cycle
is ~11%.

---

## 7. Files

| File | What it does |
|---|---|
| `zec_renko_bot.py` | The strategy, paper mode. Live aggTrade stream → three Renko charts → line/gate/flip/hold/book. Alerts with USD **and** %, per-trade fees, cycle split. |
| `backtest.py` | Replays real Binance aggTrades through the *same* bot class. Stats: lines, cycles, trades/cycle, wins/losses/neutrals, fees, net, gross, drawdown, liquidation, 1hr run lengths, bricks per cycle. |
| `history.py` | Years of 1h candles from the REST API: 1hr brick run-length distribution and line-to-line distance in bricks. Fast, no tick data. |
| `strategy_preview.html` | Visual: the cycle diagram, the rules, the fee table, 8 scenarios (5 hard, 3 easy), real results. |
| `data/` | Downloaded daily aggTrades zips. |
| `events.csv`, `state.json` | Live bot log and paper cash/savings state. |

Run:
```
pip install python-binance
python backtest.py --download 2026-11-06 2026-11-08
python backtest.py --data data --both
python backtest.py --data data --both --fill market --capital 100
python history.py
python zec_renko_bot.py          # paper, needs no keys
```

---

## 8. Open questions, answered

**Why did the long appear on the wrong brick in scenario 5?**
Drawing error, not a code bug. In that chart the long marker sat on the brick that printed
back *without* touching the line. Per the rules (§3.6–3.7) that brick is only allowed to
reverse when the position is **armed**, and the arm flag is set by an earlier brick that
*did* touch and was in the same direction as the position. After a short opened at the line
and price gapped six bricks away, the first up-brick does not qualify — the long waits for
the brick that actually touches the line. The next bot version added the explicit
`armed`/`side` pair for exactly this, so engine-side it is now correct.

**Do we still need chart 1 (0.1% tick)? Is chart 3 doing its job already?**
Keep it — but it earns its place as a **fee filter**, not as an information source. Chart 3
is 100× finer, so it can absolutely tell you "price is at the line". What chart 1 tells you
is something different: whether price is *still* there on the same 0.1% grid that defines
the line. Removing it means chart 3 trades near the line much more often, and §5b shows
neutral flips are exactly what destroys the account (41 of 53 trades). Chart 3 cannot
replace that filter, because a filter defined on the *same* granularity as the trader is no
filter at all. Both options are in the code (`GATE_MODE` "chart1" vs "distance") so it can
be measured rather than argued — but on the evidence, removing it increases cost.

**Is 0.001% the best chart-3 box?**
It is the finest box the exchange's price grid allows (one tick), which is exactly what you
want for "did price touch the line". It is *not* the cheapest: every brick that touches
produces a flip. If the backtest shows flips are excessive once fees are zero, the first
thing to try is 0.003% or 0.005%, which triples/quintuples the move required to flip and
cuts neutral flips by roughly the same factor, at the cost of exiting slightly off the line.

**Stop order at the line — advantage or disadvantage?**
Net advantage, with one condition. The strategy's exit *is* the line, so a resting
stop-market at that exact price adds no new level — it only fires when the bot would have
acted anyway, and it saves you from a dropped connection, a sleeping laptop or a dead
process while holding at 75x. The condition: it can fire on a wick that your bot's
chart-3 brick never confirms, leaving the bot's state out of sync with the exchange. So:
place it **1 chart-1 brick beyond the line** (a disaster stop, not a strategy exit), and
reconcile position state on startup. At 20x you have room for that; at 75x you do not.

**Alerts — done.** LONG opened, SHORT opened, trade closed (USD + %), cycle profit/loss
(USD + %), hold, trading on, new line, liquidation. Terminal + bell always; Telegram if
`TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID` are set in the environment (never in the file).

**10 Claude accounts sharing the context.**
Not something that can be configured from here, and there is no cross-account memory
setting. The working method: create one **Project** per account, put this file in its
Project knowledge, and put the short block below in Custom Instructions. That is the only
mechanism that persists across chats; free-tier chats otherwise start blank. Cheaper
alternative: keep one file, paste it when needed — this document is written to be pasted.

**Chart-3 losses vs profit.** Agreed, and de-emphasised everywhere (§4). They are 1/100 of
a 0.1% brick each. The scenario stats still count them, because they are in the cycle total.

---

## 9. Open tasks

1. **Re-run the backtest at 20x** (`--capital 100`, and add a `--leverage` flag) to see
   whether the cycle PnL survives with fees zeroed and no liquidation to truncate the run.
2. **Longer sample.** Three days is 9 cycles. `backtest.py --download` can pull months
   (a month of ZEC ticks is several GB and hours of CPU). Do weeks before believing a rate.
3. **Lighter execution layer.** Public market data + REST/WS order placement, no keys in
   code, 0% fee confirmed per order, 300ms taker latency budgeted.
4. **Restart reconciliation.** Read open position from the exchange on boot; never assume
   flat.
5. **Gate comparison.** Run `GATE_MODE="distance"` vs chart 1 on the same data to put a
   number on the filter (§8).
6. **Box sweep.** 0.001% vs 0.003% vs 0.005% on chart 3, scored on net after fees per cycle.
7. **Live order code**, only after 2–4 weeks of paper results and a positive net backtest.

---

## 10. Known limits

- No funding fees, latency, or order-book depth in the backtest. Funding on ZEC is ~11%/yr
  and matters little for hours-long holds.
- `FILL="line"` is the best case; `FILL="market"` is closer to reality. Live fills are
  worse than both in fast moves.
- Liquidation is approximate (`1/leverage − 0.5%`).
- The 1hr Renko grid depends on its start point, so it will not match a TradingView chart
  brick-for-brick. The line is a level, not a picture.
- One cycle is 5–6 bricks typically, so a few weeks is a small sample; do not tune to it.
- The edge is public information (everyone can draw hourly Renko lines) and will decay.

---

## 11. For another AI picking this up

You have: the rules in §3, the fee maths in §4, verified venue data in §6, and the code in
§7 — a paper bot whose strategy logic is the reference implementation. Do not re-derive the
strategy from scratch or "improve" it silently: the user has iterated on these rules many
times and each clause in §3 exists because a simpler version was wrong. The one number to
keep in front of you is **fees per order ÷ profit per brick**; at 0.05%/75x that ratio is
~0.95 and the strategy cannot work, and at 0%/20x it is 0 and the strategy is worth testing
properly. Never quote percentages without USD, and never call a floating gain a profit — a
profit exists only after the held trade closes at the newest line, net of fees.
