Merge branch '15-use-interfaces-instead-of-classes-where-possible' into 'main'

Resolve "Use interfaces instead of classes where possible"

Closes #15

See merge request finvis/usxi!14
This commit is contained in:
Carter Bertolini 2023-11-10 17:24:05 -05:00
commit 5450f48820
8 changed files with 110 additions and 153 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@finvis/usxi", "name": "@finvis/usxi",
"version": "1.0.0", "version": "1.1.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1,6 +1,10 @@
import Alpaca from '@alpacahq/alpaca-trade-api'; import Alpaca from '@alpacahq/alpaca-trade-api';
import { Action, ActionSide, ActionFetchOptions, ActionFetchResponse, ActionDateType } from '../interface/actions'; import { ActionSide, ActionFetchOptions, ActionFetchResponse, ActionDateType } from '../interface/actions';
/**
* The activity object returned by Alpaca.
* @see https://docs.alpaca.markets/reference/getaccountactivitiesbyactivitytype
*/
interface AlpacaActivity { interface AlpacaActivity {
id: string; id: string;
activity_type: string; activity_type: string;
@ -29,20 +33,20 @@ export class AlpacaActionProvider {
pageSize: options.pageSize, pageSize: options.pageSize,
pageToken: undefined, pageToken: undefined,
}) as Promise<AlpacaActivity[]>).then((activities) => { }) as Promise<AlpacaActivity[]>).then((activities) => {
return new ActionFetchResponse( return {
activities actions: activities
.filter((activity) => activity.order_status === "filled") .filter((activity) => activity.order_status === "filled")
.map((activity) => { .map((activity) => {
return new Action( return {
activity.symbol, symbol: activity.symbol,
parseInt(activity.qty, 10), quantity: parseInt(activity.qty, 10),
activity.side === "buy" ? ActionSide.Buy : ActionSide.Sell, side: activity.side === "buy" ? ActionSide.Buy : ActionSide.Sell,
parseFloat(activity.price), pricePerShare: parseFloat(activity.price),
new Date(activity.transaction_time), timestamp: new Date(activity.transaction_time),
); };
}), }),
undefined fetchNextPage: undefined
); };
}).catch((err) => { }).catch((err) => {
return err; return err;
}); });

View File

@ -1,29 +1,29 @@
import Alpaca from '@alpacahq/alpaca-trade-api'; import Alpaca from '@alpacahq/alpaca-trade-api';
import { PortfolioProvider, Portfolio, Position } from '../interface/portfolio'; import { PortfolioProvider, Portfolio } from '../interface/portfolio';
/** /**
* The position object returned by Alpaca. * The position object returned by Alpaca.
* @see https://alpaca.markets/docs/api-references/trading-api/positions/#properties * @see https://alpaca.markets/docs/api-references/trading-api/positions/#properties
*/ */
class AlpacaPosition { interface AlpacaPosition {
asset_id!: string; asset_id: string;
symbol!: string; symbol: string;
exchange!: string; exchange: string;
asset_class!: string; asset_class: string;
avg_entry_price!: string; avg_entry_price: string;
qty!: string; qty: string;
qty_available!: string; qty_available: string;
side!: string; side: string;
market_value!: string; market_value: string;
cost_basis!: string; cost_basis: string;
unrealized_pl!: string; unrealized_pl: string;
unrealized_plpc!: string; unrealized_plpc: string;
unrealized_intraday_pl!: string; unrealized_intraday_pl: string;
unrealized_intraday_plpc!: string; unrealized_intraday_plpc: string;
current_price!: string; current_price: string;
lastday_price!: string; lastday_price: string;
change_today!: string; change_today: string;
asset_marginable!: string; asset_marginable: string;
} }
/** /**
@ -40,15 +40,17 @@ export class AlpacaPortfolioProvider implements PortfolioProvider {
*/ */
readonly fetchPortfolio = (): Promise<Portfolio> => { readonly fetchPortfolio = (): Promise<Portfolio> => {
return (this.alpaca.getPositions() as Promise<AlpacaPosition[]>).then((positions) => { return (this.alpaca.getPositions() as Promise<AlpacaPosition[]>).then((positions) => {
return new Portfolio(positions.map((position) => { return {
return new Position( positions: positions.map((position) => {
position.symbol, return {
parseInt(position.qty, 10), symbol: position.symbol,
parseFloat(position.market_value), quantity: parseInt(position.qty, 10),
parseFloat(position.cost_basis), marketValue: parseFloat(position.market_value),
parseFloat(position.market_value) costBasis: parseFloat(position.cost_basis),
); pricePerShare: parseFloat(position.avg_entry_price)
})); };
})
};
}); });
}; };

View File

@ -17,14 +17,14 @@ export class AlpacaQuoteProvider implements QuoteProvider {
*/ */
readonly fetchQuote = (symbol: string): Promise<Quote> => { readonly fetchQuote = (symbol: string): Promise<Quote> => {
return this.alpaca.getLatestQuote(symbol).then((quote) => { return this.alpaca.getLatestQuote(symbol).then((quote) => {
return new Quote( return {
quote.Symbol, symbol: quote.Symbol,
quote.AskPrice, askPrice: quote.AskPrice,
quote.AskSize, askSize: quote.AskSize,
quote.BidPrice, bidPrice: quote.BidPrice,
quote.BidSize, bidSize: quote.BidSize,
new Date(Date.parse(quote.Timestamp)) timestamp: new Date(Date.parse(quote.Timestamp))
); };
}).catch((err) => { }).catch((err) => {
return err; return err;
}); });

View File

@ -9,7 +9,8 @@ export const enum ActionSide {
/** /**
* Represents an action taken on a stock, such as a buy or sell order. * Represents an action taken on a stock, such as a buy or sell order.
*/ */
export class Action { export interface Action {
/** /**
* The symbol of the asset being traded. * The symbol of the asset being traded.
*/ */
@ -34,23 +35,6 @@ export class Action {
* The timestamp of the action * The timestamp of the action
*/ */
readonly timestamp: Date; readonly timestamp: Date;
/**
* Represents a user Action.
* @constructor
* @param {string} symbol - The symbol of the asset being traded.
* @param {number} quantity - The quantity of the asset being traded.
* @param {ActionSide} side - The side of the trade (buy or sell).
* @param {number} pricePerShare - The price per share of the asset being traded.
* @param {Date} timestamp - The timestamp of the action.
*/
constructor(symbol: string, quantity: number, side: ActionSide, pricePerShare: number, timestamp: Date) {
this.symbol = symbol;
this.quantity = quantity;
this.side = side;
this.pricePerShare = pricePerShare;
this.timestamp = timestamp;
}
} }
/** /**
@ -66,7 +50,8 @@ export const enum ActionDateType {
/** /**
* Represents options for a date filter in an action fetch. * Represents options for a date filter in an action fetch.
*/ */
export class ActionDateOptions { export interface ActionDateOptions {
/** /**
* The date to filter on. * The date to filter on.
*/ */
@ -76,22 +61,13 @@ export class ActionDateOptions {
* The type of date filter to use. * The type of date filter to use.
*/ */
readonly dateType: ActionDateType; readonly dateType: ActionDateType;
/**
* Creates a new ActionDateOptions instance.
* @param date The date to filter on.
* @param dateType The type of date filter to use.
*/
constructor(date: Date, dateType: ActionDateType) {
this.date = date;
this.dateType = dateType;
}
} }
/** /**
* Represents the options for fetching actions. * Represents the options for fetching actions.
*/ */
export class ActionFetchOptions { export interface ActionFetchOptions {
/** /**
* The number of items to fetch per page. * The number of items to fetch per page.
*/ */
@ -101,23 +77,13 @@ export class ActionFetchOptions {
* The date options for filtering actions. * The date options for filtering actions.
*/ */
readonly dateOptions?: ActionDateOptions; readonly dateOptions?: ActionDateOptions;
/**
* Creates a set of options for an Action fetch.
* @constructor
* @param pageSize - The size of the page if paging is desired.
* @param dateOptions - The options for Date filtering.
*/
constructor(pageSize?: number, dateOptions?: ActionDateOptions) {
this.pageSize = pageSize;
this.dateOptions = dateOptions;
}
} }
/** /**
* Represents the response of a fetch action request. * Represents the response of a fetch action request.
*/ */
export class ActionFetchResponse { export interface ActionFetchResponse {
/** /**
* An array of `Action` objects. * An array of `Action` objects.
*/ */
@ -128,17 +94,6 @@ export class ActionFetchResponse {
* Returns a promise that resolves to an `ActionFetchResponse` object. * Returns a promise that resolves to an `ActionFetchResponse` object.
*/ */
readonly fetchNextPage?: () => Promise<ActionFetchResponse>; readonly fetchNextPage?: () => Promise<ActionFetchResponse>;
/**
* Creates an instance of the Actions class.
* @constructor
* @param actions The list of actions.
* @param fetchNextPage A function that fetches the next page of actions.
*/
constructor(actions: Action[], fetchNextPage?: () => Promise<ActionFetchResponse>) {
this.actions = actions;
this.fetchNextPage = fetchNextPage;
}
} }
/** /**

View File

@ -1,7 +1,8 @@
/** /**
* Represents a financial position in a portfolio. * Represents a financial position in a portfolio.
*/ */
export class Position { export interface Position {
/** /**
* The symbol name of the asset * The symbol name of the asset
*/ */
@ -26,43 +27,17 @@ export class Position {
* The current asset price per share * The current asset price per share
*/ */
readonly pricePerShare: number; readonly pricePerShare: number;
/**
* Creates a new Portfolio object.
* @constructor
* @param {string} symbol - The symbol of the asset in the portfolio.
* @param {number} quantity - The quantity of the asset in the portfolio.
* @param {number} marketValue - The market value of the asset in the portfolio.
* @param {number} costBasis - The cost basis of the asset in the portfolio.
* @param {number} pricePerShare - The price per share of the asset in the portfolio.
*/
constructor(symbol: string, quantity: number, marketValue: number, costBasis: number, pricePerShare: number) {
this.symbol = symbol;
this.quantity = quantity;
this.marketValue = marketValue;
this.costBasis = costBasis;
this.pricePerShare = pricePerShare;
}
} }
/** /**
* Represents a portfolio of financial positions. * Represents a portfolio of financial positions.
*/ */
export class Portfolio { export interface Portfolio {
/** /**
* An array of positions held in the portfolio. * An array of positions held in the portfolio.
*/ */
readonly positions: Position[]; readonly positions: Position[];
/**
* Creates a new Portfolio instance.
* @constructor
* @param {Position[]} positions - An array of Position objects representing the positions in the portfolio.
*/
constructor(positions: Position[]) {
this.positions = positions;
}
} }
/** /**

View File

@ -1,22 +1,37 @@
/** /**
* Represents a stock quote. * Represents a stock quote.
*/ */
export class Quote { export interface Quote {
readonly symbol: string;
readonly askPrice: number;
readonly askSize: number;
readonly bidPrice: number;
readonly bidSize: number;
readonly timeStamp: Date;
constructor(symbol: string, askPrice: number, askSize: number, bidPrice: number, bidSize: number, timeStamp: Date) { /**
this.symbol = symbol; * The symbol of the asset
this.askPrice = askPrice; */
this.askSize = askSize; readonly symbol: string;
this.bidPrice = bidPrice;
this.bidSize = bidSize; /**
this.timeStamp = timeStamp; * The ask price of the asset
} */
readonly askPrice: number;
/**
* The ask size of the asset
*/
readonly askSize: number;
/**
* The bid price of the asset
*/
readonly bidPrice: number;
/**
* The bid size of the asset
*/
readonly bidSize: number;
/**
* The timestamp of the quote
*/
readonly timeStamp: Date;
} }
/** /**

View File

@ -1,6 +1,6 @@
import { describe, expect, test } from '@jest/globals'; import { describe, expect, test } from '@jest/globals';
import 'dotenv/config'; import 'dotenv/config';
import { ActionDateOptions, ActionDateType, ActionFetchOptions, AlpacaExchange } from '../src/index'; import { ActionDateType, AlpacaExchange } from '../src/index';
import { createLogger, transports, format } from "winston"; import { createLogger, transports, format } from "winston";
const timeout = 10000; const timeout = 10000;
@ -39,9 +39,15 @@ describe('Alpaca Tests', () => {
expect(process.env.ALPACA_SECRET_KEY).toBeDefined(); expect(process.env.ALPACA_SECRET_KEY).toBeDefined();
const exchange = new AlpacaExchange(process.env.ALPACA_API_KEY!, process.env.ALPACA_SECRET_KEY!, true); const exchange = new AlpacaExchange(process.env.ALPACA_API_KEY!, process.env.ALPACA_SECRET_KEY!, true);
const fetchOptions = new ActionFetchOptions(undefined, new ActionDateOptions(new Date("2023-10-23T13:30:28.163Z"), ActionDateType.On)); const date = new Date("2023-10-23T13:30:28.163Z");
const response = await exchange.actionProvider.fetchActions(fetchOptions); const response = await exchange.actionProvider.fetchActions({
pageSize: undefined,
dateOptions: {
date: date,
dateType: ActionDateType.On
}
});
expect(response).toBeDefined(); expect(response).toBeDefined();
@ -55,9 +61,9 @@ describe('Alpaca Tests', () => {
expect(action.pricePerShare).toBeDefined(); expect(action.pricePerShare).toBeDefined();
expect(action.timestamp).toBeDefined(); expect(action.timestamp).toBeDefined();
expect(action.timestamp.getFullYear()).toBe(2023); expect(action.timestamp.getFullYear()).toBe(date.getFullYear());
expect(action.timestamp.getMonth()).toBe(9); expect(action.timestamp.getMonth()).toBe(date.getMonth());
expect(action.timestamp.getDate()).toBe(23); expect(action.timestamp.getDate()).toBe(date.getDate());
} }
}, timeout); }, timeout);
}); });