Files
fit/src/alpaca.ts
Jon 075572a01c Remove executor, add buy/sell to Alpaca, move capital allocation to Bot
Replace the Executor logging placeholder with real buy/sell methods on
the Alpaca class that place market orders via createOrder and return the
fill price. Strategies now receive their capital amount directly and
place orders themselves. Bot accepts StrategyAllocation[] to decouple
capital allocation from strategy definition.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:05:21 -07:00

139 lines
3.7 KiB
TypeScript

import dotenv from 'dotenv';
dotenv.config();
// eslint-disable-next-line @typescript-eslint/no-require-imports
const AlpacaJS = require('@alpacahq/alpaca-trade-api')
if (!process.env.ALPACA_KEY_ID || !process.env.ALPACA_SECRET_KEY) {
throw new Error('Missing ALPACA_KEY_ID or ALPACA_SECRET_KEY in environment');
}
const paper_credentials = {
keyId : process.env.ALPACA_KEY_ID,
secretKey : process.env.ALPACA_SECRET_KEY,
paper : true
}
export interface AlpacaAccount {
cash: string;
}
export interface AlpacaAsset {
symbol: string;
fractionable: boolean;
status: string;
asset_class: string;
}
export interface AlpacaClock {
is_open: boolean;
next_open: string;
next_close: string;
}
export interface AlpacaQuote {
AskPrice: number;
BidPrice: number;
}
export interface AlpacaOrder {
id: string;
symbol: string;
filled_avg_price: string;
filled_qty: string;
side: string;
status: string;
}
export interface AlpacaTrade {
p: number; // price
s: number; // size
t: string; // timestamp
}
export interface AlpacaClient {
getAccount(): Promise<AlpacaAccount>;
getAssets(params: { status: string; asset_class: string }): Promise<AlpacaAsset[]>;
getAsset(symbol: string): Promise<AlpacaAsset>;
getClock(): Promise<AlpacaClock>;
getLatestQuote(symbol: string): Promise<AlpacaQuote>;
getLatestTrades(symbols: string[]): Promise<Map<string, AlpacaTrade>>;
createOrder(order: {
symbol: string;
notional: number;
side: 'buy' | 'sell';
type: string;
time_in_force: string;
}): Promise<AlpacaOrder>;
}
export class Alpaca {
private alpaca: AlpacaClient;
constructor(live = false, client?: AlpacaClient) {
if (client) {
this.alpaca = client;
}
else if (live) {
throw new Error("not doing live yet");
}
else {
this.alpaca = new AlpacaJS(paper_credentials);
}
}
public async getAccount() {
return this.alpaca.getAccount();
}
public async getAssets() {
return this.alpaca.getAssets({
status : 'active',
asset_class : 'us_equity'
});
}
public async getAsset(symbol: string) {
return this.alpaca.getAsset(symbol);
}
public async getClock() {
return this.alpaca.getClock();
}
public async getLatestAsk(symbol: string): Promise<number> {
const quote = await this.alpaca.getLatestQuote(symbol);
return quote.AskPrice;
}
public async getLatestBid(symbol: string): Promise<number> {
const quote = await this.alpaca.getLatestQuote(symbol);
return quote.BidPrice;
}
public async getLatestSpread(symbol: string): Promise<number> {
const quote = await this.alpaca.getLatestQuote(symbol);
return quote.AskPrice - quote.BidPrice;
}
public async getLatestTrades(symbols: string[]) {
return this.alpaca.getLatestTrades(symbols);
}
public async buy(symbol: string, dollarAmount: number): Promise<number> {
const order = await this.alpaca.createOrder({
symbol,
notional: dollarAmount,
side: 'buy',
type: 'market',
time_in_force: 'day',
});
return parseFloat(order.filled_avg_price);
}
public async sell(symbol: string, dollarAmount: number): Promise<number> {
const order = await this.alpaca.createOrder({
symbol,
notional: dollarAmount,
side: 'sell',
type: 'market',
time_in_force: 'day',
});
return parseFloat(order.filled_avg_price);
}
}