Skip to content

Commit f6a248c

Browse files
committed
feat(release): initial release v0.1.0 🎉
- Add CI/CD workflow for JSR publishing - Configure Deno project settings and linting rules - Establish strict JSDoc documentation standards - Implement core transformation logic with strict anchor alignment - Provide documentation with usage examples and limitations
0 parents  commit f6a248c

10 files changed

Lines changed: 517 additions & 0 deletions

File tree

.github/workflows/publish.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Deno Publish
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
publish:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
id-token: write
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: denoland/setup-deno@v2
18+
with:
19+
deno-version: v2.5.4
20+
21+
- name: Check package
22+
run: deno check src/index.ts
23+
24+
- name: Run unit tests
25+
run: deno test --no-check
26+
27+
- name: Publish package
28+
run: deno publish --allow-dirty

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Deno
2+
.deno/
3+
coverage/
4+
deno.lock
5+
6+
# IDE
7+
.vscode/
8+
.idea/
9+
10+
# OS
11+
.DS_Store
12+
Thumbs.db

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 NeaByteLab
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Candle Transform [![JSR](https://jsr.io/badges/@neabyte/candle-transform)](https://jsr.io/@neabyte/candle-transform)
2+
3+
High-precision OHLC transformation with strict anchor time alignment.
4+
5+
## Features
6+
7+
- **Strict Anchor Alignment**: Ensures candles align with specific sessions (e.g., 4h candle starting at 23:00).
8+
- **High Performance**: Batch processing optimized for thousands of candles.
9+
- **Flexible Timeframes**: Supports `m`, `h`, `d` inputs.
10+
11+
## Installation
12+
13+
```bash
14+
deno add jsr:@neabyte/candle-transform
15+
```
16+
17+
## Usage
18+
19+
```typescript
20+
import { Transform } from '@neabyte/candle-transform'
21+
22+
// 1m data input
23+
const data = [
24+
{ time: 1704067200000, open: 1.0, high: 2.0, low: 0.9, close: 1.5 },
25+
{ time: 1704067260000, open: 1.5, high: 2.5, low: 1.4, close: 2.0 },
26+
{ time: 1704067320000, open: 2.0, high: 3.0, low: 1.8, close: 2.5 }
27+
// ... more candles
28+
]
29+
30+
// Convert to 4-hour chart (Default Anchor 23:00 UTC)
31+
const h4 = Transform.from(data).to('4h')
32+
console.log(h4) // Output: [ { time: 1704063600000, open: 1, high: 2, low: 0.5, close: 1.5, ... }, ... ]
33+
34+
// Convert to 1-day chart with custom anchor (e.g., 00:00 UTC)
35+
const daily = Transform.from(data).anchor(0).to('1d')
36+
console.log(daily) // Output: [ { time: 1704063600000, open: 1, high: 2, low: 0.5, close: 1.5, ... }, ... ]
37+
```
38+
39+
## API Reference
40+
41+
### `Transform.from(data)`
42+
43+
Creates a transformation instance from source data.
44+
45+
**Parameters:**
46+
47+
- `data: CandleData[]` - Array of source candles
48+
49+
### `.anchor(hour)`
50+
51+
Sets the anchor hour in UTC for time alignment.
52+
53+
**Parameters:**
54+
55+
- `hour: number` - Hour (0-23). Default is `23` (23:00 UTC Market Open)
56+
57+
### `.to(timeframe)`
58+
59+
Executes the transformation.
60+
61+
**Parameters:**
62+
63+
- `timeframe: TimeframeStr` - Target timeframe (e.g., `'15m'`, `'4h'`, `'1d'`)
64+
65+
**Returns:** `CandleData[]` - Transformed candles
66+
67+
## Limitations
68+
69+
Currently supports timeframes up to `1d` (Daily). Weekly (`1w`) and Monthly (`1M`) are not yet supported.
70+
71+
## License
72+
73+
This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info.

deno.json

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
{
2+
"name": "@neabyte/candle-transform",
3+
"description": "High-precision OHLC transformation with strict anchor time alignment.",
4+
"version": "0.1.0",
5+
"type": "module",
6+
"license": "MIT",
7+
"exports": "./src/index.ts",
8+
"compilerOptions": {
9+
"allowUnreachableCode": false,
10+
"allowUnusedLabels": false,
11+
"checkJs": false,
12+
"exactOptionalPropertyTypes": true,
13+
"jsx": "react",
14+
"lib": ["deno.ns", "dom", "esnext"],
15+
"noErrorTruncation": true,
16+
"noFallthroughCasesInSwitch": true,
17+
"noImplicitAny": true,
18+
"noImplicitOverride": true,
19+
"noImplicitReturns": true,
20+
"noImplicitThis": true,
21+
"noPropertyAccessFromIndexSignature": true,
22+
"noUncheckedIndexedAccess": true,
23+
"noUnusedLocals": true,
24+
"noUnusedParameters": true,
25+
"strict": true,
26+
"strictBindCallApply": true,
27+
"strictFunctionTypes": true,
28+
"strictNullChecks": true,
29+
"strictPropertyInitialization": true,
30+
"useUnknownInCatchVariables": true
31+
},
32+
"fmt": {
33+
"bracePosition": "sameLine",
34+
"indentWidth": 2,
35+
"lineWidth": 100,
36+
"proseWrap": "preserve",
37+
"semiColons": false,
38+
"singleBodyPosition": "nextLine",
39+
"singleQuote": true,
40+
"spaceAround": false,
41+
"spaceSurroundingProperties": true,
42+
"trailingCommas": "never",
43+
"useBraces": "always",
44+
"useTabs": false
45+
},
46+
"lint": {
47+
"include": ["src/"],
48+
"rules": {
49+
"tags": ["fresh", "jsr", "jsx", "react", "recommended", "workspace"],
50+
"include": [
51+
"ban-untagged-todo",
52+
"camelcase",
53+
"default-param-last",
54+
"eqeqeq",
55+
"explicit-function-return-type",
56+
"explicit-module-boundary-types",
57+
"guard-for-in",
58+
"no-await-in-loop",
59+
"no-boolean-literal-for-arguments",
60+
"no-const-assign",
61+
"no-eval",
62+
"no-implicit-declare-namespace-export",
63+
"no-inferrable-types",
64+
"no-invalid-triple-slash-reference",
65+
"no-non-null-asserted-optional-chain",
66+
"no-non-null-assertion",
67+
"no-self-compare",
68+
"no-sparse-arrays",
69+
"no-sync-fn-in-async-fn",
70+
"no-throw-literal",
71+
"no-undef",
72+
"no-useless-rename",
73+
"no-top-level-await",
74+
"single-var-declarator"
75+
],
76+
"exclude": ["no-console", "no-external-import", "prefer-ascii", "prefer-primordials"]
77+
}
78+
},
79+
"lock": true,
80+
"nodeModulesDir": "auto",
81+
"test": {
82+
"include": ["tests/**/*.ts"],
83+
"exclude": ["tests/**/*.d.ts"]
84+
},
85+
"tasks": {
86+
"start": "deno run --allow-all --watch index.ts",
87+
"check": "deno fmt src/ && deno lint src/ && deno check src/",
88+
"test": "deno fmt tests/ && deno lint tests/ && deno test --no-check"
89+
},
90+
"imports": {
91+
"@std/assert": "jsr:@std/assert@^1.0.16",
92+
"@app/": "./src/",
93+
"@helpers/": "./src/helpers/"
94+
},
95+
"publish": {
96+
"exclude": [
97+
"*.bench.ts",
98+
"*.spec.ts",
99+
"*.test.ts",
100+
".github/",
101+
"bench/",
102+
"build.config.ts",
103+
"coverage/",
104+
"tests/"
105+
]
106+
}
107+
}

src/Time.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/** Milliseconds per minute */
2+
const msPerMinute = 60 * 1000
3+
/** Milliseconds per hour */
4+
const msPerHour = 60 * msPerMinute
5+
/** Milliseconds per day */
6+
const msPerDay = 24 * msPerHour
7+
8+
/**
9+
* Default anchor offset.
10+
* @description Default 23h offset in ms.
11+
*/
12+
export const defaultAnchorOffset = 23 * msPerHour
13+
14+
/**
15+
* Aligns timestamp to anchor.
16+
* @param timestamp - Input timestamp to align
17+
* @param intervalMs - Interval duration in ms
18+
* @param anchorHour - Hour offset for alignment
19+
* @returns Aligned start timestamp
20+
*/
21+
export function getCandleOpenTime(timestamp: number, intervalMs: number, anchorHour = 23): number {
22+
const refTime = (anchorHour - 24) * msPerHour
23+
const delta = timestamp - refTime
24+
const remainder = delta % intervalMs
25+
return timestamp - remainder
26+
}
27+
28+
/**
29+
* Parses timeframe string.
30+
* @param tf - Timeframe string input
31+
* @returns Duration in milliseconds
32+
*/
33+
export function parseTimeframe(tf: string): number {
34+
const match = tf.match(/^(\d+)([mhd])$/)
35+
if (!match || !match[1] || !match[2]) {
36+
throw new Error(`Invalid timeframe format: ${tf}. Use format like '15m', '4h', '1d'`)
37+
}
38+
const value = parseInt(match[1], 10)
39+
const unit = match[2] as 'm' | 'h' | 'd'
40+
switch (unit) {
41+
case 'm':
42+
return value * msPerMinute
43+
case 'h':
44+
return value * msPerHour
45+
case 'd':
46+
return value * msPerDay
47+
default:
48+
throw new Error(`Unknown time unit: ${unit}`)
49+
}
50+
}

0 commit comments

Comments
 (0)