ObjectiveModel

Defined in fynance.models.objective

class ObjectiveModel(net=None, *, n_assets=None, layers=(16, 8), loss=None, optimizer=torch.optim.Adam, lr=1e-3, epochs=80, batch_size=None, shuffle=True, position_fn=torch.tanh, cost=0.0, seed=0)[source]

Bases: object

Train a net to maximize a differentiable financial objective.

Parameters:
nettorch.nn.Module, optional

Architecture mapping a feature matrix (T, F) to a position book (T, N) (N = number of assets; (T, 1) for the single-asset case). Defaults to an MLP built lazily on the first fit (so it learns F and N). Pass any nn.Module (e.g. a TCN/LSTM) to use a custom architecture; a custom net receives X as the 2-D matrix (T, F) (a 3-D panel (T, N, M) is flattened to (T, N*M) first).

n_assetsint, optional

Number of assets N in the position book. None (default) infers it at fit: from the 2nd dimension of a 2-D y (T, N), or from the 2nd dimension of a 3-D X (T, N, M), falling back to 1 for a 1-D y (the single-asset case).

layerstuple of int

Hidden sizes of the default MLP (ignored when net is given).

lossBaseLoss, optional

Differentiable financial loss applied to the strategy returns positions * returns. Defaults to SharpeLoss.

optimizertype[torch.optim.Optimizer]

Optimizer class (default Adam).

lrfloat

Learning rate.

epochsint

Passes over the data per fit. With full-batch (batch_size None) this is the number of optimizer steps; with mini-batches it is epochs * ceil(T / batch_size) steps — far more updates, which the objective usually needs to converge on long series.

batch_sizeint, optional

Train on contiguous mini-batches of this many bars (order preserved so the turnover penalty stays meaningful). None (default) = full batch. Mini-batching is the practical way to actually train on long (e.g. minute) series — full-batch gives only epochs gradient steps total.

shufflebool

When mini-batching, shuffle the order of the contiguous chunks each epoch (rows within a chunk stay ordered). Improves SGD; default True.

position_fncallable

Maps the net output to a position; default tanh (positions in [-1, 1]).

costfloat

Per-bar proportional turnover cost penalized during training (e.g. 0.0026 for 26 bps). When non-zero the objective is computed on the net-of-cost return positions * returns - cost * |Δpositions|, so the net learns to hold positions instead of churning — the anti-churn brick for high-cost / high-frequency settings. Use the same value as the backtest’s ProportionalCost. Default 0 (no penalty, original behaviour).

seedint

Seed for reproducible initialization/training.

Notes

The net is warm-started across successive fit calls (so a walk-forward refit adapts online). Build a fresh model for an independent run.

clone()[source]

A fresh model with the same (deep-copied) weights, trained apart.

The net is deep-copied so the clone starts from identical weights yet shares no parameter storage with the original; its optimizer is built lazily on first training, so fitting the clone leaves the original’s predictions bit-identical. This is the per-asset branch of the pretrain/finetune workflow: pretrain_pooled a shared net, then clone().finetune(...) once per asset.

Returns:
ObjectiveModel

The independent copy.

Examples

>>> import numpy as np
>>> from fynance.models import ObjectiveModel
>>> rng = np.random.default_rng(0)
>>> X = rng.standard_normal((16, 2)).astype("float32")
>>> y = (X[:, 0] * 0.01).astype("float32")
>>> m = ObjectiveModel(layers=(4,), epochs=3, seed=0).fit(X, y)
>>> c = m.clone()
>>> bool(np.allclose(m.predict(X), c.predict(X), atol=1e-7))
True
>>> before = m.predict(X)
>>> _ = c.fit(X, y)                             # train the clone ...
>>> bool(np.array_equal(before, m.predict(X)))  # ... original untouched
True
finetune(X, y, *, freeze_trunk=True, lr=None, epochs=None, **fit_kw)[source]

Continue training from the current weights (no reinitialization).

Unlike fit (which warm-starts but reuses the model’s persistent optimizer and full parameter set), finetune builds a fresh optimizer over the currently trainable parameters, so a different lr or a frozen trunk take effect immediately. It is the adaptation step after pretrain_pooled: pretrain one net on a pool of assets, then finetune a per-asset copy (see clone) on that asset’s own data.

Parameters:
X, yarray-like

One contiguous (X, y) series, same shapes as fit.

freeze_trunkbool, optional

If True (default), freeze every parameter except the head — the last parameterized leaf module (see _head_module); for the default MLP only the final Linear head is trained. If False, all parameters (trunk included) keep training.

lrfloat, optional

Learning rate for the finetuning optimizer. None (default) reuses the model’s lr.

epochsint, optional

Passes over the data. None (default) reuses epochs.

**fit_kw

Per-run overrides of the training hyper-parameters batch_size, shuffle, cost and seed (restored afterwards).

Returns:
ObjectiveModel

self.

Raises:
RuntimeError

If called before the model has been fitted (no weights to continue from).

Examples

>>> import numpy as np
>>> from fynance.models import ObjectiveModel
>>> rng = np.random.default_rng(0)
>>> X = rng.standard_normal((16, 2)).astype("float32")
>>> y = (X[:, 0] * 0.01).astype("float32")
>>> m = ObjectiveModel(layers=(4,), epochs=3, seed=0).fit(X, y)
>>> _ = m.finetune(X, y, epochs=2)      # continue from current weights
>>> m.predict(X).shape
(16, 1)
fit(X, y)[source]

Train the net to maximize the objective of the net-of-cost return.

Parameters:
Xarray-like, shape (T, F) or (T, N, M)

Feature matrix. A 3-D panel (T, N, M) (N assets, M features each) is flattened to (T, N*M) for the default dense net.

yarray-like, shape (T,) or (T, N)

Realized per-bar returns aligned with X (not a supervised label). A 2-D y carries the per-asset returns of the position book; a 1-D y is the single-asset case (N == 1).

Returns:
ObjectiveModel

self.

classmethod load(path)[source]

Reconstruct a model saved by save.

Rebuilds the model from the persisted init config (net, loss, optimizer class, position function and scalar hyper-parameters) and reloads the weights, yielding predictions identical to the saved model. A fresh optimizer is created lazily on the next fit / finetune, so the reloaded model is immediately trainable again.

Parameters:
pathstr or os.PathLike

File written by save.

Returns:
ObjectiveModel

The reconstructed model.

See also

save

the round-trip example lives there.

predict(X)[source]

Return the position book for X, shape (T, N).

Accepts a 2-D X (T, F) or a 3-D panel (T, N, M) (flattened to (T, N*M) for the default net). The output is a position book with one column per asset ((T, 1) in the single-asset case). With the default position_fn (tanh) positions are bounded in [-1, 1]; a custom position_fn may produce unbounded values.

save(path)[source]

Serialize the weights and the full init config to path.

Writes (via torch.save) the net state_dict together with everything needed to reconstruct the model: the net module, the loss, the optimizer class, the position function and the scalar hyper-parameters. Reload with load; the pair round-trips to bit-identical predictions and the reloaded model stays trainable.

Parameters:
pathstr or os.PathLike

Destination file.

Examples

>>> import os, tempfile
>>> import numpy as np
>>> from fynance.models import ObjectiveModel
>>> rng = np.random.default_rng(0)
>>> X = rng.standard_normal((16, 2)).astype("float32")
>>> y = (X[:, 0] * 0.01).astype("float32")
>>> m = ObjectiveModel(layers=(4,), epochs=3, seed=0).fit(X, y)
>>> path = os.path.join(tempfile.mkdtemp(), "objective.pt")
>>> m.save(path)
>>> restored = ObjectiveModel.load(path)
>>> bool(np.allclose(m.predict(X), restored.predict(X), atol=1e-7))
True