Jesse Crypto Trading Framework

repository·master·Indexed 27 days ago

https://github.com/jesse-ai/jesse

An advanced Python-based crypto trading framework for research, backtesting, optimization, and live trading of custom strategies. It features a built-in technical analysis library, an end-to-end machine learning pipeline for strategy gating, and a high-performance backtesting engine optimized with jesse-rust. The framework includes tools for hyperparameter optimization, candle data management, and detailed performance reporting via a dedicated application server and MCP resources.

Tokens
34.5K
Snippets
63
Records
129
Agent score
93%

What's inside Jesse

  1. Understand the Jesse Strategy Execution Model

    master

    Jesse executes strategy logic once per candle, after the candle has closed. The sequence is:

    1. before() runs.
    2. If a position is open: update_position() runs (used for managing stops/targets or liquidating).
    3. If no position is open:
      • If entry orders are pending: should_cancel_entry() is checked. If True, pending orders are cancelled.
      • If no orders are pending: should_long() and should_short() are checked. If they pass, go_long() or go_short() are called to set self.buy or self.sell. If filters() are defined, they must all pass before orders are submitted.
    4. after() runs.
    5. update_chart() runs (after completed candles in backtests, or ~once per second on forming candles in live/paper sessions).

    Note: should_long, should_short, and should_cancel_entry are not called while a position is open; only update_position runs in that state.

  2. Understand Jesse MCP usage limits and metering

    master

    The Jesse MCP server meters expensive research-run tools to manage resource usage for different user plans.

    Metered Tools

    Each of these tools costs 1 credit per execution:

    • run_backtest
    • run_significance_test
    • run_monte_carlo
    • run_optimization

    All other operations (reading configs, candles, strategies, indicators, status polls, logs, etc.) are FREE and unmetered.

    Daily Budgets (UTC)

    • Guests (no license token): 0 credits (requires an account to run research tools).
    • Free users (logged-in): 100 credits per day.
    • Paid plans: Unlimited.

    Plan Determination

    User plans are determined by calling the Jesse API (POST {JESSE_API_URL}/v2/user-info).

    • Any plan that is not free or guest is treated as a paid plan and has unlimited access.
    • If the plan cannot be determined due to a backend error, the system fails open, allowing the run to proceed to avoid blocking legitimate users.
  3. Understand the Backtest Performance Overhaul

    master

    The Jesse backtesting engine has undergone a performance overhaul, achieving up to a 2.88x overall speedup. The improvements are split between jesse-rust (core kernels) and jesse (Python call-sites).

    Performance Gains:

    • Fast Mode (fast_mode=True): Skips the simulator. Typically sees 2.1x–2.5x speedup. This is the default for Dashboard and MCP users.
    • Step Mode (fast_mode=False): Full per-minute simulator. Typically sees 2.4x–4.5x speedup.

    Correctness Guarantee: All optimizations are bit-identical to the baseline. Correctness is verified using:

    • Fingerprint Gate: A trades_hash (sha256 over every closed trade's prices, quantity, fees, and timestamps) and key metrics must match the baseline exactly.
    • Regression Tests: Real-strategy tests ensure that complex multi-timeframe systems produce identical results to the pre-optimization version.
  4. Implement Multi-Timeframe Analysis

    master

    Higher-timeframe candle arrays are not automatically provided. You must implement them using self.get_candles() as a @property. Jesse handles lookahead bias internally, so you do not need to manually shift higher-timeframe values.

    @property
    def candles_6h(self):
        return self.get_candles(self.exchange, self.symbol, '6h')
    
    @property
    def big_trend(self):
        # Using 1D candles to determine trend
        k, d = ta.srsi(self.get_candles(self.exchange, self.symbol, '1D'))
        return 1 if k > d else -1 if k < d else 0
  5. Guidelines for using `update_config` in MCP agents

    master

    The update_config tool is strictly reserved for user-driven configuration changes (e.g., when a user explicitly asks to change a balance, fee, or leverage).

    Prohibited uses for agents:

    • Bug Workarounds: Do not use update_config to patch configuration shapes or missing keys to resolve MCP tool errors (like KeyError or config-shape mismatches). If a tool fails, surface the exact error to the user.
    • Field Injection: Do not inject fields required by specific runners (e.g., MonteCarloRunner or Optimize runner) into the user's config. The MCP layer is responsible for providing the correct configuration shape to the runner.
    • Silent Mutation: Never change user-owned values (balance, fee, leverage, exchange selection) without explicit user instruction.

    Note: While the server performs a recursive merge on update_config payloads to prevent wiping other sections, this does not authorize agent-driven rewrites.

  6. Detect strategy overfitting using Candles MC

    master

    To determine if a strategy is overfit, compare the original backtest metric against the distribution of candles MC scenarios. For higher-is-better metrics (e.g., sharpe_ratio, net_profit_percentage, win_rate, calmar_ratio), use this rule:

    Where original fallsVerdict
    original > best_5Overfit / suspect. The backtest is in the luckiest 5% tail. Do not trust.
    best_5 >= original > medianBorderline. Above median but within plausible range.
    original ≈ medianGood. The backtest is representative of typical outcomes.
    original < medianFantastic. The result is conservative compared to typical outcomes.

    Note: worst_5 (the 5th-percentile) is used to assess the downside tail (e.g., "how bad does it get?") rather than overfitting.

  7. Handle sequential data for indicators

    master

    When using indicators that support sequential mode, set sequential=True to receive a full series of values as a numpy array. This is useful for accessing historical values for trend analysis or debugging.

    • sequential=True: Returns a numpy array of all indicator values.
    • sequential=False (default): Typically returns only the current value.
    # Returns a numpy array of all RSI values
    rsi_series = ta.rsi(self.candles, period=14, sequential=True)
    
    # Access current value
    current_rsi = rsi_series[-1]
    
    # Access previous values for trend analysis
    prev_rsi = rsi_series[-2]
  8. Configure Strategy Hyperparameters for Optimization

    master

    To use Jesse's genetic algorithm optimization, define a hyperparameters() method that returns a list of dictionaries. Each dictionary defines a parameter's name, type, range, and default value.

    Supported Types:

    • int
    • float
    • 'categorical' (requires options list)

    Access these values during strategy execution via self.hp['parameter_name'].

    def hyperparameters(self) -> list:
        return [
            {'name': 'sma_period', 'type': int, 'min': 10, 'max': 200, 'default': 50},
            {'name': 'stop_loss', 'type': float, 'min': 1, 'max': 5, 'step': 0.1, 'default': 2.5},
            {'name': 'trend_method', 'type': 'categorical', 'options': ['supertrend', 'ema'], 'default': 'supertrend'},
        ]
    
    @property
    def sma(self):
        # Accessing an optimized hyperparameter
        return ta.sma(self.candles, self.hp['sma_period'])
  9. Interpret Trades MC results

    master

    Trades MC shuffles the order of trades but does not change the trades themselves. Therefore:

    • Invariant Metrics: total_return, win_rate, and other non-path-dependent metrics are noise in Trades MC. Do not report their percentiles or use them for overfit detection.
    • Informative Metric: Only max_drawdown (and derived calmar_ratio) is meaningful. It answers: "How bad could the equity-curve drawdown have been if the wins and losses arrived in a different order?"

    Reporting Rule: For Trades MC, report only max_drawdown percentiles (original, worst_5, median, best_5).

  10. Ensure sufficient candle data for indicators

    master

    To prevent errors when calculating indicators (like RSI or Bollinger Bands), always verify that the available candle count meets the indicator's required period. Use max() to determine the minimum required candles if using multiple indicators.

    # CORRECT
    def should_long(self):
        required_candles = max(self.rsi_period, self.bb_period, self.slow_ma_period)
        if len(self.candles) < required_candles:
            return False
        return rsi < 30
  11. Standard Hyperparameter Optimization Workflow

    master

    Follow this pattern to run an optimization and check for results:

    1. Create a draft with contiguous training and testing windows.
    2. Run the optimization.
    3. Poll the session status using an adaptive interval (e.g., every 20-60 seconds) until the status is finished, stopped, or terminated.
    4. Analyze the best_candidates for the smallest degradation between training and testing metrics.

    If the session stopped due to missing candles, use import_candles() for both windows and then call rerun_optimization(session_id).

    # 1. Stage the optimization
    draft = create_optimization_draft(
        exchange="Binance Perpetual Futures",
        routes='[{"exchange":"Binance Perpetual Futures","strategy":"MyStrategy","symbol":"BTC-USDT","timeframe":"4h"}]',
        training_start_date="2023-01-01", training_finish_date="2024-06-01",
        testing_start_date="2024-06-01",  testing_finish_date="2024-12-01",
        objective_function="sharpe", trials=100,
        hypothesis="MyStrategy's EMA periods generalize out-of-sample.",
    )
    sid = draft["session_id"]
    
    # 2. Fire it
    run_optimization(sid)
    
    # 3. Keep checking until terminal
    while True:
        s = get_optimization_session(sid)
        status = s["data"]["session"]["status"]
        if status in ("finished", "stopped", "terminated"):
            break
        time.sleep(20)
    
    # 4. Pick the candidate that generalizes best
    session = s["data"]["session"]
    if status == "finished":
        cands = session["best_candidates"]
        # Analyze candidates...
  12. Implement multi-route coordination using `self.shared_vars`

    master

    In Jesse, you can coordinate logic across multiple routes (different symbols) within a single strategy run using self.shared_vars. This is a dictionary shared across every route in the run.

    To implement a multi-route strategy (like Pairs Trading):

    1. Leading Route: Perform calculations (e.g., cointegration, z-score) and write decisions into self.shared_vars.
    2. Follower Routes: Read the keys set by the leading route in should_long, should_short, or update_position to mirror the actions.

    Useful utility functions for multi-route math:

    • utils.prices_to_returns(...)
    • utils.z_score(...)
    • utils.are_cointegrated(...)
    • utils.calculate_alpha_beta(...) to determine margin weighting between legs.