Marketplace Execution Helpers
Marketplace algos can use investflex.marketplace helpers to track every order
and child strategy created during one execution. The helpers make lifecycle
commands predictable: when an operator cancels, suspends, or flats the parent
algo, the algo has one registry it can use to clean up the work it started.
New Marketplace Studio templates already include the helper pattern.
Imports
from investflex.marketplace import (
AlgoContext,
ExecutionReport,
Fill,
ManagedExecution,
MarketplaceAlgo,
PayoffControl,
PositionControl,
TrackedHandle,
)
import {
type AlgoContext,
ExecutionReport,
type Fill,
ManagedExecution,
MarketplaceAlgo,
PayoffControl,
PositionControl,
TrackedHandle,
} from "@investflex/sdk/marketplace";
ManagedExecution
Create one ManagedExecution instance per algo instance.
class MyMarketplaceAlgo(MarketplaceAlgo):
def __init__(self) -> None:
self.execution = ManagedExecution()
class MyMarketplaceAlgo extends MarketplaceAlgo {
readonly execution = new ManagedExecution();
}
Use it when the algo creates orders or child strategies. The helper stores the
returned handle, extracts common ids such as order_id, algo_id,
strategy_id, or id, and keeps payload/status metadata for cleanup and
diagnostics.
order = await self.execution.create_order(
ctx,
symbol=ctx.params.symbol,
side="BUY",
quantity=ctx.params.quantity,
)
child = await self.execution.create_strategy(
ctx,
strategy="TWAP",
symbol=ctx.params.symbol,
quantity=ctx.params.quantity,
)
class MyMarketplaceAlgo extends MarketplaceAlgo {
readonly execution = new ManagedExecution();
override async start(ctx: AlgoContext): Promise<void> {
const order = await this.execution.createOrder(ctx, {
symbol: ctx.params.symbol,
side: "BUY",
quantity: ctx.params.quantity,
});
const child = await this.execution.createStrategy(ctx, {
strategy: "TWAP",
symbol: ctx.params.symbol,
quantity: ctx.params.quantity,
});
}
}
If an order or strategy was created through another helper, register the handle explicitly.
self.execution.register_order(order_response)
self.execution.register_strategy(strategy_response)
execution.registerOrder(orderResponse);
execution.registerStrategy(strategyResponse);
Lifecycle Cleanup
Call cleanup helpers from lifecycle methods before the parent algo stops emitting work.
async def suspend(self, ctx: AlgoContext) -> None:
await self.execution.cancel_all_orders(ctx, reason="parent suspended")
await self.execution.suspend_all_strategies(ctx, reason="parent suspended")
await ctx.lifecycle.heartbeat("suspended")
async def cancel(self, ctx: AlgoContext) -> None:
await self.execution.cancel_all(ctx, reason="parent cancelled")
await ctx.lifecycle.stop("cancelled")
async def cancel_and_flat(self, ctx: AlgoContext) -> None:
await self.execution.cancel_and_flat(
ctx,
symbol=ctx.params.symbol,
reason="cancel and flat requested",
)
await ctx.lifecycle.stop("flat requested")
class MyMarketplaceAlgo extends MarketplaceAlgo {
readonly execution = new ManagedExecution();
override async suspend(ctx: AlgoContext): Promise<void> {
await this.execution.cancelAllOrders(ctx, "parent suspended");
await this.execution.suspendAllStrategies(ctx, "parent suspended");
await ctx.lifecycle.heartbeat("suspended");
}
override async cancel(ctx: AlgoContext): Promise<void> {
await this.execution.cancelAll(ctx, "parent cancelled");
await ctx.lifecycle.stop("cancelled");
}
override async cancelAndFlat(ctx: AlgoContext): Promise<void> {
await this.execution.cancelAndFlat(ctx, {
symbol: ctx.params.symbol,
reason: "cancel and flat requested",
});
await ctx.lifecycle.stop("flat requested");
}
}
For suspend, the marketplace review accepts either canceling open orders or suspending child strategies. Use the behavior that matches the algo:
cancel_all_orders(ctx, reason=...)cancel_all_strategies(ctx, reason=...)suspend_all_strategies(ctx, reason=...)resume_all_strategies(ctx, reason=...)cancel_all(ctx, reason=...)suspend_all(ctx, reason=...)cancel_and_flat(ctx, symbol=..., reason=...)
All cleanup helpers are best effort and idempotent. They return an
ExecutionReport instead of failing on the first rejected action.
ExecutionReport
ExecutionReport records each attempted lifecycle action.
report = await self.execution.cancel_all(ctx, reason="operator cancel")
if not report.ok:
for failure in report.failures:
await ctx.lifecycle.heartbeat(f"cleanup failed: {failure.error}")
const report = await execution.cancelAll(ctx, "operator cancel");
if (!report.ok) {
for (const failure of report.failures) {
await ctx.lifecycle.heartbeat(`cleanup failed: ${failure.error}`);
}
}
Use report.to_dict() when the algo needs to write structured diagnostics.
TrackedHandle
Each tracked order or strategy is stored as a TrackedHandle.
Useful fields:
kind:orderorstrategyhandle_id: extracted platform id when availablestatus: local status such assubmitted,cancelled, orsuspendedpayload: request fields used when the handle was createdis_final: true for final statuses such as cancelled, filled, rejected, expired, done, or closed
Use snapshot() to inspect the current registry.
snapshot = self.execution.snapshot()
open_orders = self.execution.open_orders()
open_strategies = self.execution.open_strategies()
const snapshot = execution.snapshot();
const openOrders = execution.openOrders();
const openStrategies = execution.openStrategies();
PositionControl
PositionControl is a pure helper for calculating position from fills.
fills = [
Fill(symbol="PETR4", side="BUY", quantity=100, price=30.10),
Fill(symbol="PETR4", side="SELL", quantity=40, price=30.30),
]
positions = PositionControl.from_fills(fills)
net = PositionControl.net("PETR4", positions)
const fills: Fill[] = [
{ symbol: "PETR4", side: "BUY", quantity: 100, price: 30.1 },
{ symbol: "PETR4", side: "SELL", quantity: 40, price: 30.3 },
];
const positions = PositionControl.fromFills(fills);
const net = PositionControl.net("PETR4", positions);
In managed runtime templates, prefer live runtime positions when available:
async def current_position(ctx: AlgoContext) -> int:
positions = getattr(getattr(ctx, "execution", None), "positions", None)
if positions is None:
return 0
return int(await positions.net(ctx.params.symbol))
async function currentPosition(ctx: AlgoContext): Promise<number> {
const positions = ctx.execution?.positions;
if (!positions) return 0;
return Number(await positions.net(ctx.params.symbol));
}
PayoffControl
PayoffControl provides local payoff and PnL calculations for author-side
accounting.
positions = PositionControl.from_fills(fills)
realized = PayoffControl.realized_pnl(fills)
unrealized = PayoffControl.unrealized_pnl(positions, {"PETR4": 30.50})
scenario = PayoffControl.scenario(positions["PETR4"], [29.80, 30.00, 30.50])
const positions = PositionControl.fromFills(fills);
const realized = PayoffControl.realizedPnl(fills);
const unrealized = PayoffControl.unrealizedPnl(positions, { PETR4: 30.5 });
const scenario = PayoffControl.scenario(positions.get("PETR4")!, [29.8, 30.0, 30.5]);
Available helpers:
realized_pnl(fills)unrealized_pnl(positions, marks)mark_to_market(position, mark)breakeven(position, fees=0.0)scenario(position, prices)max_loss(position, prices)max_gain(position, prices)
Review Expectations
Marketplace review checks lifecycle methods for cleanup calls. New algos should
use ManagedExecution or the runtime ctx.execution helpers in suspend,
cancel, and cancel_and_flat.
The recommended pattern is:
- create orders and child strategies through
self.execution - call
cancel_all_ordersorsuspend_all_strategiesduringsuspend - call
cancel_allduringcancel - call
cancel_and_flatduringcancel_and_flat - call
ctx.lifecycle.heartbeat(...)orctx.lifecycle.stop(...)after the lifecycle command reaches a stable state