Build a Multi-Model Financial Research Agent for AI Hackathons: Featherless AI and Kraken CLI

Thursday, May 14, 2026 by kimoisteve
Build a Multi-Model Financial Research Agent for AI Hackathons: Featherless AI and Kraken CLI

Introduction

Not all financial tasks need the same model. Analyzing 12 hours of OHLC candles and identifying support levels is a different cognitive job from taking that analysis and committing to a BUY, SELL, or HOLD decision with real money on the line. Yet most AI trading tutorials pick a single model and hand it both jobs.

This tutorial takes a different approach: two specialized open-source LLMs, each doing what it does best, wired together through Featherless AI and Kraken CLI.

This multi-model routing pattern is also a strong foundation for an AI hackathon project, where judges reward systems that demonstrate deliberate reasoning about which model solves which problem. If you are looking for a competition to test it in, lablab.ai's global AI hackathons include tracks focused on autonomous agents and financial AI systems.

Qwen2.5-72B handles research. It reads live ticker data and hourly OHLC candles from Kraken, then produces a structured market analysis covering trend, volatility, support and resistance levels, and key risks.

DeepSeek-V3.2 handles the trading decision. It reads the analysis (not the raw data) and outputs a JSON decision: action, confidence score, risk level, and reasoning.

Kraken CLI executes the result as a paper trade, no real money involved.

The whole thing runs behind a web dashboard where you can hit Run Agent and watch each step complete in real time.

By the end of this tutorial you will have:

  • A working multi-model agent that fetches live BTC/USD data and reasons about it
  • A Featherless API client that can swap between 36,000+ models by changing one environment variable
  • Paper trading execution via Kraken CLI
  • A streaming Flask dashboard showing the analysis and decision as they arrive

Prerequisites

  • Python 3.10 or later
  • A Featherless AI account with an API key (Premium plan required for DeepSeek-V3.2)
  • A Kraken account with an API key and secret
  • Kraken CLI installed. On macOS or Linux with Cargo: cargo install kraken-cli. Pre-built binaries are available on the releases page
  • Basic familiarity with Python and REST APIs

Step 1: Project setup

Clone the finished repo or create the directory structure yourself:

git clone https://github.com/Stephen-Kimoi/featherless-kraken-agent.git
cd featherless-kraken-agent
pip install -r requirements.txt

The requirements.txt contains two dependencies:

openai>=1.0.0
python-dotenv>=1.0.0

Featherless exposes an OpenAI-compatible API, so the openai SDK is all you need to talk to any model in their catalogue. Copy .env.example to .env and fill in your credentials:

FEATHERLESS_API_KEY=your_featherless_api_key

KRAKEN_API_KEY=your_kraken_api_key
KRAKEN_API_SECRET=your_kraken_api_secret
KRAKEN_CLI_PATH=/path/to/kraken        # e.g. /Users/you/.cargo/bin/kraken

RESEARCH_MODEL=Qwen/Qwen2.5-72B-Instruct
TRADING_MODEL=deepseek-ai/DeepSeek-V3.2

TRADING_PAIR=BTC/USD
TRADE_SIZE_USD=500

Initialize a fresh paper trading account with $10,000 in simulated USD:

kraken paper init --balance 10000 --currency USD

Step 2: Fetch live market data with Kraken CLI

The Kraken CLI ships with a built-in paper trading engine and a ticker command that returns real-time market data. The market_data.py module wraps these CLI commands in Python functions.

The internal _run helper (lines 12-22) calls the CLI as a subprocess, passes credentials via environment variables, and parses the JSON response:

def _run(args: list[str]) -> dict | list:
    env = os.environ.copy()
    env["KRAKEN_API_KEY"] = KRAKEN_API_KEY
    env["KRAKEN_API_SECRET"] = KRAKEN_API_SECRET
    result = subprocess.run(
        [KRAKEN] + args + ["--output", "json"],
        capture_output=True, text=True, env=env
    )
    if result.returncode != 0:
        raise RuntimeError(f"kraken CLI error: {result.stderr.strip()}")
    return json.loads(result.stdout)

get_ticker (lines 25-40) normalizes the Kraken API's raw response format into a clean dictionary:

def get_ticker(pair: str) -> dict:
    data = _run(["ticker", pair])
    raw = data.get(pair, {})
    return {
        "pair": pair,
        "ask": raw.get("a", [None])[0],
        "bid": raw.get("b", [None])[0],
        "last": raw.get("c", [None])[0],
        "high_24h": (raw.get("h") or [None, None])[1],
        "low_24h": (raw.get("l") or [None, None])[1],
        "volume_24h": (raw.get("v") or [None, None])[1],
        "open": raw.get("o"),
    }

get_ohlc (lines 43-59) fetches hourly candlestick data and converts the raw arrays (timestamp, open, high, low, close, vwap, volume) into named dictionaries:

def get_ohlc(pair: str, interval: int = 60) -> list[dict]:
    data = _run(["ohlc", pair, "--interval", str(interval)])
    candles = data.get(pair, []) if isinstance(data, dict) else data
    return [
        {
            "timestamp": c[0],
            "open": c[1],
            "high": c[2],
            "low": c[3],
            "close": c[4],
            "vwap": c[5],
            "volume": c[6],
        }
        for c in candles
        if isinstance(c, list) and len(c) >= 7
    ]

The same module also contains paper_buy and paper_sell (lines 75-98), which execute simulated market orders through Kraken's paper trading engine. The --yes flag skips the confirmation prompt so the agent can run unattended.


Step 3: The research layer (Qwen2.5-72B)

agent.py starts by initializing a single Featherless client (lines 15-18). Because Featherless implements the OpenAI API spec, you just swap the base_url:

client = OpenAI(
    api_key=FEATHERLESS_API_KEY,
    base_url="https://api.featherless.ai/v1",
)

The call_model function (lines 21-31) is intentionally model-agnostic. It accepts a model ID as a parameter, which is how the router sends different tasks to different models without duplicating any API code:

def call_model(model: str, system: str, user: str) -> str:
    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": user},
        ],
        temperature=0.3,
        max_tokens=1024,
    )
    return response.choices[0].message.content.strip()

analyze_market (lines 34-69) uses the last 12 hourly candles and the current ticker to build a prompt for Qwen2.5-72B. The system prompt constrains it to a quantitative analyst role, and the user prompt explicitly asks it not to make a trade recommendation. The analysis is the input to the next model, so keeping the two tasks separate prevents the research model from anchoring the trading decision:

def analyze_market(ticker: dict, ohlc: list[dict]) -> str:
    recent_candles = ohlc[-12:] if len(ohlc) >= 12 else ohlc

    ohlc_summary = "\n".join(
        f"  O: {c.get('open')} H: {c.get('high')} L: {c.get('low')} "
        f"C: {c.get('close')} Vol: {c.get('volume')}"
        for c in recent_candles
    )

    prompt = f"""You are a financial market analyst. Analyze the following market data for {TRADING_PAIR} and extract key signals.

Current ticker:
- Ask: {ticker.get('ask')}
- Bid: {ticker.get('bid')}
- Last price: {ticker.get('last')}
- 24h High: {ticker.get('high_24h')}
- 24h Low: {ticker.get('low_24h')}
- 24h Volume: {ticker.get('volume_24h')}

Recent hourly OHLC candles (last 12):
{ohlc_summary}

Provide a concise structured analysis covering:
1. Price trend (bullish/bearish/sideways) with evidence from candles
2. Volatility assessment
3. Volume analysis
4. Support and resistance levels
5. Key risk factors

Keep your analysis factual and data-driven. Do not make a trade recommendation."""

    return call_model(
        RESEARCH_MODEL,
        "You are a quantitative financial analyst. Be precise, data-driven, and concise.",
        prompt,
    )

Step 4: The trading decision layer (DeepSeek-V3.2)

make_trade_decision (lines 72-116) receives the Qwen analysis (not the raw market data) and passes it to DeepSeek-V3.2 along with the current paper account balance. The prompt includes three rules that enforce disciplined behaviour: only BUY with sufficient balance and a clear signal, only SELL when holding the asset, and HOLD when signals are mixed.

DeepSeek is instructed to respond only with JSON:

prompt = f"""You are an algorithmic trading agent making paper trading decisions. You must respond with valid JSON only.

Market analysis for {TRADING_PAIR}:
{analysis}

Current paper account status:
- USD balance: {usd_balance}
- Holdings: {json.dumps(holdings, indent=2)}

Trade budget: ${TRADE_SIZE_USD} USD per trade.

Respond with this exact JSON structure:
{{
  "action": "BUY" | "SELL" | "HOLD",
  "reasoning": "two to three sentence explanation referencing specific signals from the analysis",
  "confidence": 0.0 to 1.0,
  "risk_level": "low" | "medium" | "high"
}}"""

The function then strips any markdown fences that the model might wrap around its JSON response before parsing (lines 108-116):

raw = raw.strip()
if raw.startswith("```"):
    raw = raw.split("```")[1]
    if raw.startswith("json"):
        raw = raw[4:]
return json.loads(raw.strip())

Step 5: Executing paper trades

execute_trade (lines 119-135) converts the trade budget in USD to a BTC volume using the current last price, then routes to paper_buy or paper_sell accordingly. HOLD simply returns None and skips execution:

def execute_trade(decision: dict, ticker: dict) -> dict | None:
    action = decision.get("action", "HOLD")
    last_price = float(ticker.get("last", 0))

    if action == "HOLD" or last_price == 0:
        return None

    volume = round(TRADE_SIZE_USD / last_price, 6)

    if action == "BUY":
        return paper_buy(TRADING_PAIR, volume)
    elif action == "SELL":
        return paper_sell(TRADING_PAIR, volume)

A successful paper trade returns a confirmation from Kraken's engine:

{
  "action": "market_order_filled",
  "cost": 479.24,
  "fee": 1.25,
  "mode": "paper",
  "order_id": "PAPER-00001",
  "pair": "BTCUSD",
  "price": 79873.20,
  "side": "buy",
  "volume": 0.006
}

Step 6: The web dashboard

Running the agent as a CLI script works, but a dashboard lets you see all four steps and the full model outputs without reading terminal logs.

app.py runs each step of the agent in a background thread and streams results to the browser using Server-Sent Events (SSE). The run_agent_stream function (lines 18-50) calls emit() after each step completes, which puts a typed JSON message into a queue:

def run_agent_stream(q: queue.Queue):
    def emit(type: str, **kwargs):
        q.put({"type": type, **kwargs})

    emit("step", index=1, label="Fetching live market data from Kraken...")
    ticker = get_ticker(TRADING_PAIR)
    ohlc = get_ohlc(TRADING_PAIR, interval=60)
    paper_status = get_paper_status()
    emit("market", ticker=ticker, paper=paper_status)

    emit("step", index=2, label="Analyzing market data with Qwen2.5-72B...")
    analysis = analyze_market(ticker, ohlc)
    emit("analysis", text=analysis)

    emit("step", index=3, label="Making trade decision with DeepSeek-V3.2...")
    decision = make_trade_decision(analysis, paper_status)
    emit("decision", **decision)

    emit("step", index=4, label="Executing trade...")
    trade_result = execute_trade(decision, ticker)
    emit("trade", result=trade_result)

    final_status = get_paper_status()
    emit("complete", paper=final_status)

The /api/run route (lines 68-85) drains that queue and yields each message as an SSE frame. The browser's EventSource API picks these up without any WebSocket complexity:

@app.route("/api/run")
def run_agent():
    q = queue.Queue()
    thread = threading.Thread(target=run_agent_stream, args=(q,), daemon=True)
    thread.start()

    def generate():
        while True:
            msg = q.get()
            if msg is None:
                break
            yield f"data: {json.dumps(msg)}\n\n"

    return Response(stream_with_context(generate()), mimetype="text/event-stream")

Start the server:

python app.py

Open http://localhost:5000. This is what you will see on first load:

Dashboard initial state — live BTC/USD price, $10,000 paper balance, and empty analysis panels ready to run

The top row shows three live stats that update every 15 seconds: the current BTC/USD last price with bid and ask spread, your paper account balance, and unrealized P&L across any open positions. The Run Agent button in the top right triggers the full pipeline.

The four-step pipeline

Hit Run Agent and the four step indicators light up in sequence:

All four pipeline steps completed — Fetch market data, Analyze with Qwen2.5, Decide with DeepSeek, Execute paper trade

Each indicator maps directly to a function in the codebase:

  • Step 1 — Fetch market data: get_ticker and get_ohlc call the Kraken CLI to pull the current bid, ask, and last price alongside the last 12 hourly OHLC candles. get_paper_status reads the current paper account balance and open positions.
  • Step 2 — Analyze with Qwen2.5: analyze_market packages the ticker and candle data into a structured prompt and sends it to Qwen/Qwen2.5-72B-Instruct via the Featherless API. The model returns a full market analysis.
  • Step 3 — Decide with DeepSeek: make_trade_decision passes the Qwen analysis (not the raw data) to deepseek-ai/DeepSeek-V3.2. The model returns a JSON object with action, confidence, risk level, and reasoning.
  • Step 4 — Execute paper trade: execute_trade converts the USD trade budget to an asset volume and calls paper_buy or paper_sell via the Kraken CLI. If the decision is HOLD, this step is skipped.

Reading the market analysis

The left panel shows the full Qwen2.5-72B output rendered as formatted markdown:

Market Analysis panel showing bullish trend, volatility assessment, and support/resistance levels identified by Qwen2.5-72B

The analysis is always structured around five sections. Price Trend identifies whether the market is bullish, bearish, or sideways, backed by specific candle evidence (higher highs and higher lows in this run). Volatility Assessment quantifies the 24-hour range and typical hourly candle range. Volume Analysis checks whether volume is confirming or contradicting the price move. Support and Resistance Levels gives the exact price levels where the model expects the market to react, derived from recent swing highs and lows in the candle data. Key Risk Factors flags macro, regulatory, and liquidity risks that could override the technical picture.

This output becomes the sole input to the trading model in the next step — DeepSeek never sees the raw OHLC data, only this summary.

Reading the trade decision and paper trade result

The right panel shows the DeepSeek-V3.2 decision and, if a trade was placed, the Kraken paper trade confirmation:

Trade Decision panel showing BUY at 75% confidence, Medium risk, with reasoning — and the Paper Trade Result confirming PAPER-00001 filled at $81,549

The action badge shows BUY, SELL, or HOLD in color (green for BUY, red for SELL, amber for HOLD). Below it, Confidence is a 0-1 score the model assigns to its own certainty, and Risk Level reflects whether the trade setup is low, medium, or high risk given the current signals. The reasoning box gives the model's plain-language justification, always referencing specific signals from the Qwen analysis above.

When a trade executes, the Paper Trade Result section below shows the Kraken confirmation: order ID, fill price, volume in BTC, total cost in USD, and the exchange fee. In this run, DeepSeek identified a clear bullish breakout approaching resistance at $82,017, placed a BUY for 0.006132 BTC at $81,549, costing $500.06 plus a $1.30 fee.

Here is the completed dashboard after a full successful run:

Completed run — all steps green, paper balance updated to $10,000.42, BUY decision executed, P&L tracking active

The paper balance has moved from $10,000.00 to $10,000.42, reflecting the unrealized gain on the open BTC position as price continued to rise after the trade was placed. All four step indicators are green, confirming a clean end-to-end run.


Frequently Asked Questions

Q: Can I swap the models for other ones on Featherless? Yes. Change RESEARCH_MODEL or TRADING_MODEL in your .env file to any model ID from the Featherless catalogue. The agent uses an OpenAI-compatible client so no code changes are needed — the new model loads on the next run.

Q: Does this agent use real money? No. All trades go through Kraken CLI's built-in paper trading engine (kraken paper), which simulates market orders without touching your real account. You can reset the simulated balance at any time with kraken paper reset --balance 10000 --currency USD --yes.

Q: What trading pairs does Kraken CLI support for paper trading? Any spot pair available on Kraken, including BTC/USD, ETH/USD, SOL/USD, and others. Change the TRADING_PAIR variable in .env to switch pairs. The agent's market data and execution code work the same regardless of the pair.

Q: Is this a good project for an AI hackathon? Yes. The multi-model routing architecture demonstrates deliberate agent design, and the Kraken CLI paper trading engine lets you show live decision-making results to judges without any financial risk. This setup is particularly well-suited to AI agent hackathons that focus on autonomous systems.

Q: How do I add a third model for news sentiment? Call call_model with a third model ID before analyze_market, passing in news headlines from any financial news API. Feed the returned sentiment summary into the analyze_market prompt alongside the OHLC data so the research model has both price and sentiment context.


Conclusion

The key idea in this agent is separation of concerns at the model level. Qwen2.5-72B is strong at structured analysis over long context, which is exactly what you want when summarizing 12 hours of candlestick data. DeepSeek-V3.2 handles the decision, where reasoning under constraints (budget limits, position rules, confidence thresholds) matters more than breadth of knowledge.

Featherless makes this routing practical. Both models run through the same OpenAI-compatible client and you can swap either for any of the 36,000+ models in their catalogue by changing a single line in .env. No infrastructure to manage, no GPU bills, no cold-start latency.

From here you can extend the agent in several directions:

  • Add more pairs by changing TRADING_PAIR or running multiple agent instances in parallel
  • Plug in a third model for sentiment analysis of news headlines before the research step
  • Tighten the decision rules by adding position sizing logic or stop-loss conditions to the trading prompt
  • Move to live trading by removing the paper prefix from the Kraken CLI commands once you are satisfied with the paper results

The full source code is at github.com/Stephen-Kimoi/featherless-kraken-agent.