Skip to content

Commit 20343ac

Browse files
committed
v0.1.3 - Enhancing Pine Script compatibility, Check CHANGELOG for details
1 parent 5996bf5 commit 20343ac

File tree

16 files changed

+685
-141
lines changed

16 files changed

+685
-141
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Change Log
22

3+
## [0.1.3] - 2025-02-10 -
4+
5+
### Added
6+
7+
- Multiple transpiler fixes
8+
- Fix Logical, Binary and unary expressions when passed as arguments to PineTS internal functions (e.g plot(close && open, ...))
9+
- Support fo "na" as valid value (will be converted to NaN by the transpiler)
10+
- Fix for Pine Script functions returning tupples
11+
- Add partial support for color.rgb and color.new (these need to be completely rewritten)
12+
- Experimenting a cache approach for TA functions (not yet ready, only tested with sma)
13+
- Add Support for querying large time interval from MarketDataProvider by running multiple requests with a step, the requested market data is cached to prevent rate limiting and optimize performance
14+
- Complete refactor of math.\* functions to ensure compatibility with time series for all functions using the same syntax as Pine Script
15+
316
## [0.1.2] - 2025-02-05 - initial request.security() support
417

518
### Added

docs/api-coverage/color.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
| `color.b()` | |
1111
| `color.from_gradient()` | |
1212
| `color.g()` | |
13-
| `color.new()` | |
13+
| `color.new()` | |
1414
| `color.r()` | |
15-
| `color.rgb()` | |
15+
| `color.rgb()` | |
1616
| `color.t()` | |

docs/api-coverage/math.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
| `math.log()` | ✔️ |
1919
| `math.max()` ||
2020
| `math.min()` ||
21+
| `math.sum()` | ✔️ |
2122
| `math.pow()` | ✔️ |
2223
| `math.random()` | ✔️ |
2324
| `math.round()` | ✔️ |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pinets",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"description": "",
55
"main": "dist/pinets.dev.es.js",
66
"types": "dist/types/index.d.ts",

src/Context.class.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class Context {
2121
ohlc4: [],
2222
};
2323
public cache: any = {};
24+
public useTACache = false;
2425

2526
public NA: any = NaN;
2627

@@ -129,6 +130,7 @@ export class Context {
129130
* by default it is set to 10 decimals which is the same as pine script
130131
* @param n - the number to be precision
131132
* @param decimals - the number of decimals to precision to
133+
132134
* @returns the precision number
133135
*/
134136
precision(n: number, decimals: number = 10) {
@@ -151,6 +153,7 @@ export class Context {
151153
if (Array.isArray(source)) {
152154
if (index) {
153155
this.params[name] = source.slice(index);
156+
this.params[name].length = source.length; //ensure length is correct
154157
return this.params[name];
155158
}
156159
this.params[name] = source.slice(0);

src/PineTS.class.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class PineTS {
9494
return this._readyPromise;
9595
}
9696

97-
public async run(pineTSCode: Function | String, n?: number): Promise<Context> {
97+
public async run(pineTSCode: Function | String, n?: number, useTACache?: boolean): Promise<Context> {
9898
await this.ready();
9999
if (!n) n = this._periods;
100100

@@ -109,7 +109,7 @@ export class PineTS {
109109
});
110110

111111
context.pineTSCode = pineTSCode;
112-
112+
context.useTACache = useTACache;
113113
const transformer = transpile.bind(this);
114114
let transpiledFn = transformer(pineTSCode);
115115

src/marketData/Binance/BinanceProvider.class.ts

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,153 @@ const timeframe_to_binance = {
2222

2323
import { IProvider } from '@pinets/marketData/IProvider';
2424

25+
interface CacheEntry<T> {
26+
data: T;
27+
timestamp: number;
28+
}
29+
30+
class CacheManager<T> {
31+
private cache: Map<string, CacheEntry<T>>;
32+
private readonly cacheDuration: number;
33+
34+
constructor(cacheDuration: number = 5 * 60 * 1000) {
35+
// Default 5 minutes
36+
this.cache = new Map();
37+
this.cacheDuration = cacheDuration;
38+
}
39+
40+
private generateKey(params: Record<string, any>): string {
41+
return Object.entries(params)
42+
.filter(([_, value]) => value !== undefined)
43+
.map(([key, value]) => `${key}:${value}`)
44+
.join('|');
45+
}
46+
47+
get(params: Record<string, any>): T | null {
48+
const key = this.generateKey(params);
49+
const cached = this.cache.get(key);
50+
51+
if (!cached) return null;
52+
53+
if (Date.now() - cached.timestamp > this.cacheDuration) {
54+
this.cache.delete(key);
55+
return null;
56+
}
57+
58+
return cached.data;
59+
}
60+
61+
set(params: Record<string, any>, data: T): void {
62+
const key = this.generateKey(params);
63+
this.cache.set(key, {
64+
data,
65+
timestamp: Date.now(),
66+
});
67+
}
68+
69+
clear(): void {
70+
this.cache.clear();
71+
}
72+
73+
// Optional: method to remove expired entries
74+
cleanup(): void {
75+
const now = Date.now();
76+
for (const [key, entry] of this.cache.entries()) {
77+
if (now - entry.timestamp > this.cacheDuration) {
78+
this.cache.delete(key);
79+
}
80+
}
81+
}
82+
}
83+
2584
export class BinanceProvider implements IProvider {
85+
private cacheManager: CacheManager<any[]>;
86+
87+
constructor() {
88+
this.cacheManager = new CacheManager(5 * 60 * 1000); // 5 minutes cache duration
89+
}
90+
91+
async getMarketDataInterval(tickerId: string, timeframe: string, sDate: number, eDate: number): Promise<any> {
92+
try {
93+
const interval = timeframe_to_binance[timeframe.toUpperCase()];
94+
if (!interval) {
95+
console.error(`Unsupported timeframe: ${timeframe}`);
96+
return [];
97+
}
98+
99+
const timeframeDurations = {
100+
'1m': 60 * 1000,
101+
'3m': 3 * 60 * 1000,
102+
'5m': 5 * 60 * 1000,
103+
'15m': 15 * 60 * 1000,
104+
'30m': 30 * 60 * 1000,
105+
'1h': 60 * 60 * 1000,
106+
'2h': 2 * 60 * 60 * 1000,
107+
'4h': 4 * 60 * 60 * 1000,
108+
'1d': 24 * 60 * 60 * 1000,
109+
'1w': 7 * 24 * 60 * 60 * 1000,
110+
'1M': 30 * 24 * 60 * 60 * 1000,
111+
};
112+
113+
let allData = [];
114+
let currentStart = sDate;
115+
const endTime = eDate;
116+
const intervalDuration = timeframeDurations[interval];
117+
118+
if (!intervalDuration) {
119+
console.error(`Duration not defined for interval: ${interval}`);
120+
return [];
121+
}
122+
123+
while (currentStart < endTime) {
124+
const chunkEnd = Math.min(currentStart + 1000 * intervalDuration, endTime);
125+
126+
const data = await this.getMarketData(
127+
tickerId,
128+
timeframe,
129+
1000, // Max allowed by Binance
130+
currentStart,
131+
chunkEnd
132+
);
133+
134+
if (data.length === 0) break;
135+
136+
allData = allData.concat(data);
137+
138+
// CORRECTED LINE: Remove *1000 since closeTime is already in milliseconds
139+
currentStart = data[data.length - 1].closeTime + 1;
140+
141+
// Keep this safety check to exit when we get less than full page
142+
if (data.length < 1000) break;
143+
}
144+
145+
return allData;
146+
} catch (error) {
147+
console.error('Error in getMarketDataInterval:', error);
148+
return [];
149+
}
150+
}
26151
//TODO : allow querying more than 1000 klines
27152
//TODO : immplement cache
28153
async getMarketData(tickerId: string, timeframe: string, limit?: number, sDate?: number, eDate?: number): Promise<any> {
29154
try {
155+
// Check cache first
156+
const cacheParams = { tickerId, timeframe, limit, sDate, eDate };
157+
const cachedData = this.cacheManager.get(cacheParams);
158+
if (cachedData) {
159+
console.log('cache hit', tickerId, timeframe, limit, sDate, eDate);
160+
return cachedData;
161+
}
162+
30163
const interval = timeframe_to_binance[timeframe.toUpperCase()];
31164
if (!interval) {
32165
console.error(`Unsupported timeframe: ${timeframe}`);
33166
return [];
34167
}
35168
let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
169+
if (!limit && sDate && eDate) {
170+
return this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
171+
}
36172

37173
//example https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1m&limit=1000
38174
if (limit) {
@@ -53,20 +189,24 @@ export class BinanceProvider implements IProvider {
53189
const result = await response.json();
54190
const data = result.map((item) => {
55191
return {
56-
openTime: parseInt(item[0]) / 1000,
192+
openTime: parseInt(item[0]),
57193
open: parseFloat(item[1]),
58194
high: parseFloat(item[2]),
59195
low: parseFloat(item[3]),
60196
close: parseFloat(item[4]),
61197
volume: parseFloat(item[5]),
62-
closeTime: parseInt(item[6]) / 1000,
198+
closeTime: parseInt(item[6]),
63199
quoteAssetVolume: parseFloat(item[7]),
64200
numberOfTrades: parseInt(item[8]),
65201
takerBuyBaseAssetVolume: parseFloat(item[9]),
66202
takerBuyQuoteAssetVolume: parseFloat(item[10]),
67203
ignore: item[11],
68204
};
69205
});
206+
207+
// Cache the results
208+
this.cacheManager.set(cacheParams, data);
209+
70210
return data;
71211
} catch (error) {
72212
console.error('Error in binance.klines:', error);

src/namespaces/Core.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,35 @@
22

33
export class Core {
44
public color = {
5+
param: (source, index = 0) => {
6+
if (Array.isArray(source)) {
7+
return source[index];
8+
}
9+
return source;
10+
},
11+
rgb: (r: number, g: number, b: number, a?: number) => (a ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`),
12+
new: (color: string, a?: number) => {
13+
// Handle hexadecimal colors
14+
if (color && color.startsWith('#')) {
15+
// Remove # and convert to RGB
16+
const hex = color.slice(1);
17+
const r = parseInt(hex.slice(0, 2), 16);
18+
const g = parseInt(hex.slice(2, 4), 16);
19+
const b = parseInt(hex.slice(4, 6), 16);
20+
21+
return a ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`;
22+
}
23+
// Handle existing RGB format
24+
return a ? `rgba(${color}, ${a})` : color;
25+
},
526
white: 'white',
627
lime: 'lime',
728
green: 'green',
829
red: 'red',
930
maroon: 'maroon',
31+
1032
black: 'black',
33+
1134
gray: 'gray',
1235
blue: 'blue',
1336
};
@@ -36,7 +59,7 @@ export class Core {
3659
this.context.plots[title].data.push({
3760
time: this.context.marketData[this.context.marketData.length - this.context.idx - 1].openTime,
3861
value: series[0],
39-
options: this.extractPlotOptions(options),
62+
options: { ...this.extractPlotOptions(options), style: 'char' },
4063
});
4164
}
4265

@@ -56,10 +79,8 @@ export class Core {
5679
return Array.isArray(series) ? isNaN(series[0]) : isNaN(series);
5780
}
5881
nz(series: any, replacement: number = 0) {
59-
if (Array.isArray(series)) {
60-
return isNaN(series[0]) ? replacement : series[0];
61-
} else {
62-
return isNaN(series) ? replacement : series;
63-
}
82+
const val = Array.isArray(series) ? series[0] : series;
83+
const rep = Array.isArray(series) ? replacement[0] : replacement;
84+
return isNaN(val) ? rep : val;
6485
}
6586
}

src/namespaces/PineColor.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)