SDK reference¶
The authoring API (signalquarry.sdk) is the stable surface: additive changes only in 0.x.
Strategy authoring API (stable).
A strategy is one pure function::
from signalquarry.sdk import Ctx, Decision, Field, Params, strategy, ta
class P(Params):
symbol: str = "SPY"
period: int = Field(200, ge=20, le=400)
@strategy(params=P, lookback=lambda p: p.period)
def decide(ctx: Ctx, p: P) -> Decision:
close = ctx.bars(p.symbol).close
if close[-1] > ta.sma(close, p.period)[-1]:
return Decision.target({p.symbol: "0.95"}, "PRICE_ABOVE_SMA")
return Decision.target({}, "PRICE_BELOW_SMA")
Params
¶
Bases: BaseModel
Base class for strategy parameters. Field bounds double as the sweep space.
Ctx
dataclass
¶
Everything a strategy may use for one decision.
Bars
dataclass
¶
Completed daily bars for one symbol, oldest first; index -1 is the last completed session.
Arrays are read-only float64 views, point-in-time split-adjusted as of the information cutoff. Missing sessions are NaN.
Decision
dataclass
¶
A strategy's output. Build it with :meth:target, :meth:hold or :meth:unavailable.
hold(*reason_codes, state=None)
classmethod
¶
Keep current holdings unchanged.
target(weights, *reason_codes, state=None)
classmethod
¶
Hold these portfolio weights (fractions of equity). Symbols not listed are sold.
unavailable(*reason_codes)
classmethod
¶
No decision can be made (e.g. missing data). Holdings and state stay unchanged.
FactorCtx
dataclass
¶
A read-only window ending strictly before the decision session.
Columns follow universe order. Missing observations are NaN in numeric
panels and false in present. Prices are raw, without lifecycle adjustment.
The engine constructs this from a verified, truncated panel and an explicit
universe; its membership provenance is checked separately.
FactorDef
dataclass
¶
definition_of(obj)
¶
factor(*, params, lookback)
¶
Declare a factor returning finite scores for covered universe symbols.
factor_definition_of(obj)
¶
Indicators (signalquarry.sdk.ta)¶
Technical-analysis helpers over completed-bar arrays. Pure numpy, no look-ahead.
Each function returns an array aligned with its input; positions without
enough history are NaN. Use [-1] for the value as of the last completed bar.
ema(values, period)
¶
Exponential moving average seeded with the first period-bar SMA.
returns(values, periods=1)
¶
Simple returns over periods bars.
rolling_std(values, period)
¶
Rolling sample standard deviation.
rsi(values, period=14)
¶
Wilder's relative strength index.
sma(values, period)
¶
Simple moving average.
zscore(values, period)
¶
Distance from the rolling mean in rolling standard deviations.
Cross-sectional research (signalquarry.sdk.xs)¶
factor is a pure research API. It requires a separately supplied universe and
does not produce an evidence grade or authorize paper orders. Panel prices are
raw; corporate-action adjustment and historical universe provenance remain
required before point-in-time factor claims.
from signalquarry.sdk import FactorCtx, Params, factor, xs
class Momentum(Params):
lookback: int = 21
@factor(params=Momentum, lookback=lambda p: p.lookback)
def momentum(ctx: FactorCtx, p: Momentum) -> dict[str, float]:
close = ctx.panel("close")
change = close[-1] / close[0] - 1
scores = xs.rank(change)
return {
symbol: float(score)
for symbol, score in zip(ctx.universe, scores, strict=True)
if score == score # NaN means insufficient coverage
}
Run sqy check --factor your_package.momentum --params-json '{"lookback":21}'
from the project to check one decorated factor on synthetic panels. The module
must live under the project and define exactly one factor. The check reports
import policy, score contract, determinism, and future-bar perturbation results.
It imports and runs the authored module, so use an isolated development
environment for untrusted code. It does not establish historical data or
universe provenance.
For a project factor that an agent can discover by ID, add its module to
signalquarry.toml:
[factors]
modules = ["your_package.momentum.factor"]
Place factor.yaml beside factor.py:
schema: signalquarry.factor/v1
id: price-momentum
family: momentum
version: "1"
hypothesis:
statement: Past prices predict next returns.
falsification: Rank IC is nonpositive out of sample.
params:
lookback: 21
evaluation:
horizons: [1, 5, 21]
chronological_blocks: 6
cost_bps: 10
capital: 1000000
trial_budget: 50
holdout:
months: 12
sqy factor ls lists the factor's code-tree and configuration hashes.
sqy check --factor-id price-momentum runs the synthetic checks with the
declared parameters. Editing code, metadata, or parameters changes the
configuration identity. Listing and checking do not record a trial or grant
an evidence grade. The pure factor-trial key binds evaluation settings, data,
dated universe, labels, decision sessions and accepted-factor comparisons.
To calculate descriptive metrics from locally cached data, provide the dataset
ID and one --universe-manifest for each dated membership decision:
sqy factor evaluate --factor price-momentum --dataset-id DATASET \
--universe-manifest data/universe/builds/first.json \
--universe-manifest data/universe/builds/next.json --json
The command re-hashes the selected dataset pages and replays each selected
universe manifest from its cached inputs. Its output has data.scope set to
unverified; consistency checks do not establish provider completeness,
complete corporate-action history, ticker continuity, or delisting outcomes.
It does not append a strategy or factor trial, calculate significance or a
grade, or access a holdout. A controlled real-data evidence path still needs
those controls. All three commands import authored factor code, so inspect
untrusted project code before running it outside an isolated development
environment.
The code hash covers the factor's full top-level project package, including
shared helpers; edits elsewhere in that package also change its identity. The
import policy checks that complete package as well.
Pure cross-sectional operators. NaN marks unavailable observations.
neutralize(values, exposures)
¶
OLS residuals against numeric exposure columns and an intercept.
Rows missing a score or any exposure stay NaN. Rank-deficient designs fail rather than silently returning an arbitrary decomposition.
rank(values)
¶
Average-tie percentile ranks in (0, 1); missing values stay NaN.
zscore(values)
¶
Population z-scores; zero cross-sectional dispersion is unavailable.