EnginePolicies

Defining a policy module

Package a policy as a factory with explicit settings and isolated instance state.

A small policy module can be a factory function in your L3 source. The factory captures configuration and returns a policy object; the framework supplies runtime state when it calls the hooks. No registration API or runtime import is needed.

Extract the quantity guard

In the quickstart, replace maximumQuantity, quantityPolicy, and the original policies declaration with this factory and instance. Keep the existing config, stackKey, app, and intent exports.

function createQuantityPolicy({ id, maximumQuantity }) {
  if (typeof id !== "string" || !id.trim()) {
    throw new Error("A policy ID is required");
  }
  if (!Number.isFinite(maximumQuantity) || maximumQuantity <= 0) {
    throw new Error("maximumQuantity must be a positive finite number");
  }

  return {
    id,
    description: `Limit place/modify quantity to ${maximumQuantity}`,
    intents: ["block"],
    async onFrame({ policyId }) {
      return { policyId, status: "watching", reason: "Quantity guard ready" };
    },
    async onIntent({ policyId, policyState }, request) {
      if (request.source === "manual"
        || !["place_order", "modify_order"].includes(request.kind)) {
        return { policyId, allowed: true, reason: "Outside automated quantity check" };
      }

      const quantity = request.options?.order?.quantity;
      const allowed = typeof quantity === "number"
        && Number.isFinite(quantity)
        && quantity > 0
        && quantity <= maximumQuantity;
      if (!allowed) {
        policyState.rejectedRequests = Number(policyState.rejectedRequests ?? 0) + 1;
      }
      return {
        policyId,
        allowed,
        reason: allowed ? "Quantity within limit" : "Invalid or excessive quantity",
        audit: { quantity: quantity ?? null, maximumQuantity },
      };
    },
  };
}

const policies = [
  createQuantityPolicy({ id: "quantity-limit", maximumQuantity: 2 }),
];

Invalid configuration fails when the module is evaluated. Invalid execution input produces an explicit rejection when the hook runs. These are different failure categories and should be tested separately.

Configuration versus state

maximumQuantity is configuration captured by the factory. rejectedRequests is mutable per-policy state supplied by the stack. Keep counters, timestamps, and decision history in policyState so two instances do not accidentally share closure state.

This counter measures checks rejected by the policy, including repeated attempts. It does not count distinct broker orders or confirmed trade outcomes.

Define the module's contract

Document the factory's required settings, which request kinds it handles, its behavior with missing input, the state fields it owns, and the results callers should expect. For this module:

  • IDs must be non-empty and unique within the stack.
  • The limit is positive and finite, and applies to each place/modify request.
  • Missing or non-numeric quantities are rejected.
  • Manual, close, and cancel requests are outside its automated quantity check.
  • policyState.rejectedRequests is an inspection counter.

Keeping the factory in the L3 source makes the example usable in an editor without file imports. Reusing source across saved strategies is a separate authoring concern; defining a factory does not create a globally available injected engine module.

Continue with Using modules.

On this page