Metadata-Version: 2.4
Name: edenalpha
Version: 0.3.0
Summary: EdenAlpha SDK — write trading strategies in Python and run them against the EdenAlpha engine (hosted backtests and local-driven backtest sessions; one contract shared with paper/live deployments).
Project-URL: Homepage, https://edenalpha.in
Author: EdenAlpha
License: MIT
License-File: LICENSE
Keywords: backtesting,nse,quant,strategies,trading
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.5
Requires-Dist: rich>=13.0
Requires-Dist: typer>=0.12
Description-Content-Type: text/markdown

# edenalpha

Write trading strategies in Python. Run them against the EdenAlpha engine —
the same engine, fills, charges, and risk controls behind every EdenAlpha
backtest, paper deployment, and live deployment. Two ways to backtest:
**hosted**, where your file runs on EdenAlpha compute, and **local sessions**,
where your code stays on your machine and drives the engine bar by bar.
Deployment happens in the web app.

```python
# my_strategy.py
from edenalpha import strategy, Feature

@strategy(features=[Feature(name="rsi", period=14)])
def decide(ctx):
    if ctx.position.is_open and ctx.features["rsi_14"] > 55:
        return "EXIT"
    if not ctx.position.is_open and ctx.features["rsi_14"] < 30:
        return "BUY"
    return "HOLD"
```

```bash
pip install edenalpha
edenalpha login                       # paste an API key from Settings -> API keys
edenalpha backtest my_strategy.py \
    --symbol RELIANCE --timeframe 15m \
    --start 2026-06-01 --end 2026-07-01
```

> **Beta.** The SDK is in open beta and enabled per account: sign up at
> [edenalpha.in](https://edenalpha.in), then request SDK access to have API-key
> creation turned on in Settings. Hosted backtests and local sessions both work
> today; paper deployment from the SDK is not exposed yet (deploy from the web
> app). Python backtests inside the web app need no grant. The `decide` contract
> is stable — code written against 0.2.x keeps working.

## How it works

Your function is the **agent**; EdenAlpha is the **world**. On every bar
close the engine hands you a `Ctx` — the bar, your declared indicators
(computed server-side, identical to the web rule builder), your position,
your cash, and the available bar history — and you answer `BUY`, `SELL`
(opens a short), `EXIT`, or `HOLD`. Hosted backtests expose the full run
through the current bar; paper deployments expose a rolling window from
deployment time. Fills, charges, sizing, stop-losses, and
square-off stay in the engine, so a backtest here is directly comparable to
every other EdenAlpha run. The same strategy contract powers paper
deployments in the EdenAlpha web app (currently operator-gated).

The contract does not change with *where* your code runs: one `decide`
function works hosted, driven locally through `run_local`, or stepped by hand
in your own loop.

- **Sizing is server-owned.** You return direction; quantity comes from the
  run's sizing configuration. A strategy that can't over-size in a backtest
  can't over-size live.
- **Broker-agnostic.** Nothing in this contract names a broker; execution
  routing happens server-side behind your deployment settings.
- **Typed everywhere.** `Ctx`, `Bar`, `Position`, `Decision` are Pydantic
  models with full annotations (`py.typed` shipped) — your IDE's
  autocomplete is the API reference.
- **Unit-testable.** `@strategy` returns a callable: build a fake `Ctx` and
  assert on the returned `Decision` in plain pytest, no network involved.

## Shared-pool portfolio strategies

The web app also supports hosted Python portfolio backtests and paper
deployments over up to 50 symbols. One agent sees the latest state of the
whole universe while EdenAlpha keeps ownership of slots, quantities, fills,
charges, risk, and square-off:

```python
from edenalpha import Feature, Order, portfolio_strategy

@portfolio_strategy(
    features=[Feature(name="rsi", period=14, alias="rsi_14")],
    lookback_bars=50,
)
def decide(pf):
    orders = []
    for view in pf.holdings():
        if view.actionable_this_step and view.features["rsi_14"] > 55:
            orders.append(Order(symbol=view.symbol, action="EXIT"))

    for view in pf.symbols.values():
        rsi = view.features.get("rsi_14")
        if view.actionable_this_step and not view.position.is_open and rsi is not None and rsi < 30:
            orders.append(Order(
                symbol=view.symbol,
                action="BUY",
                score=30 - rsi,
                reason="oversold",
            ))
    return orders
```

Higher `score` proposals are admitted first. With unequal configured slot
weights, higher scores also receive the larger available slots. A remote
`run_local_portfolio` and multi-symbol step-session are not available yet;
create and run portfolio strategies from EdenAlpha's web interface.

## Python API

```python
import edenalpha

client = edenalpha.Client()          # auth: EDENALPHA_API_KEY or `edenalpha login`
outcome = client.backtest(
    "my_strategy.py",
    symbol="RELIANCE", timeframe="15m",
    start="2026-06-01", end="2026-07-01",
)
print(outcome.summary.net_return_pct)
for trade in outcome.trades:
    print(trade["entry_time"], trade["net_pnl"])
```

Errors are typed (`AuthenticationError`, `ScopeError`,
`InsufficientCreditsError`, `RateLimitError`, `StrategyError`,
`TransportError`, `EngineError`) and retriable statuses (429/5xx) are retried
with backoff automatically.

## Hosted execution

`client.backtest(...)` runs your file on EdenAlpha compute next to the data
(requires the `backtest:hosted` scope). Hosted strategies are single
self-contained files with an import allowlist (`numpy`, `pandas`, and the
computation-flavored stdlib). Inside hosted compute, auth is ambient — the
runner injects the session; your code never handles keys.

## Local sessions — your code stays on your machine

A **session** inverts the loop: instead of uploading your file, you drive the
engine one bar at a time over HTTPS. The engine still owns fills, charges,
sizing, and risk — you only answer with a direction. Requires the
`session:backtest` scope.

The simplest form takes the **same `@strategy` file** as `client.backtest`:

```python
outcome = client.run_local(
    "my_strategy.py",
    symbol="RELIANCE", timeframe="15m",
    start="2026-06-01", end="2026-07-01",
)
print(outcome.summary.net_pnl)
```

`decide()` runs on your computer, so local imports, private models, and
anything else you would rather not upload stay put. No import allowlist
applies to code that never leaves your machine.

For research loops — walk-forward, notebooks, policy inference — drive the
session yourself:

```python
from edenalpha import Feature

with client.session(symbol="RELIANCE", timeframe="15m",
                    start="2026-06-01", end="2026-07-01",
                    features=[Feature(name="rsi", period=14)]) as sess:
    t = sess.start()
    while not t.done:
        rsi = t.ctx.features["rsi_14"]
        t = sess.step("BUY" if rsi is not None and rsi < 30 else "HOLD")
    print(sess.result().summary.net_pnl)
```

Each `step()` returns a `Transition`:

- **`t.ctx`** — the next decidable bar. `ctx.history` is rebuilt locally from
  incremental updates, so a long run never retransmits its past.
- **`t.ack`** — what happened to the action you just submitted. Exactly one
  action is accepted per bar sequence number, so a runaway loop cannot fire
  twice for the same bar.
- **`t.events`** — what the *engine* did, as typed events: `FilledEvent`,
  `OrderPendingEvent`, `EntrySkippedEvent`, `ActionIgnoredEvent`. "Accepted"
  is never dressed up as "filled" — with the default `next_open` fill policy
  the fill arrives on the *following* transition.
- **`t.done`** — the run is over and `sess.result()` holds the full outcome.

A session costs the same one backtest credit as any other run, and completed
sessions are saved to your Runs page alongside web and hosted backtests.
Use the context manager (or call `sess.close()`) so an abandoned session
releases its server-side slot promptly.

## Declared features

Any indicator from the EdenAlpha catalog (the same one behind the web rule
builder — Strategies → Reference lists all ~57):

```python
Feature(name="rsi", period=14)                      # ctx.features["rsi_14"]
Feature(name="vwap")                                # ctx.features["vwap"]
Feature(name="sma", period=50, alias="slow_ma")     # ctx.features["slow_ma"]
Feature(name="macd", params={"fast": 12, "slow": 26, "signal": 9},
        outputs={"line": "macd", "signal": "macd_sig", "histogram": "macd_hist"})
```

Raw OHLCV (`open`, `high`, `low`, `close`, `volume`) is always present in
`ctx.features`. Prefer declared features over hand-rolled ones — they're
computed by the exact code that will feed your strategy in paper/live, so
train/serve skew can't happen.

## Requirements & license

Python 3.10+. MIT licensed — the SDK is open; the EdenAlpha engine and
platform are a separate, server-side service.
