Refactor for TDD workflow with Vitest and ESLint

- 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>
This commit is contained in:
Jon
2026-01-30 13:49:31 -07:00
parent 892305e349
commit 5e06c06987
12 changed files with 3781 additions and 262 deletions

View File

@@ -1,130 +1,93 @@
import ws from 'ws';
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 = {
key : 'PKCSK7Z6WGNF6QDEKEG3',
secret : '89JxFVaJ14BVa1xVzDL6gIZETsFHYl1cnseEVJog'
}
const live_livecredentials = {
key : '', //not yet
secret : ''
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 credentials: {key: string, secret: string};
private api_url : string;
constructor(live = false) {
if (live) {
this.credentials = live_livecredentials;
this.api_url = ''//
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.credentials = paper_credentials;
this.api_url = 'https://paper-api.alpaca.markets';
this.alpaca = new AlpacaJS(paper_credentials);
}
}
private get(url: string) {
//probably options
return fetch(this.api_url + url, {
method: 'GET',
headers: {
'APCA-API-KEY-ID': this.credentials.key,
'APCA-API-SECRET-KEY': this.credentials.secret
}
});
}
private data(url: string) {
return fetch( url, {
method: 'GET',
headers: {
'APCA-API-KEY-ID': this.credentials.key,
'APCA-API-SECRET-KEY': this.credentials.secret
}
});
}
public async getAccount() {
const response = await this.get('/v2/account');
return await response.json();
return this.alpaca.getAccount();
}
public async getPositions() {
const response = await this.get('/v2/positions');
return await response.json();
}
public async getOrders() {
const response = await this.get('/v2/orders');
return await response.json();
}
public async getAssets() {
const response = await this.get('/v2/assets');
return await response.json();
return this.alpaca.getAssets({
status : 'active',
asset_class : 'us_equity'
});
}
public async getAsset(symbol: string) {
const response = await this.get('/v2/assets/' + symbol);
return await response.json();
}
public async getWatchlists() {
const response = await this.get('/v2/watchlists');
return await response.json();
}
public async getCalendar() {
const response = await this.get('/v2/calendar');
return await response.json();
return this.alpaca.getAsset(symbol);
}
public async getClock() {
const response = await this.get('/v2/clock');
return await response.json();
return this.alpaca.getClock();
}
public async getAccountConfigurations() {
const response = await this.get('/v2/account/configurations');
return await response.json();
public async getLatestQuote(symbol: string) {
return this.alpaca.getLatestQuote(symbol);
}
public async getAccountActivities() {
const response = await this.get('/v2/account/activities');
return await response.json();
}
public async getPortfolioHistory() {
const response = await this.get('/v2/account/portfolio/history');
return await response.json();
}
public async getOrdersByPath() {
const response = await this.get('/v2/orders');
return await response.json();
}
public async getOrderByPath() {
const response = await this.get('/v2/orders');
return await response.json();
}
public async getOrderByClientOrderId() {
const response = await this.get('/v2/orders');
return await response.json();
}
public async getOrderByID() {
const response = await this.get('/v2/orders');
return await response.json();
}
public async getLatestQuote(symbol: string) {{
const response = await this.data(`https://data.alpaca.markets/v2/stocks/${symbol}/quotes/latest`);
return response.json();
}}
public async getLatestTrades(symbols: string[]) {
console.log(symbols.length)
console.log(symbols.join(','))
const response = await this.data('https://data.alpaca.markets/v2/stocks/trades/latest?symbols=' + symbols.join(','));
return response.json();
return this.alpaca.getLatestTrades(symbols);
}
}