- Move API keys from hardcoded values to .env via dotenv - Extract business logic into src/trading.ts with dependency injection - Add typed AlpacaClient interface, replace `any` on Alpaca class - Add Vitest test suites for trading logic and Alpaca wrapper (14 tests) - Set up ESLint with @typescript-eslint for linting - Fix getAsset to pass symbol string directly to SDK - Fix typo (alpacha -> alpaca), remove unused ws/node-fetch deps - Update .gitignore for node_modules and .env Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
93 lines
2.3 KiB
TypeScript
93 lines
2.3 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 {
|
|
ap: number; // ask price
|
|
bp: number; // bid price
|
|
}
|
|
|
|
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>>;
|
|
}
|
|
|
|
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 getLatestQuote(symbol: string) {
|
|
return this.alpaca.getLatestQuote(symbol);
|
|
}
|
|
public async getLatestTrades(symbols: string[]) {
|
|
return this.alpaca.getLatestTrades(symbols);
|
|
}
|
|
|
|
} |