npm package: @finkovsky/client
Description: TypeScript client SDK for the Finkovsky API — typed financial analysis, equity data, and economic indicators.
Latest version: 0.2.0

# @finkovsky/client

> TypeScript client SDK for the [Finkovsky API](https://github.com/daiwanwei/finkovsky-api) — typed financial analysis, equity data, and economic indicators.

Built on [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/) (~2 KB, zero dependencies) with types generated by [`openapi-typescript`](https://openapi-ts.dev/).

## Install

### From local tarball

```bash
cd sdk/typescript
npm install && npm run build && npm pack
# Then, in your project:
npm install /path/to/finkovsky-client-0.1.0.tgz
```

### From source (development)

```bash
cd sdk/typescript
npm install
npm run build
```

## Quick Start

```typescript
import { createFinkovskyClient } from "@finkovsky/client";

const client = createFinkovskyClient({
  apiKey: process.env.FINKOVSKY_API_KEY!,
  baseUrl: "https://finkovsky-api.motosan.org", // default: https://api.finkovsky.com
});

// Equity quote — fully typed path and response
const { data, error } = await client.GET("/v1/equity/quote/{symbol}", {
  params: { path: { symbol: "AAPL" } },
});

if (error) {
  console.error("API error:", error);
} else {
  console.log("Quote:", data);
}
```

## Usage Examples

### Technical Analysis

```typescript
// Full analysis (trend, stage2, CANSLIM, patterns, risk)
const { data: analysis } = await client.GET("/v1/ta/analyze/{symbol}", {
  params: { path: { symbol: "AAPL" } },
});

// Crypto analysis
const { data: crypto } = await client.GET("/v1/ta/crypto/analyze/{symbol}", {
  params: { path: { symbol: "BTC" } },
});

// Custom indicators
const { data: indicators } = await client.GET("/v1/ta/indicators/{symbol}", {
  params: {
    path: { symbol: "AAPL" },
    query: { indicators: "sma,rsi" },
  },
});

// Candlestick patterns
const { data: patterns } = await client.GET("/v1/ta/patterns/{symbol}", {
  params: { path: { symbol: "AAPL" } },
});
```

### Screening

```typescript
// Stage 2 (Mark Minervini)
const { data: stage2 } = await client.GET("/v1/ta/screener/stage2/{symbol}", {
  params: { path: { symbol: "AAPL" } },
});
// → { passes: true, score: 5, max_score: 7, checks: { ... } }

// CANSLIM (William O'Neil)
const { data: canslim } = await client.GET("/v1/ta/screener/canslim/{symbol}", {
  params: { path: { symbol: "AAPL" } },
});

// Entry signal
const { data: entry } = await client.GET("/v1/ta/screener/entry/{symbol}", {
  params: { path: { symbol: "AAPL" } },
});

// Batch scan — multiple symbols at once
const { data: scan } = await client.GET("/v1/screener/scan", {
  params: { query: { symbols: "AAPL,NVDA,TSM", filter: "stage2" } },
});

// Universe scan (e.g. S&P 500)
const { data: universe } = await client.GET("/v1/screener/universe/{name}", {
  params: { path: { name: "sp500" }, query: { limit: 10 } },
});
```

### Risk & Position Sizing

```typescript
const { data: risk } = await client.GET("/v1/ta/risk/{symbol}", {
  params: {
    path: { symbol: "NVDA" },
    query: {
      account_value: 100000,
      risk_pct: 1.5,
      stop_pct: 7,
      target_multiple: 3,
    },
  },
});
// → { symbol, entry, stop_price, target, risk_reward_ratio, shares, position_value, ... }
```

### Equity Data

```typescript
// Real-time quote
const { data: quote } = await client.GET("/v1/equity/quote/{symbol}", {
  params: { path: { symbol: "AAPL" } },
});

// OHLCV history
const { data: bars } = await client.GET("/v1/equity/history/{symbol}", {
  params: {
    path: { symbol: "AAPL" },
    query: { interval: "day", limit: 100 },
  },
});

// Company profile
const { data: profile } = await client.GET("/v1/equity/profile/{symbol}", {
  params: { path: { symbol: "AAPL" } },
});
```

### Economic Data

```typescript
// FRED series (GDP, unemployment, etc.)
const { data: gdp } = await client.GET("/v1/economy/fred/{series_id}", {
  params: {
    path: { series_id: "GDP" },
    query: { start_date: "2020-01-01" },
  },
});

// Economic calendar
const { data: calendar } = await client.GET("/v1/economy/calendar", {
  params: { query: { country: "US", importance: "high" } },
});
```

### RAG News Search

```typescript
const { data: news } = await client.GET("/v1/rag/news/search", {
  params: { query: { q: "TSMC earnings", symbol: "TSM", limit: 5 } },
});
// → { results: [{ title, url, score, snippet, published_at }], total }
```

### Taiwan Market

```typescript
// Institutional flow (三大法人)
const { data: flow } = await client.GET("/v1/tw/institutional/{symbol}", {
  params: { path: { symbol: "2330" }, query: { days: 10 } },
});

// Top institutional buys/sells
const { data: top } = await client.GET("/v1/tw/institutional/top", {
  params: { query: { limit: 10, sort: "foreign_net" } },
});

// Margin trading (融資融券)
const { data: margin } = await client.GET("/v1/tw/margin/{symbol}", {
  params: { path: { symbol: "2330" }, query: { days: 30 } },
});
```

## Error Handling

All methods return `{ data, error, response }`. On success `data` is populated; on failure `error` contains the API error:

```typescript
const { data, error, response } = await client.GET("/v1/ta/analyze/{symbol}", {
  params: { path: { symbol: "INVALID" } },
});

if (error) {
  // error: { error: string }
  console.error(`HTTP ${response.status}:`, error.error);
  return;
}

// data is typed based on the endpoint
console.log(data);
```

Common HTTP status codes:

| Status | Meaning |
|--------|---------|
| 200 | Success |
| 401 | Invalid or missing API key |
| 404 | Resource not found (e.g. unknown symbol) |
| 422 | Invalid parameters |
| 429 | Rate limit exceeded |
| 502 | Upstream data provider error |
| 503 | Service temporarily unavailable |

## Type Imports

All response types are re-exported for annotation convenience:

```typescript
import type {
  AnalysisResponse,
  Stage2Response,
  CanslimResponse,
  EntrySignalResponse,
  PatternResponse,
  RiskResponse,
  ScanResponse,
  ErrorResponse,
  HealthResponse,
  RagNewsSearchResponse,
  InstitutionalFlowResponse,
  MarginTradingResponse,
} from "@finkovsky/client";
```

## API Coverage

All 22 REST endpoints from the Finkovsky OpenAPI spec are available as typed paths:

| Category | Endpoints |
|----------|-----------|
| **Health** | `/health`, `/health/live`, `/health/ready`, `/health/workers` |
| **Technical Analysis** | `/v1/ta/analyze/{symbol}`, `/v1/ta/crypto/analyze/{symbol}`, `/v1/ta/indicators/{symbol}`, `/v1/ta/screener/stage2/{symbol}`, `/v1/ta/screener/canslim/{symbol}`, `/v1/ta/screener/entry/{symbol}`, `/v1/ta/patterns/{symbol}`, `/v1/ta/risk/{symbol}` |
| **Equity** | `/v1/equity/quote/{symbol}`, `/v1/equity/history/{symbol}`, `/v1/equity/profile/{symbol}` |
| **Economy** | `/v1/economy/fred/{series_id}`, `/v1/economy/calendar` |
| **Screener** | `/v1/screener/scan`, `/v1/screener/universe/{name}` |
| **RAG** | `/v1/rag/news/search` |
| **Taiwan Market** | `/v1/tw/institutional/{symbol}`, `/v1/tw/institutional/top`, `/v1/tw/margin/{symbol}` |

## Testing

```bash
npm install
npx vitest run
```

Tests cover client construction, auth middleware injection, URL building, query parameter serialization, and response parsing.

## Regenerating Types

When the API schema changes, regenerate `src/openapi.d.ts` from a running API instance:

```bash
# From the repo root (recommended):
make sdk-gen

# Or from this directory:
FINKOVSKY_API_URL=http://localhost:3000 npm run gen
```

The `FINKOVSKY_API_URL` env var defaults to `http://localhost:3000`. You can also use `API_URL` as an alias.

This fetches `/openapi.json` and runs `openapi-typescript` to produce the type definitions. You do **not** need the server running for `npm run build` — only for `npm run gen`.

### CI type-sync check

A GitHub Actions workflow (`.github/workflows/sdk-typecheck.yml`) runs on every PR that touches API route files or the SDK. It:

1. Builds and starts the API server
2. Regenerates `src/openapi.d.ts` from the live `/openapi.json`
3. Fails the build if the generated file differs from the committed version
4. Runs `tsc --noEmit` to verify the SDK compiles

If CI fails with a stale `openapi.d.ts`, start the API locally and run `make sdk-gen`, then commit the updated file.

### Repo-root Makefile targets

| Target | Description |
|--------|-------------|
| `make sdk-gen` | Regenerate `openapi.d.ts` from a running API |
| `make sdk-build` | Install deps + build the SDK |
| `make sdk-pack` | Create a publishable `.tgz` tarball |

## Build Output

```
dist/
  index.js     ESM module
  index.cjs    CommonJS module
  index.d.ts   Type declarations (ESM)
  index.d.cts  Type declarations (CJS)
```

Both ESM (`import`) and CJS (`require`) are supported via the `exports` field in `package.json`.

## License

Same license as the parent repository.