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:
169
src/alpaca.ts
169
src/alpaca.ts
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user