@gaberoo/kalshitools 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +328 -27
  2. package/dist/commands/config/init.js +4 -4
  3. package/dist/commands/config/show.js +5 -5
  4. package/dist/commands/markets/list.d.ts +5 -1
  5. package/dist/commands/markets/list.js +28 -8
  6. package/dist/commands/markets/orderbook.d.ts +13 -0
  7. package/dist/commands/markets/orderbook.js +83 -0
  8. package/dist/commands/markets/scan.d.ts +18 -0
  9. package/dist/commands/markets/scan.js +237 -0
  10. package/dist/commands/markets/show.d.ts +3 -3
  11. package/dist/commands/markets/show.js +7 -7
  12. package/dist/commands/orders/cancel.d.ts +3 -3
  13. package/dist/commands/orders/cancel.js +7 -7
  14. package/dist/commands/orders/create.d.ts +5 -5
  15. package/dist/commands/orders/create.js +33 -33
  16. package/dist/commands/orders/list.d.ts +1 -1
  17. package/dist/commands/orders/list.js +9 -9
  18. package/dist/commands/portfolio/analytics.d.ts +12 -0
  19. package/dist/commands/portfolio/analytics.js +192 -0
  20. package/dist/commands/portfolio/fills.d.ts +1 -1
  21. package/dist/commands/portfolio/fills.js +7 -7
  22. package/dist/commands/portfolio/history.d.ts +14 -0
  23. package/dist/commands/portfolio/history.js +245 -0
  24. package/dist/commands/portfolio/positions.d.ts +1 -0
  25. package/dist/commands/portfolio/positions.js +11 -2
  26. package/dist/commands/portfolio/risk.d.ts +11 -0
  27. package/dist/commands/portfolio/risk.js +206 -0
  28. package/dist/lib/analytics.d.ts +64 -0
  29. package/dist/lib/analytics.js +236 -0
  30. package/dist/lib/base-command.d.ts +2 -2
  31. package/dist/lib/base-command.js +8 -8
  32. package/dist/lib/config/manager.d.ts +25 -25
  33. package/dist/lib/config/manager.js +51 -51
  34. package/dist/lib/config/schema.d.ts +11 -11
  35. package/dist/lib/config/schema.js +6 -6
  36. package/dist/lib/errors/base.d.ts +10 -10
  37. package/dist/lib/errors/base.js +7 -7
  38. package/dist/lib/kalshi/auth.d.ts +4 -4
  39. package/dist/lib/kalshi/auth.js +24 -24
  40. package/dist/lib/kalshi/client.d.ts +35 -35
  41. package/dist/lib/kalshi/client.js +93 -91
  42. package/dist/lib/kalshi/index.d.ts +1 -1
  43. package/dist/lib/kalshi/index.js +1 -1
  44. package/dist/lib/kalshi/types.d.ts +53 -53
  45. package/dist/lib/logger.js +3 -3
  46. package/dist/lib/output/formatter.d.ts +20 -20
  47. package/dist/lib/output/formatter.js +55 -55
  48. package/dist/lib/retry.d.ts +2 -2
  49. package/dist/lib/retry.js +8 -10
  50. package/dist/lib/risk.d.ts +51 -0
  51. package/dist/lib/risk.js +153 -0
  52. package/dist/lib/sanitize.js +9 -9
  53. package/dist/lib/scanner.d.ts +58 -0
  54. package/dist/lib/scanner.js +160 -0
  55. package/dist/lib/shutdown.d.ts +4 -4
  56. package/dist/lib/shutdown.js +7 -7
  57. package/dist/lib/validation.d.ts +5 -5
  58. package/dist/lib/validation.js +14 -20
  59. package/docs/TRADING_STRATEGIES.md +538 -0
  60. package/oclif.manifest.json +559 -170
  61. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../lib/base-command.js';
2
+ export default class PortfolioAnalytics extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'max-ts': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'min-ts': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ period: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,192 @@
1
+ import { Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { calculateAveragePnL, calculateExposureRatio, calculatePortfolioConcentration, calculateTotalFees, calculateWinRate, } from '../../lib/analytics.js';
4
+ import { BaseCommand } from '../../lib/base-command.js';
5
+ import { createClientFromConfig } from '../../lib/kalshi/index.js';
6
+ import { logger } from '../../lib/logger.js';
7
+ export default class PortfolioAnalytics extends BaseCommand {
8
+ static description = 'Comprehensive portfolio performance analytics';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %>',
11
+ '<%= config.bin %> <%= command.id %> --period 7',
12
+ '<%= config.bin %> <%= command.id %> --min-ts 2026-01-01T00:00:00Z',
13
+ '<%= config.bin %> <%= command.id %> --json',
14
+ ];
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ 'max-ts': Flags.string({
18
+ description: 'End date for analysis (ISO-8601 format)',
19
+ }),
20
+ 'min-ts': Flags.string({
21
+ description: 'Start date for analysis (ISO-8601 format)',
22
+ }),
23
+ 'period': Flags.integer({
24
+ description: 'Analysis period in days (overridden by min-ts/max-ts)',
25
+ }),
26
+ };
27
+ async run() {
28
+ const { flags } = await this.parse(PortfolioAnalytics);
29
+ try {
30
+ const client = createClientFromConfig();
31
+ // Calculate time range
32
+ const now = new Date();
33
+ let minTs;
34
+ let maxTs;
35
+ let periodDays = flags.period || 30;
36
+ if (flags['min-ts']) {
37
+ minTs = Math.floor(new Date(flags['min-ts']).getTime() / 1000);
38
+ }
39
+ else if (flags.period) {
40
+ minTs = Math.floor((now.getTime() - flags.period * 24 * 60 * 60 * 1000) / 1000);
41
+ }
42
+ else {
43
+ // Default to 30 days
44
+ minTs = Math.floor((now.getTime() - 30 * 24 * 60 * 60 * 1000) / 1000);
45
+ }
46
+ if (flags['max-ts']) {
47
+ maxTs = Math.floor(new Date(flags['max-ts']).getTime() / 1000);
48
+ }
49
+ // Calculate actual period if we have both timestamps
50
+ if (minTs && maxTs) {
51
+ periodDays = Math.floor((maxTs - minTs) / (24 * 60 * 60));
52
+ }
53
+ else if (minTs) {
54
+ periodDays = Math.floor((now.getTime() / 1000 - minTs) / (24 * 60 * 60));
55
+ }
56
+ // Fetch data from API
57
+ const [balance, positions, fillsResult] = await Promise.all([
58
+ client.getBalance(),
59
+ client.getPositions({ settlement_status: 'open' }),
60
+ client.getFills({ limit: 500, max_ts: maxTs, min_ts: minTs }),
61
+ ]);
62
+ const { fills } = fillsResult;
63
+ // Calculate metrics
64
+ const winRate = calculateWinRate(fills);
65
+ const { avgLoss, avgWin } = calculateAveragePnL(fills);
66
+ const totalFees = calculateTotalFees(fills);
67
+ // Portfolio metrics
68
+ const portfolioValue = balance.portfolio_value / 100; // Convert from cents
69
+ const cashBalance = balance.balance / 100;
70
+ const totalCost = positions.reduce((sum, pos) => sum + pos.total_cost, 0);
71
+ const totalExposure = positions.reduce((sum, pos) => sum + pos.market_exposure, 0);
72
+ const realizedPnL = positions.reduce((sum, pos) => sum + pos.realized_pnl, 0);
73
+ const unrealizedPnL = totalExposure - totalCost;
74
+ const exposureRatio = calculateExposureRatio(positions, portfolioValue);
75
+ const concentration = calculatePortfolioConcentration(positions, portfolioValue);
76
+ // Calculate total return
77
+ const initialCapital = portfolioValue - realizedPnL - unrealizedPnL;
78
+ const totalReturnPct = initialCapital > 0 ? ((portfolioValue - initialCapital) / initialCapital) * 100 : 0;
79
+ // Trade statistics
80
+ const totalTrades = fills.length;
81
+ const winningTrades = Math.round(totalTrades * winRate);
82
+ const losingTrades = totalTrades - winningTrades;
83
+ // Concentration by ticker
84
+ const concentrationByTicker = {};
85
+ for (const [ticker, pct] of concentration) {
86
+ concentrationByTicker[ticker] = Math.round(pct * 100) / 100;
87
+ }
88
+ // Find largest position
89
+ const largestPositionPct = concentration.size > 0
90
+ ? Math.max(...concentration.values())
91
+ : 0;
92
+ // Fee impact
93
+ const feeImpactPct = portfolioValue > 0 ? (totalFees / portfolioValue) * 100 : 0;
94
+ if (this.formatter.isJSONMode()) {
95
+ this.formatter.success({
96
+ fees: {
97
+ fee_impact_pct: -Math.abs(feeImpactPct),
98
+ total_fees: totalFees,
99
+ },
100
+ period: {
101
+ days: periodDays,
102
+ end: maxTs ? new Date(maxTs * 1000).toISOString() : now.toISOString(),
103
+ start: new Date(minTs * 1000).toISOString(),
104
+ },
105
+ pnl: {
106
+ avg_loss: avgLoss,
107
+ avg_win: avgWin,
108
+ realized_pnl: realizedPnL,
109
+ total_pnl: realizedPnL + unrealizedPnL,
110
+ total_return_pct: totalReturnPct,
111
+ unrealized_pnl: unrealizedPnL,
112
+ },
113
+ portfolio: {
114
+ cash_balance: cashBalance,
115
+ current_value: portfolioValue,
116
+ exposure: totalExposure,
117
+ exposure_ratio: exposureRatio,
118
+ total_cost: totalCost,
119
+ },
120
+ risk: {
121
+ concentration_by_ticker: concentrationByTicker,
122
+ largest_position_pct: largestPositionPct,
123
+ },
124
+ trades: {
125
+ losing: losingTrades,
126
+ total: totalTrades,
127
+ win_rate: winRate,
128
+ winning: winningTrades,
129
+ },
130
+ });
131
+ }
132
+ else {
133
+ // Human-readable output
134
+ this.log(chalk.cyan.bold('Portfolio Analytics'));
135
+ this.log();
136
+ // Period
137
+ this.log(chalk.yellow('Period:'));
138
+ this.log(` ${periodDays} days (${new Date(minTs * 1000).toLocaleDateString()} - ${maxTs ? new Date(maxTs * 1000).toLocaleDateString() : 'now'})`);
139
+ this.log();
140
+ // Trading Performance
141
+ this.log(chalk.yellow('Trading Performance:'));
142
+ this.log(` Total Trades: ${totalTrades}`);
143
+ this.log(` Win Rate: ${chalk.green((winRate * 100).toFixed(1) + '%')} (${winningTrades}W / ${losingTrades}L)`);
144
+ this.log(` Avg Win: ${chalk.green('+$' + avgWin.toFixed(2))}`);
145
+ this.log(` Avg Loss: ${chalk.red('$' + avgLoss.toFixed(2))}`);
146
+ this.log();
147
+ // P&L
148
+ this.log(chalk.yellow('Profit & Loss:'));
149
+ const totalPnL = realizedPnL + unrealizedPnL;
150
+ this.log(` Total P&L: ${totalPnL >= 0 ? chalk.green('+$' + totalPnL.toFixed(2)) : chalk.red('-$' + Math.abs(totalPnL).toFixed(2))}`);
151
+ this.log(` Realized: ${realizedPnL >= 0 ? chalk.green('+$' + realizedPnL.toFixed(2)) : chalk.red('-$' + Math.abs(realizedPnL).toFixed(2))}`);
152
+ this.log(` Unrealized: ${unrealizedPnL >= 0 ? chalk.green('+$' + unrealizedPnL.toFixed(2)) : chalk.red('-$' + Math.abs(unrealizedPnL).toFixed(2))}`);
153
+ this.log(` Total Return: ${totalReturnPct >= 0 ? chalk.green('+' + totalReturnPct.toFixed(2) + '%') : chalk.red(totalReturnPct.toFixed(2) + '%')}`);
154
+ this.log();
155
+ // Portfolio
156
+ this.log(chalk.yellow('Portfolio:'));
157
+ this.log(` Current Value: ${chalk.cyan('$' + portfolioValue.toFixed(2))}`);
158
+ this.log(` Cash Balance: ${chalk.cyan('$' + cashBalance.toFixed(2))}`);
159
+ this.log(` Total Exposure: ${chalk.cyan('$' + totalExposure.toFixed(2))}`);
160
+ this.log(` Exposure Ratio: ${chalk.cyan((exposureRatio * 100).toFixed(1) + '%')}`);
161
+ this.log();
162
+ // Risk
163
+ this.log(chalk.yellow('Risk:'));
164
+ this.log(` Largest Position: ${largestPositionPct >= 20 ? chalk.red(largestPositionPct.toFixed(1) + '%') : chalk.green(largestPositionPct.toFixed(1) + '%')}`);
165
+ if (concentration.size > 0) {
166
+ this.log(` Top Concentrations:`);
167
+ const sorted = [...concentration.entries()]
168
+ .sort((a, b) => b[1] - a[1])
169
+ .slice(0, 5);
170
+ for (const [ticker, pct] of sorted) {
171
+ this.log(` ${ticker}: ${pct >= 20 ? chalk.red(pct.toFixed(1) + '%') : chalk.cyan(pct.toFixed(1) + '%')}`);
172
+ }
173
+ }
174
+ this.log();
175
+ // Fees
176
+ this.log(chalk.yellow('Fees:'));
177
+ this.log(` Total Fees: ${chalk.red('$' + totalFees.toFixed(2))}`);
178
+ this.log(` Fee Impact: ${chalk.red(feeImpactPct.toFixed(2) + '%')}`);
179
+ }
180
+ logger.info({
181
+ exposure_ratio: exposureRatio,
182
+ period_days: periodDays,
183
+ total_pnl: realizedPnL + unrealizedPnL,
184
+ total_trades: totalTrades,
185
+ win_rate: winRate,
186
+ }, 'Portfolio analytics generated');
187
+ }
188
+ catch (error) {
189
+ throw error;
190
+ }
191
+ }
192
+ }
@@ -3,8 +3,8 @@ export default class PortfolioFills extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
- ticker: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
6
  limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
7
+ ticker: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  };
10
10
  run(): Promise<void>;
@@ -13,12 +13,12 @@ export default class PortfolioFills extends BaseCommand {
13
13
  ];
14
14
  static flags = {
15
15
  ...BaseCommand.baseFlags,
16
- ticker: Flags.string({
17
- description: 'Filter by ticker',
18
- }),
19
16
  limit: Flags.integer({
20
- description: 'Maximum number of fills to return',
21
17
  default: 50,
18
+ description: 'Maximum number of fills to return',
19
+ }),
20
+ ticker: Flags.string({
21
+ description: 'Filter by ticker',
22
22
  }),
23
23
  };
24
24
  async run() {
@@ -28,12 +28,12 @@ export default class PortfolioFills extends BaseCommand {
28
28
  const client = createClientFromConfig();
29
29
  // Fetch fills
30
30
  const result = await client.getFills({
31
- ticker: flags.ticker,
32
31
  limit: flags.limit,
32
+ ticker: flags.ticker,
33
33
  });
34
- const fills = result.fills;
34
+ const { fills } = result;
35
35
  if (this.formatter.isJSONMode()) {
36
- this.formatter.success({ fills, cursor: result.cursor });
36
+ this.formatter.success({ cursor: result.cursor, fills });
37
37
  }
38
38
  else {
39
39
  if (fills.length === 0) {
@@ -0,0 +1,14 @@
1
+ import { BaseCommand } from '../../lib/base-command.js';
2
+ export default class PortfolioHistory extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'group-by': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'max-ts': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'min-ts': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ ticker: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,245 @@
1
+ import { Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { calculateMakerTakerStats, groupFillsByPeriod, groupFillsByTicker, } from '../../lib/analytics.js';
4
+ import { BaseCommand } from '../../lib/base-command.js';
5
+ import { createClientFromConfig } from '../../lib/kalshi/index.js';
6
+ import { logger } from '../../lib/logger.js';
7
+ export default class PortfolioHistory extends BaseCommand {
8
+ static description = 'Historical performance analysis with time-series data';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %>',
11
+ '<%= config.bin %> <%= command.id %> --ticker MARKET-A',
12
+ '<%= config.bin %> <%= command.id %> --group-by week',
13
+ '<%= config.bin %> <%= command.id %> --min-ts 2026-01-01T00:00:00Z --max-ts 2026-02-01T00:00:00Z',
14
+ '<%= config.bin %> <%= command.id %> --json',
15
+ ];
16
+ static flags = {
17
+ ...BaseCommand.baseFlags,
18
+ 'group-by': Flags.string({
19
+ default: 'day',
20
+ description: 'Time aggregation level',
21
+ options: ['day', 'week', 'month'],
22
+ }),
23
+ 'limit': Flags.integer({
24
+ default: 500,
25
+ description: 'Maximum fills to fetch (handles pagination)',
26
+ }),
27
+ 'max-ts': Flags.string({
28
+ description: 'End date (ISO-8601 format)',
29
+ }),
30
+ 'min-ts': Flags.string({
31
+ description: 'Start date (ISO-8601 format)',
32
+ }),
33
+ 'ticker': Flags.string({
34
+ description: 'Filter by specific market ticker',
35
+ }),
36
+ };
37
+ async run() {
38
+ const { flags } = await this.parse(PortfolioHistory);
39
+ try {
40
+ const client = createClientFromConfig();
41
+ // Convert timestamps
42
+ const minTs = flags['min-ts'] ? Math.floor(new Date(flags['min-ts']).getTime() / 1000) : undefined;
43
+ const maxTs = flags['max-ts'] ? Math.floor(new Date(flags['max-ts']).getTime() / 1000) : undefined;
44
+ // Fetch fills with pagination
45
+ const allFills = [];
46
+ let cursor;
47
+ let remaining = flags.limit;
48
+ while (remaining > 0) {
49
+ const batchSize = Math.min(remaining, 100); // API limit per request
50
+ const result = await client.getFills({
51
+ cursor,
52
+ limit: batchSize,
53
+ max_ts: maxTs,
54
+ min_ts: minTs,
55
+ ticker: flags.ticker,
56
+ });
57
+ allFills.push(...result.fills);
58
+ remaining -= result.fills.length;
59
+ if (!result.cursor || result.fills.length === 0) {
60
+ break;
61
+ }
62
+ cursor = result.cursor;
63
+ }
64
+ if (allFills.length === 0) {
65
+ if (this.formatter.isJSONMode()) {
66
+ this.formatter.success({
67
+ by_ticker: {},
68
+ maker_taker: {
69
+ maker: { fills: 0, volume: 0 },
70
+ taker: { fills: 0, volume: 0 },
71
+ },
72
+ period: {
73
+ days: 0,
74
+ end: maxTs ? new Date(maxTs * 1000).toISOString() : new Date().toISOString(),
75
+ start: minTs ? new Date(minTs * 1000).toISOString() : new Date().toISOString(),
76
+ },
77
+ summary: {
78
+ buy_volume: 0,
79
+ maker_fills: 0,
80
+ net_volume: 0,
81
+ sell_volume: 0,
82
+ taker_fills: 0,
83
+ total_fees: 0,
84
+ total_fills: 0,
85
+ total_volume: 0,
86
+ },
87
+ time_series: {},
88
+ top_trades: {},
89
+ });
90
+ }
91
+ else {
92
+ this.log(chalk.yellow('No fills found for the specified period'));
93
+ }
94
+ return;
95
+ }
96
+ // Calculate period
97
+ const oldestFill = allFills[allFills.length - 1];
98
+ const startDate = minTs ? new Date(minTs * 1000) : new Date(oldestFill.created_time);
99
+ const endDate = maxTs ? new Date(maxTs * 1000) : new Date(allFills[0].created_time);
100
+ const periodDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1000));
101
+ // Calculate summary statistics
102
+ const totalVolume = allFills.reduce((sum, f) => sum + f.count * f.price, 0);
103
+ const buyVolume = allFills.filter(f => f.action === 'buy').reduce((sum, f) => sum + f.count * f.price, 0);
104
+ const sellVolume = allFills.filter(f => f.action === 'sell').reduce((sum, f) => sum + f.count * f.price, 0);
105
+ const makerFills = allFills.filter(f => !f.is_taker).length;
106
+ const takerFills = allFills.filter(f => f.is_taker).length;
107
+ // Estimate fees
108
+ const MAKER_FEE = 0.005;
109
+ const TAKER_FEE = 0.015;
110
+ const totalFees = allFills.reduce((sum, f) => {
111
+ const notional = f.count * f.price;
112
+ return sum + notional * (f.is_taker ? TAKER_FEE : MAKER_FEE);
113
+ }, 0);
114
+ // Group by ticker
115
+ const byTicker = groupFillsByTicker(allFills);
116
+ const byTickerArray = [...byTicker.entries()].map(([ticker, stats]) => ({
117
+ fills: stats.count,
118
+ pnl: stats.estimatedPnL,
119
+ ticker,
120
+ volume: stats.volume,
121
+ win_rate: 0, // TODO: Calculate from individual trades
122
+ }));
123
+ // Group by time period
124
+ const timeSeries = groupFillsByPeriod(allFills, flags['group-by']);
125
+ const timeSeriesArray = [...timeSeries.entries()]
126
+ .map(([date, fills]) => ({
127
+ date,
128
+ fills: fills.length,
129
+ pnl: 0, // TODO: Calculate P&L for period
130
+ volume: fills.reduce((sum, f) => sum + f.count * f.price, 0),
131
+ }))
132
+ .sort((a, b) => a.date.localeCompare(b.date));
133
+ // Maker vs taker analysis
134
+ const makerTakerStats = calculateMakerTakerStats(allFills);
135
+ // Find top trades (by volume)
136
+ const sortedByVolume = [...allFills].sort((a, b) => b.count * b.price - a.count * a.price);
137
+ const largestTrade = sortedByVolume[0];
138
+ if (this.formatter.isJSONMode()) {
139
+ this.formatter.success({
140
+ by_ticker: Object.fromEntries(byTickerArray.map(t => [t.ticker, {
141
+ fills: t.fills,
142
+ pnl: t.pnl,
143
+ volume: t.volume,
144
+ win_rate: t.win_rate,
145
+ }])),
146
+ maker_taker: makerTakerStats,
147
+ period: {
148
+ days: periodDays,
149
+ end: endDate.toISOString(),
150
+ start: startDate.toISOString(),
151
+ },
152
+ summary: {
153
+ buy_volume: buyVolume,
154
+ maker_fills: makerFills,
155
+ net_volume: sellVolume - buyVolume,
156
+ sell_volume: sellVolume,
157
+ taker_fills: takerFills,
158
+ total_fees: totalFees,
159
+ total_fills: allFills.length,
160
+ total_volume: totalVolume,
161
+ },
162
+ time_series: {
163
+ [flags['group-by']]: timeSeriesArray,
164
+ },
165
+ top_trades: {
166
+ largest_by_volume: largestTrade ? {
167
+ date: largestTrade.created_time,
168
+ ticker: largestTrade.ticker,
169
+ volume: largestTrade.count * largestTrade.price,
170
+ } : null,
171
+ },
172
+ });
173
+ }
174
+ else {
175
+ // Human-readable output
176
+ this.log(chalk.cyan.bold('Historical Performance Analysis'));
177
+ this.log();
178
+ // Period
179
+ this.log(chalk.yellow('Period:'));
180
+ this.log(` ${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()} (${periodDays} days)`);
181
+ this.log();
182
+ // Summary
183
+ this.log(chalk.yellow('Summary:'));
184
+ this.log(` Total Fills: ${allFills.length}`);
185
+ this.log(` Total Volume: ${chalk.cyan('$' + totalVolume.toFixed(2))}`);
186
+ this.log(` Buy Volume: ${chalk.green('$' + buyVolume.toFixed(2))}`);
187
+ this.log(` Sell Volume: ${chalk.red('$' + sellVolume.toFixed(2))}`);
188
+ this.log(` Net Volume: ${sellVolume - buyVolume >= 0 ? chalk.green('+$' + (sellVolume - buyVolume).toFixed(2)) : chalk.red('-$' + Math.abs(sellVolume - buyVolume).toFixed(2))}`);
189
+ this.log(` Total Fees: ${chalk.red('$' + totalFees.toFixed(2))}`);
190
+ this.log();
191
+ // Maker vs Taker
192
+ this.log(chalk.yellow('Maker vs Taker:'));
193
+ this.log(` Maker Fills: ${makerFills} (${((makerFills / allFills.length) * 100).toFixed(1)}%)`);
194
+ this.log(` Maker Volume: ${chalk.cyan('$' + makerTakerStats.maker.volume.toFixed(2))}`);
195
+ this.log(` Taker Fills: ${takerFills} (${((takerFills / allFills.length) * 100).toFixed(1)}%)`);
196
+ this.log(` Taker Volume: ${chalk.cyan('$' + makerTakerStats.taker.volume.toFixed(2))}`);
197
+ this.log();
198
+ // By Ticker
199
+ if (byTickerArray.length > 0) {
200
+ this.log(chalk.yellow('Performance by Ticker:'));
201
+ const topTickers = byTickerArray
202
+ .sort((a, b) => b.volume - a.volume)
203
+ .slice(0, 10);
204
+ const tickerRows = topTickers.map(t => [
205
+ t.ticker,
206
+ t.fills.toString(),
207
+ '$' + t.volume.toFixed(2),
208
+ t.pnl >= 0 ? chalk.green('+$' + t.pnl.toFixed(2)) : chalk.red('$' + t.pnl.toFixed(2)),
209
+ ]);
210
+ this.formatter.outputTable(['Ticker', 'Fills', 'Volume', 'Est. P&L'], tickerRows);
211
+ this.log();
212
+ }
213
+ // Time Series
214
+ this.log(chalk.yellow(`Time Series (${flags['group-by']}ly):`));
215
+ if (timeSeriesArray.length > 0) {
216
+ const recent = timeSeriesArray.slice(-10); // Show last 10 periods
217
+ const timeRows = recent.map(t => [
218
+ t.date,
219
+ t.fills.toString(),
220
+ '$' + t.volume.toFixed(2),
221
+ ]);
222
+ this.formatter.outputTable(['Period', 'Fills', 'Volume'], timeRows);
223
+ if (timeSeriesArray.length > 10) {
224
+ this.log();
225
+ this.log(chalk.gray(` ... showing last 10 of ${timeSeriesArray.length} periods`));
226
+ }
227
+ }
228
+ this.log();
229
+ // Top Trades
230
+ if (largestTrade) {
231
+ this.log(chalk.yellow('Top Trades:'));
232
+ this.log(` Largest by Volume: ${chalk.cyan(largestTrade.ticker)} - $${(largestTrade.count * largestTrade.price).toFixed(2)} (${new Date(largestTrade.created_time).toLocaleDateString()})`);
233
+ }
234
+ }
235
+ logger.info({
236
+ fills: allFills.length,
237
+ period_days: periodDays,
238
+ total_volume: totalVolume,
239
+ }, 'Historical analysis completed');
240
+ }
241
+ catch (error) {
242
+ throw error;
243
+ }
244
+ }
245
+ }
@@ -3,6 +3,7 @@ export default class PortfolioPositions extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ 'settlement-status': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
7
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
8
  };
8
9
  run(): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { Flags } from '@oclif/core';
1
2
  import chalk from 'chalk';
2
3
  import { BaseCommand } from '../../lib/base-command.js';
3
4
  import { createClientFromConfig } from '../../lib/kalshi/index.js';
@@ -6,18 +7,26 @@ export default class PortfolioPositions extends BaseCommand {
6
7
  static description = 'View current positions with P&L';
7
8
  static examples = [
8
9
  '<%= config.bin %> <%= command.id %>',
10
+ '<%= config.bin %> <%= command.id %> --settlement-status open',
11
+ '<%= config.bin %> <%= command.id %> --settlement-status settled',
9
12
  '<%= config.bin %> <%= command.id %> --json',
10
13
  ];
11
14
  static flags = {
12
15
  ...BaseCommand.baseFlags,
16
+ 'settlement-status': Flags.string({
17
+ description: 'Filter by settlement status',
18
+ options: ['open', 'pending', 'settled'],
19
+ }),
13
20
  };
14
21
  async run() {
15
- await this.parse(PortfolioPositions);
22
+ const { flags } = await this.parse(PortfolioPositions);
16
23
  try {
17
24
  // Create API client from configuration
18
25
  const client = createClientFromConfig();
19
26
  // Fetch positions
20
- const positions = await client.getPositions();
27
+ const positions = await client.getPositions({
28
+ settlement_status: flags['settlement-status'],
29
+ });
21
30
  if (this.formatter.isJSONMode()) {
22
31
  this.formatter.success(positions);
23
32
  }
@@ -0,0 +1,11 @@
1
+ import { BaseCommand } from '../../lib/base-command.js';
2
+ export default class PortfolioRisk extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'group-by': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ threshold: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
8
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ run(): Promise<void>;
11
+ }