diff --git a/supplier/.env.example b/supplier/.env.example index 82569f2..08a8476 100644 --- a/supplier/.env.example +++ b/supplier/.env.example @@ -155,7 +155,7 @@ SURPLUS_UNDERCUT_BPS=10 SURPLUS_MAX_CANDIDATE_ORDER_BOOKS=10 SURPLUS_REPRICE_INTERVAL_MS=300000 SURPLUS_REQUEST_TIMEOUT_MS=120000 -SURPLUS_STOP_AFTER_SETTLED_REQUESTS=1 -# Leave empty until a live earnings record establishes the terminal status. -SURPLUS_SETTLED_STATUSES= +# 0 keeps selling. 1 pauses after the first trade or one offer-cap of spend. +SURPLUS_STOP_AFTER_SETTLED_REQUESTS=0 +SURPLUS_SETTLED_STATUSES=confirmed SURPLUS_STATE_PATH=/var/lib/surplus-manager/state.json diff --git a/supplier/src/surplus/config.ts b/supplier/src/surplus/config.ts index 027a2e3..e0bd30c 100644 --- a/supplier/src/surplus/config.ts +++ b/supplier/src/surplus/config.ts @@ -120,9 +120,9 @@ export function loadSurplusManagerConfig( 300_000, ), stopAfterTrades: boundedInteger( - env.SURPLUS_STOP_AFTER_SETTLED_REQUESTS ?? "1", + env.SURPLUS_STOP_AFTER_SETTLED_REQUESTS ?? "0", "SURPLUS_STOP_AFTER_SETTLED_REQUESTS", - 1, + 0, 1, ), settledStatuses: statusList( diff --git a/supplier/src/surplus/controller.ts b/supplier/src/surplus/controller.ts index 7a1cf0f..e04e508 100644 --- a/supplier/src/surplus/controller.ts +++ b/supplier/src/surplus/controller.ts @@ -453,7 +453,7 @@ export class SurplusSellerController { ? baselineAllowance - allowance.remainingUsdNanos : 0n; const spendCap = parseUsdDecimal(this.config.perOfferCapUsd, "ceil"); - if (providerSpend >= spendCap) { + if (this.config.stopAfterTrades > 0 && providerSpend >= spendCap) { const stopping: SurplusControllerState = { version: 1, phase: "stopping", @@ -476,6 +476,7 @@ export class SurplusSellerController { const book = await this.client.getOrderBook(currentState.intent.model); const ownBookOffer = book.offers.find((offer) => offer.id === currentState.offerId); if ( + this.config.stopAfterTrades > 0 && ownBookOffer && ownBookOffer.trades24h >= currentState.highestTrades24h + this.config.stopAfterTrades diff --git a/tests/unit/surplus-controller.test.ts b/tests/unit/surplus-controller.test.ts index 23fec7e..0cbe800 100644 --- a/tests/unit/surplus-controller.test.ts +++ b/tests/unit/surplus-controller.test.ts @@ -211,10 +211,11 @@ function controller( allowance: { readAllowance(): Promise } = { readAllowance: async () => ALLOWANCE, }, + managerConfig: SurplusManagerConfig = config(), ): SurplusSellerController { let mutation = 0; return new SurplusSellerController({ - config: config(), + config: managerConfig, client, allowance, stateStore: store, @@ -283,6 +284,31 @@ describe("SurplusSellerController", () => { expect(store.state.phase).toBe("awaiting_settlement"); }); + it("keeps selling in continuous mode after trades and provider spend", async () => { + const client = new FakeSurplusClient(); + const store = new MemoryStateStore(); + let remaining = 20_000_000_000n; + const activeController = controller( + client, + store, + { + readAllowance: async () => ({ + ...ALLOWANCE, + remainingUsdNanos: remaining, + }), + }, + { ...config(), stopAfterTrades: 0 }, + ); + await activeController.runOnce(); + client.trades24h = 1; + remaining = 18_700_000_000n; + + await activeController.runOnce(); + + expect(client.offer?.status).toBe("active"); + expect(store.state.phase).toBe("active"); + }); + it("adopts and resumes an inactive offer after an ambiguous create", async () => { const client = new FakeSurplusClient(); client.offer = {