Strategies
Jesse KDJstrategy Faithful Fixed-Default Port — LTC Spot Daily
Outcome: Abandoned
JesseKdjLtcSpotDaily
Outcome Summary
This run translated Jesse's KDJstrategy onto LTC spot daily bars and kept the source's default parameters. It took 3 iterations to fix configuration and order-contingency issues, after which the port ran cleanly. The full-history backtest lost 39.1% over about 6 years on 336 trades, losing money in six of seven years and in all three volatility regimes, and it lost money even before commissions. The analyst abandoned it at backtest review for negative expectancy, which confirmed QA's earlier concern about the edge, and optimization, risk review and promotion were not reached.
Hypothesis
Translate Jesse community's MIT-licensed KDJstrategy faithfully onto LTCUSDT.BINANCE_SPOT using 1-DAY bars, a CASH account and no leverage. Preserve executable Python semantics rather than correcting the comments. KDJ defaults are fastk_period=9, slowk_period=3, slowk_matype=0, slowd_period=3, slowd_matype=0 and sequential=False. Compute the nine-bar stochastic, K as its three-bar SMA, D as K's three-bar SMA, and J=3*K-2*D. Calculate previous J using candles excluding the current candle. ATR period is 14. Preserve Jesse's indicator initialization and ATR smoothing; do not substitute EMA-based KDJ. Source: [KDJstrategy, pinned commit](https://github.com/jesse-ai/example-strategies/blob/7c91e0a37bf62165790120d730442e4f6eb00364/KDJstrategy/__init__.py). Defaults verified against [Jesse KDJ implementation](https://raw.githubusercontent.com/jesse-ai/jesse/master/jesse/indicators/kdj.py); the strategy does not pin its Jesse dependency version.
The explicit request for source fidelity takes precedence over introducing a novel mechanism or changing direction. Spot is appropriate because the code is long-only and Jesse's sizing helper caps notional at capital. The exact LTC daily catalog directory exists locally. BINANCE_SPOT is below its 15% target at 11%; this proposal nevertheless adds to overrepresented single-symbol, long-only and daily buckets because changing those dimensions would change the supplied strategy. corpus_concentration is empty, so LTC's recent research share cannot be established. This is a reproduction experiment, with no claim that KDJ supplies a demonstrated structural edge.
negative_expectancy (fee_edge/no_edge). This fixed-default Jesse KDJ port on LTCUSDT spot daily bars loses money on a reliable sample of 336 trades over about 6 years. avg_trade_return_pct is -0.281% against the +0.25% BINANCE_SPOT floor. PF is 0.856, Sharpe -0.30, max drawdown 44.1% against the pre-registered 30%, and six of seven years lost money. Gross PnL before commissions and impact is also negative (about -$10.6k), so fees are not what kills the edge. The signal IC is 0.011 (t=0.5), which is noise. Alpha against LTC buy-and-hold is -11.5% a year. The plan has no tunable parameters, so optimization cannot change anything. This confirms QA's edge concern: the full-history numbers match the sandbox (-0.253%, PF 0.865). A J > D gate is true on about half of all days, so it acts as a noisy oscillator gate with about 2.8-day holds, which cannot cover the 0.20% spot round-trip cost. The verdict covers this LTC spot daily translation with the source defaults only.
Implementation
Port of Jesse's KDJstrategy to LTCUSDT spot on daily bars. The stochastic uses 9 bars, K is a 3-bar SMA of it, D is a 3-bar SMA of K, and J = 3K - 2D, all over a 240-candle non-sequential slice. It goes long when J > (K and D), using Python `and` semantics. It exits when the position is in profit and the previous J is above the current J, or when J < (K and D). Entry is a market BUY with a 2xATR(14) stop-loss that rests at the venue as an OTO child. Size follows Jesse's risk_to_qty at 5% risk, capped at 100% of capital.
Iteration 3 fixes only the Layer-3 crash. NautilusTrader rejects an OUO order whose linked_order_ids is empty. The source has no take-profit order to link the stop to, so the stop now links to itself. Linking it to the entry would not work: the entry has already filled when the stop is submitted, and the matching engine rejects an OUO order whose linked order is closed. Every OUO cancel and update path skips closed links, so the self-link has no effect. The stop stays contingent, so the base class still treats it as protective and keeps calling should_exit. I ran a local backtest on LTC daily from 2019-01 to 2024-07. It ran without errors and closed 273 trades, with an average trade of -0.27%, Sharpe -0.24 and max drawdown 43.6%. This is a reproduction port: the results are weak, but judging them is the analyst's job. Signal, sizing and parameters are unchanged.
Verification Results
If rejections show up in the full backtest diagnostics, add a buffer only when the cap binds (for example max_notional_fraction 0.995), or round the quantity down to the instrument's size precision. Record this as an engineering deviation, not a rule change.
Sizing follows Jesse's risk_to_qty: 5% of capital divided by the 2xATR stop distance, times price, capped at 100% of capital. sizing_fee_rate is 0 and sizing_precision is 8, while LTC spot quantity precision is coarser, and make_qty can round the quantity up. When ATR is below about 2.5% of price the cap binds, which happens in calm regimes. A 100%-of-equity market BUY plus the 0.1% fee on a CASH account can then exceed free cash and be rejected. This would silently drop entries, and only in calm regimes. It does not change the frozen source semantics, and the sandbox produced 335 trades, so it is not blocking.
No change required. Keep the override in sync with the base template's guards.
_submit_entry overrides the base method so it can submit an OTO market entry with a reduce-only stop-market child, which mirrors Jesse's self.stop_loss. I compared it with base_template._submit_entry: it keeps the warmup, seed-freeze, pause, market-ready and inflight guards and records the inflight entry. The base's spot-short guard is replaced by an explicit BUY-only check, which is equivalent here. The stop self-links as OUO so NautilusTrader accepts it. This is unusual but harmless: it has no sibling, and the sandbox closed every position cleanly (open_positions 0). Any future change to the base guards will not reach this override.
The sandbox covered 2161 days with 335 closed long trades. avg_trade_return_pct was -0.253% against the +0.25% BINANCE_SPOT floor, with PF 0.865, Sharpe -0.28 (CI -1.11 to 0.47), max drawdown 44% and losses in all three volatility regimes. The developer's own 2019-2024 run agrees: -0.27% per trade over 273 trades. Commissions are 9.25% of gross, so the gross edge is about flat, and the frozen plan has no tunable parameters. The entry fires whenever J > D, which is true on roughly half of all days. That makes it a noisy oscillator-momentum gate holding for about 2.8 days on average, paying 0.20% spot round-trip cost each time. Expect the full-history backtest to confirm no_edge. This concern is about profitability, not correctness, so it does not block.
Backtest Review
- Sharpe
- -0.30
- Total return
- -39.11%
- Max drawdown
- 44.10%
- Trades
- 336
- Win rate
- 46.7%
- Profit factor
- 0.86
The port is faithful and runs cleanly: 336 long-only trades over 2173 days, with no dropped entries, valid accounting, no end-unrealized exposure and a reliable sample.
The trade pattern matches the source logic: entries on J > D, about 44% time in the market, holds of about 2.8 days and no shorts.
avg_trade_return_pct is -0.281% against the +0.25% BINANCE_SPOT floor.
PF is 0.856, Sharpe -0.30 (CI -1.09 to 0.50), total return -39.1%, max drawdown 44.1% against the pre-registered 30%, and the longest drawdown lasted 1005 days.
Six of seven years lost money; only 2020 was positive. All three volatility regimes lost money, and so did five of the six stress windows.
Gross PnL before commissions and impact is about -$10.6k, so the strategy loses money even before costs.
The forecast has no information: IC 0.011, t = 0.5.
Alpha is -11.5% a year, information ratio -0.62 and beta 0.12, so it underperforms LTC buy-and-hold.
The plan has no tunable parameters, so optimization has nothing to change.
Iteration History
Verification failed (Layer 1.5 — hypothesis/config consistency) [class=hypothesis_mismatch]: - optimization_plan fixes leverage at 1.0
Verification failed (Layer 3 — sandbox backtest): `linked_order_ids` was `None` or empty when `contingency_type` OUO Bar type used: LTCUSDT.BINANCE_SPOT-1-DAY-LAST-EXTERNAL, Bars processed: 0 Ensure your strategy produces trades with the given data and parameters.
Outcome Summary
A J > D oscillator gate that is true on about half of all days, with holds of about 2.8 days, carries no predictive information on LTC daily bars and cannot cover a 0.20% spot round-trip cost, so a fixed-default port should not be expected to show an edge.
The backtest-review gate returned 'abandon' for negative expectancy (no edge): avg trade return of -0.281% against the +0.25% BINANCE_SPOT floor, losses before fees, and drawdown above the pre-registered 30%. The plan had no tunable parameters, so optimization could not change the result, and optimization and later stages were never reached.
A faithful, fixed-default port of the Jesse community's MIT-licensed KDJstrategy (SMA-based KDJ 9/3/3 with J=3K-2D, ATR(14) stop) onto LTCUSDT.BINANCE_SPOT daily bars, long-only with no leverage.
Over 2018-08-11 to 2024-07-22 it made 336 long trades with total return -39.1%, Sharpe -0.30 (CI -1.09 to 0.50), profit factor 0.856, max drawdown 44.1% and avg_trade_return_pct -0.281%. Gross PnL before costs was also negative (about -$10.6k), and the signal IC was 0.011 (t=0.5).
Analysis
KDJstrategy
Backtest and paper results are hypothetical. Trading involves risk of loss.