diff --git a/src/alpaca.ts b/src/alpaca.ts index b7fe20b..344b21b 100644 --- a/src/alpaca.ts +++ b/src/alpaca.ts @@ -153,17 +153,18 @@ export class Alpaca { } private async waitForFill(orderId: string): Promise { - const maxAttempts = 30; + const maxAttempts = 60; + const pollInterval = 2000; for (let i = 0; i < maxAttempts; i++) { - const order = await this.alpaca.getOrder(orderId); + const order = await this.retry('getOrder', () => this.alpaca.getOrder(orderId)); if (order.status === 'filled') return order; if (order.status === 'canceled' || order.status === 'expired' || order.status === 'rejected') { throw new Error(`Order ${orderId} ${order.status}`); } logger.debug(`order ${orderId} status: ${order.status}, waiting...`); - await new Promise((resolve) => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, pollInterval)); } - throw new Error(`Order ${orderId} not filled after ${maxAttempts}s`); + throw new Error(`Order ${orderId} not filled after ${maxAttempts * pollInterval / 1000}s`); } public async buy(symbol: string, dollarAmount: number): Promise { diff --git a/src/momentum-strategy.ts b/src/momentum-strategy.ts index fc763f2..011bf99 100644 --- a/src/momentum-strategy.ts +++ b/src/momentum-strategy.ts @@ -36,32 +36,35 @@ export class MomentumStrategy implements Strategy { const fill = await alpaca.buy(symbol, capitalAmount); logger.info(`[${this.name}] entered ${symbol} at price ${fill.price}, qty ${fill.qty}`); - const targetPrice = fill.price * (1 + this.config.targetGain); - let deadline = Date.now() + this.config.holdTime; + try { + const targetPrice = fill.price * (1 + this.config.targetGain); + let deadline = Date.now() + this.config.holdTime; - const clock = await alpaca.getClock(); - const safeClose = new Date(clock.next_close).getTime() - 120_000; - if (safeClose < deadline) { - logger.info(`[${this.name}] capping hold time to 2 min before market close`); - deadline = safeClose; - } - - logger.debug(`[${this.name}] monitoring ${symbol} for target price ${targetPrice} or timeout at ${new Date(deadline).toISOString()}`); - let reason: 'target' | 'timeout' = 'timeout'; - - while (Date.now() < deadline) { - await wait(this.config.pollInterval); - - const bid = await alpaca.getLatestBid(symbol); - logger.debug(`${symbol} bid: ${bid} / target: ${targetPrice}`); - if (bid >= targetPrice) { - reason = 'target'; - break; + const clock = await alpaca.getClock(); + const safeClose = new Date(clock.next_close).getTime() - 120_000; + if (safeClose < deadline) { + logger.info(`[${this.name}] capping hold time to 2 min before market close`); + deadline = safeClose; } + + logger.debug(`[${this.name}] monitoring ${symbol} for target price ${targetPrice} or timeout at ${new Date(deadline).toISOString()}`); + let reason: 'target' | 'timeout' = 'timeout'; + + while (Date.now() < deadline) { + await wait(this.config.pollInterval); + + const bid = await alpaca.getLatestBid(symbol); + logger.debug(`${symbol} bid: ${bid} / target: ${targetPrice}`); + if (bid >= targetPrice) { + reason = 'target'; + break; + } + } + + logger.info(`[${this.name}] exit ${symbol} — reason: ${reason}`); + } finally { + logger.info(`[${this.name}] selling ${fill.qty} shares of ${symbol}`); + await alpaca.sell(symbol, fill.qty); } - - logger.info(`[${this.name}] exit ${symbol} — reason: ${reason}`); - - await alpaca.sell(symbol, fill.qty); } }