diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4184d8f..90bf667 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,3 @@ ---- -name: Pull Request -about: Pull request to merge into upper branch -assignees: - - Ruthgyeul - - Copilot ---- - # Related Issue > #ISSUE_NUMBER diff --git a/README.md b/README.md index 34d109c..0e42120 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Stock Price Generator -Generates synthetic stock price data using various random algorithm models. The generated data can be used for testing and simulation purposes. +Generates random number for synthetic stock price data using various random algorithm models. The generated data can be used for testing and simulation purposes. ## Features - Generate one-time stock price arrays - Create continuous stock price generators with configurable intervals -- Support for various random algorithms (Random Walk) +- Support for various random algorithms (Random Walk, GBM, etc.) - Configurable parameters for volatility, drift, and more -- Support for both ES Modules (import/export) and CommonJS (require) +- Support for both ES Modules (import/export), CommonJS (require), and TypeScript ## Installation @@ -16,15 +16,15 @@ npm install stockprice-generator ``` ## Usage - -### ES Modules +> Check github for more example usages +### CommonJS ```javascript -import { getStockPrices, getContStockPrices } from 'stockprice-generator'; +const { getStockPrices, getContStockPrices } = require('stockprice-generator'); // Generate an array of stock prices -const result = getStockPrices({ +const result = getContStockPrices({ startPrice: 10000, - days: 100, + length: 100, volatility: 0.1, drift: 0.05, algorithm: 'RandomWalk' @@ -34,14 +34,14 @@ console.log(result.data); // Array of prices console.log(result.price); // Current price (last price in the array) ``` -### CommonJS +### ES Modules ```javascript -const { getStockPrices, getContStockPrices } = require('stockprice-generator'); +import { getStockPrices, getContStockPrices } from 'stockprice-generator'; // Generate an array of stock prices -const result = getContStockPrices({ +const result = getStockPrices({ startPrice: 10000, - days: 100, + length: 100, volatility: 0.1, drift: 0.05, algorithm: 'RandomWalk' @@ -51,18 +51,18 @@ console.log(result.data); // Array of prices console.log(result.price); // Current price (last price in the array) ``` -### Continuous Generation (ES Modules) +### Continuous Generation (CommonJS) ```javascript -import { getStockPrices } from 'stockprice-generator'; +const { getContStockPrices } = require('stockprice-generator'); // Create a continuous generator that emits prices every 60 seconds -const generator = getStockPrices({ +const generator = getContStockPrices({ startPrice: 10000, volatility: 0.1, drift: 0.05, algorithm: 'RandomWalk', interval: 60000, // 60 seconds - onPrice: (price: number) => { + onPrice: (price) => { console.log(`New price: ${price}`); } }); @@ -77,18 +77,18 @@ console.log(`Current price: ${generator.getCurrentPrice()}`); // generator.stop(); ``` -### Continuous Generation (CommonJS) +### Continuous Generation (ES Modules) ```javascript -const { getContStockPrices } = require('stockprice-generator'); +import { getStockPrices } from 'stockprice-generator'; // Create a continuous generator that emits prices every 60 seconds -const generator = getContStockPrices({ +const generator = getStockPrices({ startPrice: 10000, volatility: 0.1, drift: 0.05, algorithm: 'RandomWalk', interval: 60000, // 60 seconds - onPrice: (price) => { + onPrice: (price: number) => { console.log(`New price: ${price}`); } }); @@ -105,26 +105,30 @@ console.log(`Current price: ${generator.getCurrentPrice()}`); ## Parameters -| Parameter | Required | Type | Default | Description | -|-----------|----------|------|----------|-------------| -| `startPrice` | Yes | number | - | Initial price of the stock | -| `days` | No | number | 100 | Number of days to generate prices for | -| `volatility` | No | number | 0.1 | Volatility of the stock price (standard deviation of the returns) | -| `drift` | No | number | 0.05 | The drift of the stock price (mean of the returns) | -| `seed` | No | number | DateTime | Seed for random number generation (for reproducibility) | -| `data` | No | number[] | [] | Pre-existing array of stock prices | -| `min` | No | number | 0 | Minimum price for the stock | -| `max` | No | number | 10000 | Maximum price for the stock | -| `length` | No | number | - | Length of the output array | -| `step` | No | number | - | Step size for discretization | -| `type` | No | 'float' \| 'int' | 'float' | Type of the data | -| `algorithm` | No | 'GBM' \| 'RandomWalk' | 'GBM' | Algorithm for generating the data | -| `interval` | No | number | 60000 | For continuous generation, interval in milliseconds between price updates | -| `onPrice` | No | function | - | Callback function to handle new prices in continuous generation | -| `onError` | No | function | - | Callback function to handle errors in continuous generation | -| `onStop` | No | function | - | Callback function to handle generator stop event | -| `onStart` | No | function | - | Callback function to handle generator start event | -| `onComplete` | No | function | - | Callback function to handle generator completion event | +| Parameter | Required | Type | Default | Description | +|--------------|----------|-----------------|--------|-------------------------------------------------------------------| +| `startPrice` | Yes | number | - | Initial price of the stock | +| `length` | No | number | 100 | Length of the output array | +| `volatility` | No | number | 0.1 | Volatility of the stock price (standard deviation of the returns) | +| `drift` | No | number | 0.05 | The drift of the stock price (mean of the returns) | +| `seed` | No | number | DateTime | Seed for random number generation (for reproducibility) | +| `min` | No | number | 0 | Minimum price for the stock (min >= 0) | +| `max` | No | number | 10000 | Maximum price for the stock (max >= 0) | +| `delisting` | No |boolean|false| Keep stock price to 0, if it reaches to 0 | +| `step` | No | number | - | Step size for discretization | +| `dataType` | No | float \| int | float | Type of the output data type | +| `algorithm` | No | RandomWalk \| GBM | RandomWalk | Algorithm for generating the random number | + +## Handler Functions (only for continuous generation) + +| Parameter | Required | Type | Default | Description | +|-----------|----------|------|----------|--------------------------------------------------------------------------| +| `interval` | Yes | number | 60000 | Interval in milliseconds between price updates in continuous generation | +| `onStart` | No | function | - | Callback function to handle generator start event | +| `onPrice` | No | function | - | Callback function to handle new prices in continuous generation | +| `onStop` | No | function | - | Callback function to handle generator stop event | +| `onComplete` | No | function | - | Callback function to handle generator completion event | +| `onError` | No | function | - | Callback function to handle errors in continuous generation | ## License MIT \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 56d6646..5020536 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "stockprice-generator", - "version": "0.0.17", + "version": "0.0.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "stockprice-generator", - "version": "0.0.17", + "version": "0.0.20", "license": "MIT", "devDependencies": { "@types/jest": "^29.5.12", @@ -5034,9 +5034,9 @@ } }, "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.39.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", - "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz", + "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { diff --git a/package.json b/package.json index 6b3aa0b..16d7095 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stockprice-generator", "description": "A package for generating synthetic stock price data using various random algorithm models", - "version": "0.0.17", + "version": "0.0.20", "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", @@ -27,9 +27,6 @@ "test:w": "jest --watch", "prepare": "npm run build" }, - "publishConfig": { - "registry": "https://npm.pkg.github.com" - }, "repository": { "type": "git", "url": "git+https://github.com/Ruthgyeul/stockPriceGenerator.git" @@ -41,12 +38,14 @@ }, "homepage": "https://github.com/Ruthgyeul/stockPriceGenerator.git#readme", "keywords": [ + "random", + "number", "stock", "price", "generator", "simulation", - "GBM", "random-walk", + "GBM", "finance" ], "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 8fcb865..00c0774 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,19 +22,30 @@ class StockPriceGeneratorImpl implements StockPriceGenerator { } generateNextPrice(): number { - const { volatility = 0.1, drift = 0.05, algorithm = 'RandomWalk', seed } = this.options; + const { volatility = 0.1, drift = 0.05, algorithm = 'RandomWalk', seed, step } = this.options; if (volatility < 0) { throw new Error('Volatility must be a non-negative number'); } const selectedAlgorithm = algorithms[algorithm as AlgorithmType]; - return selectedAlgorithm({ + let nextPrice = selectedAlgorithm({ currentPrice: this.currentPrice, volatility, drift, - seed + seed, + min: this.options.min ?? 0, + max: this.options.max ?? 0, + delisting: this.options.delisting ?? false, + dataType: this.options.dataType ?? 'float' }); + + // Apply step size discretization if specified + if (step && step > 0) { + nextPrice = Math.round(nextPrice / step) * step; + } + + return nextPrice; } start(): void { @@ -52,11 +63,32 @@ class StockPriceGeneratorImpl implements StockPriceGenerator { }, this.interval); } + pause(): void { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + } + + continue(): void { + if (this.timer) return; + + this.timer = setInterval(() => { + try { + this.currentPrice = this.generateNextPrice(); + this.options.onPrice?.(this.currentPrice); + } catch (error) { + this.options.onError?.(error as Error); + } + }, this.interval); + } + stop(): void { if (this.timer) { clearInterval(this.timer); this.timer = null; this.options.onStop?.(); + this.options.onComplete?.(); } } @@ -66,11 +98,11 @@ class StockPriceGeneratorImpl implements StockPriceGenerator { } export function getStockPrices(options: StockPriceOptions): StockPriceResult { - const { startPrice, days = 100, algorithm = 'RandomWalk' } = options; + const { startPrice, length = 100, step } = options; const data: number[] = [startPrice]; let currentPrice = startPrice; - for (let i = 1; i < days; i++) { + for (let i = 1; i < length; i++) { const generator = new StockPriceGeneratorImpl({ ...options, startPrice: currentPrice diff --git a/src/types.ts b/src/types.ts index 3dbf570..8fe88ce 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,23 +4,22 @@ export type DataType = 'float' | 'int'; export interface StockPriceOptions { startPrice: number; // Initial stock price - days?: number; // Number of days to generate prices for + length?: number; // Length of the generated data volatility?: number; // Volatility of the stock price drift?: number; // Drift of the stock price seed?: number; // Seed for random number generation - data?: number[]; // Array of stock prices min?: number; // Minimum stock price max?: number; // Maximum stock price - length?: number; // Length of the generated data + delisting?: boolean; // Keep stock price to 0 if it reaches 0 step?: number; // Step size for the generated data - type?: DataType; // Type of data to generate (float or int) + dataType?: DataType; // Type of data to generate (float or int) algorithm?: Algorithm; // Algorithm to use for generating stock prices interval?: number; // Interval for generating stock prices (in milliseconds) + onStart?: () => void; // Callback for when the generator starts onPrice?: (price: number) => void; // Callback for each generated price - onError?: (error: Error) => void; // Callback for errors onStop?: () => void; // Callback for when the generator stops - onStart?: () => void; // Callback for when the generator starts onComplete?: () => void; // Callback for when the generator completes + onError?: (error: Error) => void; // Callback for errors } export interface StockPriceResult { @@ -30,6 +29,8 @@ export interface StockPriceResult { export interface StockPriceGenerator { start: () => void; // Start generating stock prices + pause: () => void; // Pause generating stock prices + continue: () => void; // Continue generating stock prices stop: () => void; // Stop generating stock prices getCurrentPrice: () => number; // Get the current stock price } \ No newline at end of file diff --git a/src/utils/algorithm/algorithmParams.ts b/src/utils/algorithm/algorithmParams.ts index 7990a03..ea887bf 100644 --- a/src/utils/algorithm/algorithmParams.ts +++ b/src/utils/algorithm/algorithmParams.ts @@ -1,6 +1,12 @@ +export type DataType = 'float' | 'int'; + export interface AlgorithmParams { currentPrice: number; // Current stock price volatility: number; // Volatility of the stock price drift: number; // Drift of the stock price seed?: number; // Seed for random number generation + min?: number; // Minimum stock price + max?: number; // Maximum stock price + delisting?: boolean; // Keep stock price to 0 if it reaches 0 + dataType?: DataType; // Type of data to generate (float or int) } \ No newline at end of file diff --git a/src/utils/algorithm/useGBM.ts b/src/utils/algorithm/useGBM.ts index 0cc9cf0..6bd2d59 100644 --- a/src/utils/algorithm/useGBM.ts +++ b/src/utils/algorithm/useGBM.ts @@ -1,7 +1,10 @@ import type { AlgorithmParams } from './algorithmParams'; import { seededRandom } from '../seed'; +import { minMaxCheck} from '../minMaxCheck'; +import { killStock } from "../killStock"; +import { outputType } from "../outputType"; -export function GBM({ currentPrice, volatility, drift, seed = (Date.now() + new Date().getMilliseconds()) }: AlgorithmParams): number { +export function GBM({ currentPrice, volatility = 0.1, drift = 0.05, seed = (Date.now() + new Date().getMilliseconds()), min = 0, max = 0, delisting = false, dataType = 'float' }: AlgorithmParams): number { const dt = 1 / 365; const u1 = seededRandom(seed); @@ -13,5 +16,17 @@ export function GBM({ currentPrice, volatility, drift, seed = (Date.now() + new volatility * Math.sqrt(dt) * z ); - return currentPrice * change; + let nextPrice = currentPrice * change; + + // Check if delisting is enabled + if (delisting) { + // Delisting logic + nextPrice = killStock(nextPrice); + } else { + // Check min and max limits + nextPrice = minMaxCheck(min, max, nextPrice); + } + + // Output with specified data type + return outputType(nextPrice, dataType); } diff --git a/src/utils/algorithm/useRandomWalk.ts b/src/utils/algorithm/useRandomWalk.ts index 14f1a7f..cbf6f8e 100644 --- a/src/utils/algorithm/useRandomWalk.ts +++ b/src/utils/algorithm/useRandomWalk.ts @@ -1,7 +1,10 @@ import type { AlgorithmParams } from './algorithmParams'; import { seededRandom } from '../seed'; +import { minMaxCheck } from '../minMaxCheck'; +import { killStock } from '../killStock'; +import { outputType } from "../outputType"; -export function RandomWalk({ currentPrice, volatility, drift, seed }: AlgorithmParams): number { +export function RandomWalk({ currentPrice, volatility = 0.1, drift = 0.05, seed, min = 0, max = 0, delisting = false, dataType = 'float' }: AlgorithmParams): number { const random = seededRandom(seed); const epsilon = (random - 0.5) * 2; // scale to [-1, 1] const dt = 1 / 365; @@ -9,6 +12,17 @@ export function RandomWalk({ currentPrice, volatility, drift, seed }: AlgorithmP const driftTerm = (drift - 0.5 * volatility ** 2) * dt; const diffusionTerm = volatility * Math.sqrt(dt) * epsilon; - const nextPrice = currentPrice * Math.exp(driftTerm + diffusionTerm); - return nextPrice; + let nextPrice = currentPrice * Math.exp(driftTerm + diffusionTerm); + + // Check if delisting is enabled + if (delisting) { + // Delisting logic + nextPrice = killStock(nextPrice); + } else { + // Check min and max limits + nextPrice = minMaxCheck(min, max, nextPrice); + } + + // Output with specified data type + return outputType(nextPrice, dataType); } diff --git a/src/utils/killStock.ts b/src/utils/killStock.ts new file mode 100644 index 0000000..02a7284 --- /dev/null +++ b/src/utils/killStock.ts @@ -0,0 +1,7 @@ +export function killStock(price : number) { + if (price <= 0) { + return Number(0); + } else { + return Number(price); + } +} \ No newline at end of file diff --git a/src/utils/minMaxCheck.ts b/src/utils/minMaxCheck.ts new file mode 100644 index 0000000..2754a79 --- /dev/null +++ b/src/utils/minMaxCheck.ts @@ -0,0 +1,67 @@ +export function minMaxCheck(min : number, max: number, price : number) { + if (min == 0 && max == 0) { + return price; + } else { + const dice0 = Math.random() * 7.5 + 0.1; + const dice1 = Math.random() * 5.5 + 0.1; + const dice2 = Math.random() * 4.5 + 0.1; + const dice3 = Math.random() * 3.5 + 0.1; + const dice4 = Math.random() * 2.5 + 0.1; + const dice5 = Math.random() * 1.5 + 0.1; + const dice6 = Math.random() * 0.5 + 0.1; + const mainDice = Math.floor(Math.random() * 7); + + const minMax = Math.random() * 0.1 + 0.001; + const calc = price * minMax; + + if (price < min) { + return adjustMinLogic(price, calc, mainDice, dice0, dice1, dice2, dice3, dice4, dice5, dice6); + } else if (price > max) { + return adjustMaxLogic(price, calc, mainDice, dice0, dice1, dice2, dice3, dice4, dice5, dice6); + } else { + return price; + } + } +} + +function adjustMinLogic(price : number, calc: number, mainDice: number, dice0: number, dice1: number, dice2: number, dice3: number, dice4: number, dice5: number, dice6: number) { + switch (mainDice) { + case 0: + return price += calc * dice0; + case 1: + return price += calc * dice1; + case 2: + return price += calc * dice2; + case 3: + return price += calc * dice3; + case 4: + return price += calc * dice4; + case 5: + return price += calc * dice5; + case 6: + return price += calc * dice6; + default: + return price += calc * dice6; + } +} + +function adjustMaxLogic(price : number, calc: number, mainDice: number, dice0: number, dice1: number, dice2: number, dice3: number, dice4: number, dice5: number, dice6: number) { + switch (mainDice) { + case 0: + return price -= calc * dice0; + case 1: + return price -= calc * dice1; + case 2: + return price -= calc * dice2; + case 3: + return price -= calc * dice3; + case 4: + return price -= calc * dice4; + case 5: + return price -= calc * dice5; + case 6: + return price -= calc * dice6; + default: + return price -= calc * dice6; + } +} \ No newline at end of file diff --git a/src/utils/outputType.ts b/src/utils/outputType.ts new file mode 100644 index 0000000..a610790 --- /dev/null +++ b/src/utils/outputType.ts @@ -0,0 +1,7 @@ +export function outputType(price : number, dataType: string) { + if (dataType === 'int') { + return Math.round(price); + } else { + return Number(price.toFixed(2)); + } +} diff --git a/test/useGBM.test.ts b/test/useGBM.test.ts index 000f411..a2d8bc3 100644 --- a/test/useGBM.test.ts +++ b/test/useGBM.test.ts @@ -3,7 +3,7 @@ import { getStockPrices, getContStockPrices, StockPriceOptions } from '../src'; describe('GBM Algorithm - Stock Price Generator', () => { const defaultOptions: StockPriceOptions = { startPrice: 10000, - days: 10, + length: 10, volatility: 0.1, drift: 0.05, algorithm: 'GBM' @@ -17,9 +17,9 @@ describe('GBM Algorithm - Stock Price Generator', () => { expect(typeof result.price).toBe('number'); }); - test('should generate correct number of days', () => { + test('should generate correct number of length', () => { const result = getStockPrices(defaultOptions); - expect(result.data.length).toBe(defaultOptions.days); + expect(result.data.length).toBe(defaultOptions.length); }); test('should support GBM algorithm with continuous generator', (done) => { @@ -60,7 +60,7 @@ describe('GBM Algorithm - Stock Price Generator', () => { test('should apply drift correctly', () => { const result = getStockPrices({ ...defaultOptions, - days: 30, + length: 30, drift: 0.1, seed: 123 }); @@ -110,10 +110,24 @@ describe('GBM Algorithm - Stock Price Generator', () => { expect(allStepAligned).toBe(true); }); - test('should round prices to integers if type is int', () => { + test('should apply delisting logic when price reaches 0', () => { const result = getStockPrices({ ...defaultOptions, - type: 'int' + startPrice: 1, + volatility: 2, + drift: -5, + delisting: true, + seed: 999 + }); + + const reachedZero = result.data.some(p => p <= 0); + expect(reachedZero).toBe(true); + }); + + test('should round prices to integers if dataType is int', () => { + const result = getStockPrices({ + ...defaultOptions, + dataType: 'int' }); const allIntegers = result.data.every(price => Number.isInteger(price)); @@ -125,11 +139,6 @@ describe('GBM Algorithm - Stock Price Generator', () => { expect(result.data[0]).toBe(12345); }); - test('should generate the number of days specified', () => { - const result = getStockPrices({ ...defaultOptions, days: 20 }); - expect(result.data.length).toBe(20); - }); - test('should apply specified volatility', () => { const low = getStockPrices({ ...defaultOptions, volatility: 0.01, seed: 456 }); const high = getStockPrices({ ...defaultOptions, volatility: 0.3, seed: 456 }); @@ -153,12 +162,6 @@ describe('GBM Algorithm - Stock Price Generator', () => { expect(r1.data).toEqual(r2.data); }); - test('should use provided data array as base', () => { - const inputData = [10000, 11000, 12000]; - const result = getStockPrices({ ...defaultOptions, data: inputData }); - expect(result.data.slice(0, 3)).toEqual(inputData); - }); - test('should clamp values within min and max', () => { const result = getStockPrices({ ...defaultOptions, min: 5000, max: 7000 }); expect(Math.min(...result.data)).toBeGreaterThanOrEqual(5000); @@ -176,8 +179,8 @@ describe('GBM Algorithm - Stock Price Generator', () => { expect(aligned).toBe(true); }); - test('should output integers if type is int', () => { - const result = getStockPrices({ ...defaultOptions, type: 'int' }); + test('should output integers if dataType is int', () => { + const result = getStockPrices({ ...defaultOptions, dataType: 'int' }); const allInts = result.data.every(p => Number.isInteger(p)); expect(allInts).toBe(true); }); @@ -205,4 +208,30 @@ describe('GBM Algorithm - Stock Price Generator', () => { done(); }, 250); }); + + test('should call onStart, onStop, onComplete, and onError callbacks', (done) => { + const onStart = jest.fn(); + const onStop = jest.fn(); + const onComplete = jest.fn(); + const onError = jest.fn(); + + const generator = getContStockPrices({ + ...defaultOptions, + interval: 100, + onStart, + onStop, + onComplete, + onError + }); + + generator.start(); + expect(onStart).toHaveBeenCalled(); + + setTimeout(() => { + generator.stop(); + expect(onStop).toHaveBeenCalled(); + // Assuming onComplete and onError might not always be called, we don't enforce them + done(); + }, 250); + }); }); \ No newline at end of file diff --git a/test/useRandomWalk.test.ts b/test/useRandomWalk.test.ts index 389cfb6..05e91fb 100644 --- a/test/useRandomWalk.test.ts +++ b/test/useRandomWalk.test.ts @@ -3,7 +3,7 @@ import { getStockPrices, getContStockPrices, StockPriceOptions } from '../src'; describe('Random Walk Algorithm - Stock Price Generator', () => { const defaultOptions: StockPriceOptions = { startPrice: 10000, - days: 10, + length: 10, volatility: 0.1, drift: 0.05, algorithm: 'RandomWalk', @@ -18,9 +18,9 @@ describe('Random Walk Algorithm - Stock Price Generator', () => { expect(typeof result.price).toBe('number'); }); - test('should generate correct number of days', () => { + test('should generate correct number of length', () => { const result = getStockPrices(defaultOptions); - expect(result.data.length).toBe(defaultOptions.days); + expect(result.data.length).toBe(defaultOptions.length); }); test('should start with the specified start price', () => { @@ -31,7 +31,7 @@ describe('Random Walk Algorithm - Stock Price Generator', () => { test('should respect volatility parameter', () => { const lowVolatility = getStockPrices({ ...defaultOptions, - days: 30, + length: 30, startPrice: 10000, volatility: 0.01, drift: 0, @@ -40,7 +40,7 @@ describe('Random Walk Algorithm - Stock Price Generator', () => { const highVolatility = getStockPrices({ ...defaultOptions, - days: 30, + length: 30, startPrice: 10000, volatility: 0.1, drift: 0, @@ -58,7 +58,7 @@ describe('Random Walk Algorithm - Stock Price Generator', () => { test('should apply drift correctly', () => { const result = getStockPrices({ ...defaultOptions, - days: 30, + length: 30, drift: 0.1, seed: 123 }); @@ -108,10 +108,10 @@ describe('Random Walk Algorithm - Stock Price Generator', () => { expect(allStepAligned).toBe(true); }); - test('should round prices to integers if type is int', () => { + test('should round prices to integers if dataType is int', () => { const result = getStockPrices({ ...defaultOptions, - type: 'int' + dataType: 'int' }); const allIntegers = result.data.every(price => Number.isInteger(price)); @@ -187,11 +187,6 @@ describe('Random Walk Algorithm - Stock Price Generator', () => { expect(result.data[0]).toBe(12345); }); - test('should generate the number of days specified', () => { - const result = getStockPrices({ ...defaultOptions, days: 20 }); - expect(result.data.length).toBe(20); - }); - test('should apply specified volatility', () => { const low = getStockPrices({ ...defaultOptions, volatility: 0.01, seed: 456 }); const high = getStockPrices({ ...defaultOptions, volatility: 0.3, seed: 456 }); @@ -209,41 +204,12 @@ describe('Random Walk Algorithm - Stock Price Generator', () => { expect(avgReturn).toBeGreaterThan(0); }); - test('should generate consistent results using the same seed', () => { - const r1 = getStockPrices({ ...defaultOptions, seed: 42 }); - const r2 = getStockPrices({ ...defaultOptions, seed: 42 }); - expect(r1.data).toEqual(r2.data); - }); - - test('should use provided data array as base', () => { - const inputData = [10000, 11000, 12000]; - const result = getStockPrices({ ...defaultOptions, data: inputData }); - expect(result.data.slice(0, 3)).toEqual(inputData); - }); - - test('should clamp values within min and max', () => { - const result = getStockPrices({ ...defaultOptions, min: 5000, max: 7000 }); - expect(Math.min(...result.data)).toBeGreaterThanOrEqual(5000); - expect(Math.max(...result.data)).toBeLessThanOrEqual(7000); - }); - - test('should return array with specified length', () => { - const result = getStockPrices({ ...defaultOptions, length: 15 }); - expect(result.data.length).toBe(15); - }); - test('should respect step size rounding', () => { const result = getStockPrices({ ...defaultOptions, step: 500 }); const aligned = result.data.every(p => p % 500 === 0); expect(aligned).toBe(true); }); - test('should output integers if type is int', () => { - const result = getStockPrices({ ...defaultOptions, type: 'int' }); - const allInts = result.data.every(p => Number.isInteger(p)); - expect(allInts).toBe(true); - }); - test('should support multiple algorithms', () => { const rw = getStockPrices({ ...defaultOptions, algorithm: 'RandomWalk' }); const gbm = getStockPrices({ ...defaultOptions, algorithm: 'GBM' }); @@ -267,4 +233,44 @@ describe('Random Walk Algorithm - Stock Price Generator', () => { done(); }, 250); }); + + test('should apply delisting when price reaches 0', () => { + const result = getStockPrices({ + ...defaultOptions, + startPrice: 1, + volatility: 2, + drift: -5, + delisting: true, + seed: 999 + }); + + const reachedZero = result.data.some(p => p <= 0); + expect(reachedZero).toBe(true); + }); + + test('should trigger onStart, onStop, onComplete, and onError callbacks', (done) => { + const onStart = jest.fn(); + const onStop = jest.fn(); + const onComplete = jest.fn(); + const onError = jest.fn(); + + const generator = getContStockPrices({ + ...defaultOptions, + interval: 100, + onStart, + onStop, + onComplete, + onError + }); + + generator.start(); + expect(onStart).toHaveBeenCalled(); + + setTimeout(() => { + generator.stop(); + expect(onStop).toHaveBeenCalled(); + // Optional checks depending on generator behavior + done(); + }, 250); + }); }); \ No newline at end of file