TIKTAPE
Backtest Recipe / DOGE / 1h

DOGE SMA Crossover Backtest

A practical tutorial for testing a classic trend-following setup on Hyperliquid 1h candles, with runnable Python and the current signal snapshot for DOGE.

8,664 hourly candles for DOGE

This walkthrough uses data from 2025-03-23 to 2026-05-10. After the SMA-200 warm-up, the strategy evaluates 8,465 hourly closes.

Use the counts here as a quick sense-check when you run the Python recipe against the same market and interval.

Golden Crosses
29
Trailing-window SMA(50/200), evaluated after candle close.
Death Crosses
28
Counted across the full stored 1h range for this market.
Latest Cross
2026-04-14T22:00:00Z
golden_cross
The current tutorial output is shown again in the Python snippet below.

What is an SMA crossover?

A simple moving average smooths price by averaging the last N closes. Traders often compare a faster line like the 50-period SMA with a slower line like the 200-period SMA to see whether momentum is strengthening or weakening.

A golden cross happens when the faster average moves above the slower one, which traders read as trend confirmation. A death cross is the opposite. The signal is easy to test, but it is still a lagging indicator and can whipsaw when price chops sideways.

The Python recipe

Copy / Paste / Run
GET https://tiktape.xyz/api/v1/ohlcv
"""
TikTape DOGE 1h SMA crossover recipe.

No lookahead bias: both SMAs use trailing windows and the crossover is
evaluated only after each candle close.

Expected output for this exact range:
candles=8664
usable_after_sma200=8465
golden_crosses=29
death_crosses=28
latest_signal=2026-04-14T22:00:00Z golden_cross
"""
import datetime
import json
import os
import urllib.parse
import urllib.request

BASE_URL = os.getenv("TIKTAPE_OHLCV_URL", "https://tiktape.xyz/api/v1/ohlcv")
API_KEY = os.getenv("TIKTAPE_API_KEY", "YOUR_TIKTAPE_API_KEY")
COIN = "DOGE"
INTERVAL = "1h"
START_MS = 1742688000000
END_MS = 1778461199999
PAGE_LIMIT = 10000
INTERVAL_MS = 60 * 60 * 1000


def fetch_all_candles():
    rows = []
    cursor = START_MS
    while cursor < END_MS:
        query = urllib.parse.urlencode(
            {
                "coin": COIN,
                "interval": INTERVAL,
                "startTime": cursor,
                "endTime": END_MS,
                "limit": PAGE_LIMIT,
            }
        )
        req = urllib.request.Request(
            f"{BASE_URL}?{query}",
            headers={"X-API-Key": API_KEY},
        )
        with urllib.request.urlopen(req, timeout=30) as resp:
            payload = json.load(resp)
        batch = payload["data"]
        if not batch:
            break
        rows.extend(batch)
        next_cursor = int(batch[-1][0]) + INTERVAL_MS
        if next_cursor <= cursor:
            raise RuntimeError("cursor did not advance; aborting pagination")
        cursor = next_cursor
    return rows


def rolling_sma(values, window):
    out = [None] * len(values)
    window_sum = 0.0
    for idx, value in enumerate(values):
        window_sum += value
        if idx >= window:
            window_sum -= values[idx - window]
        if idx >= window - 1:
            out[idx] = window_sum / window
    return out


def main():
    candles = fetch_all_candles()
    closes = [float(row[4]) for row in candles]
    sma50 = rolling_sma(closes, 50)
    sma200 = rolling_sma(closes, 200)

    golden_crosses = 0
    death_crosses = 0
    latest_signal = "none"

    for idx in range(1, len(candles)):
        if None in (sma50[idx - 1], sma200[idx - 1], sma50[idx], sma200[idx]):
            continue
        if sma50[idx - 1] <= sma200[idx - 1] and sma50[idx] > sma200[idx]:
            golden_crosses += 1
            signal_at = datetime.datetime.fromtimestamp(candles[idx][0] / 1000, tz=datetime.timezone.utc)
            latest_signal = f"{signal_at.strftime('%Y-%m-%dT%H:%M:%SZ')} golden_cross"
        elif sma50[idx - 1] >= sma200[idx - 1] and sma50[idx] < sma200[idx]:
            death_crosses += 1
            signal_at = datetime.datetime.fromtimestamp(candles[idx][0] / 1000, tz=datetime.timezone.utc)
            latest_signal = f"{signal_at.strftime('%Y-%m-%dT%H:%M:%SZ')} death_cross"

    usable_after_sma200 = max(len(candles) - 200 + 1, 0)
    print(f"candles={len(candles)}")
    print(f"usable_after_sma200={usable_after_sma200}")
    print(f"golden_crosses={golden_crosses}")
    print(f"death_crosses={death_crosses}")
    print(f"latest_signal={latest_signal}")


if __name__ == "__main__":
    main()

Expected output for the exact range embedded above:

candles=8664
usable_after_sma200=8465
golden_crosses=29
death_crosses=28
latest_signal=2026-04-14T22:00:00Z golden_cross

Run it on DOGE

The strategy currently reports 29 golden crosses and 28 death crosses in the stored 1h range. The latest crossover on DOGE printed at 2026-04-14T22:00:00Z as a golden_cross signal.

That gives you a concrete output to compare against when you run the script locally and start changing parameters.

Extending this recipe

Fetch the complete tutorial dataset with the TikTape API:

GET /api/v1/ohlcv?coin=DOGE&interval=1h&limit=8664

Use the endpoint above to request the same 1h candle history used by this walkthrough. The related links below cover the full history page, the broader backtesting guide, and the provider comparison.