@steerprotocol/strategy-utils 3.1.1 → 3.2.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.
- package/.github/workflows/nodejs.yml +12 -12
- package/.vscode/settings.json +6 -0
- package/CHANGELOG.md +3 -45
- package/asconfig.json +11 -9
- package/assembly/index.ts +1 -0
- package/assembly/panoptic/host.ts +285 -0
- package/assembly/panoptic/index.ts +3 -0
- package/assembly/panoptic/methods.ts +64 -0
- package/assembly/panoptic/types.ts +911 -0
- package/assembly/utils/Math.ts +12 -17
- package/assembly/utils/MovingAverages.ts +26 -19
- package/assembly/utils/Ranges.ts +15 -15
- package/assembly/utils/UniswapLiquidityUtils.ts +48 -0
- package/assembly/utils/index.ts +1 -3
- package/assembly/utils/types/Position.ts +0 -3
- package/assembly/utils/types/Price.ts +54 -0
- package/assembly/utils/types/index.ts +1 -4
- package/index.js +3 -14
- package/package.json +10 -14
- package/scripts/build-docs.js +68 -0
- package/tests/fixtures/json-compat.ts +30 -0
- package/tests/fixtures/panoptic-consumer.ts +24 -0
- package/tests/index.test.ts +55 -227
- package/assembly/utils/CandleGenerator.ts +0 -60
- package/assembly/utils/MarketFeedAggregator.ts +0 -140
- package/assembly/utils/SlidingWindow.ts +0 -59
- package/assembly/utils/env.ts +0 -23
- package/assembly/utils/triggers.ts +0 -438
- package/assembly/utils/types/Candle.ts +0 -39
- package/assembly/utils/types/DataConnectorConfig.ts +0 -7
- package/assembly/utils/types/ExecutionContext.ts +0 -11
- package/assembly/utils/types/RawTradeData.ts +0 -14
- package/index.html +0 -10
- package/readme.md +0 -387
- package/tests/debug.wasm +0 -0
- package/tests/utils.ts +0 -607
package/tests/index.test.ts
CHANGED
|
@@ -1,238 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
const loader = require('@assemblyscript/loader');
|
|
5
|
+
const path = require('path');
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
describe("Trigger tests", () => {
|
|
14
|
-
// Testing the following options
|
|
15
|
-
// 'Current Price set distance from center of positions',
|
|
16
|
-
// 'Price leaves active range',
|
|
17
|
-
// 'Price moves percentage of active range away',
|
|
18
|
-
// 'Price moves one way past positions',
|
|
19
|
-
// 'None'
|
|
20
|
-
// TEST tirggers
|
|
21
|
-
test("Can return positions on none type", async () => {
|
|
22
|
-
const config = `{
|
|
23
|
-
"triggerStyle": "None",
|
|
24
|
-
"period":6,
|
|
25
|
-
"standardDeviations":2.0,
|
|
26
|
-
"liquidityShape": "Linear",
|
|
27
|
-
"poolFee": 3000,
|
|
28
|
-
"placementType": "Position over current price",
|
|
29
|
-
"positionSize": 600
|
|
30
|
-
}`
|
|
31
|
-
myModule.initialize(config);
|
|
32
|
-
const positions = '[[257100],[257300],[1]]'
|
|
33
|
-
const currentTick = '257301'
|
|
34
|
-
const timeSinceLastExecution = '5600'
|
|
35
|
-
const result = myModule.execute
|
|
36
|
-
(JSON.stringify(candles2), positions, currentTick, timeSinceLastExecution);
|
|
37
|
-
expect(result).not.toEqual('continue')
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test
|
|
41
|
-
("Can return continue for active trigger", async () => {
|
|
42
|
-
const config = `{
|
|
43
|
-
"elapsedTendTime": 604800,
|
|
44
|
-
"triggerStyle": "Price leaves active range",
|
|
45
|
-
"strategy": "Classic",
|
|
46
|
-
"liquidityShape": "Linear",
|
|
47
|
-
"poolFee": 500,
|
|
48
|
-
"period":6,
|
|
49
|
-
"standardDeviations":2.0
|
|
50
|
-
}`
|
|
51
|
-
myModule.initialize(config);
|
|
52
|
-
const positions = '[[257100],[257300],[1]]'
|
|
53
|
-
const currentTick = '257251'
|
|
54
|
-
const timeSinceLastExecution = '5600'
|
|
55
|
-
const result = myModule.execute(//...[JSON.stringify(candles2), positions])
|
|
56
|
-
JSON.stringify(candles2),
|
|
57
|
-
positions,
|
|
58
|
-
currentTick,
|
|
59
|
-
timeSinceLastExecution);
|
|
60
|
-
expect(result).toEqual('continue')
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("Can return positions for active trigger", async () => {
|
|
64
|
-
const config = `{
|
|
65
|
-
"elapsedTendTime": 604800,
|
|
66
|
-
"triggerStyle": "Price leaves active range",
|
|
67
|
-
"strategy": "Classic",
|
|
68
|
-
"liquidityShape": "Linear",
|
|
69
|
-
"poolFee": 3000,
|
|
70
|
-
"placementType": "Position over current price",
|
|
71
|
-
"period":6,
|
|
72
|
-
"standardDeviations":2.0,
|
|
73
|
-
"positionSize": 600
|
|
74
|
-
}`
|
|
75
|
-
myModule.initialize(config);
|
|
76
|
-
const positions = '[[257100],[257300],[1]]'
|
|
77
|
-
const currentTick = '257301'
|
|
78
|
-
const timeSinceLastExecution = '5600'
|
|
79
|
-
const result = myModule.execute
|
|
80
|
-
(JSON.stringify(candles2), positions, currentTick, timeSinceLastExecution);
|
|
81
|
-
expect(result).not.toEqual('continue')
|
|
7
|
+
describe('AssemblyScript package surface', () => {
|
|
8
|
+
it('compiles the package with the modern AssemblyScript toolchain', () => {
|
|
9
|
+
execFileSync('npm', ['run', 'asbuild'], {
|
|
10
|
+
cwd: path.resolve(__dirname, '..'),
|
|
11
|
+
stdio: 'pipe',
|
|
12
|
+
});
|
|
82
13
|
});
|
|
83
14
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"period":6,
|
|
94
|
-
"standardDeviations":2.0,
|
|
95
|
-
"positionSize": 600
|
|
96
|
-
}`
|
|
97
|
-
myModule.initialize(config);
|
|
98
|
-
const positions = '[[257100],[257300],[1]]'
|
|
99
|
-
const currentTick = '257251'
|
|
100
|
-
const timeSinceLastExecution = '5600'
|
|
101
|
-
const result = myModule.execute
|
|
102
|
-
(JSON.stringify(candles2), positions, currentTick, timeSinceLastExecution);
|
|
103
|
-
expect(result).toEqual('continue')
|
|
15
|
+
it('compiles a Panoptic consumer using exported DTOs and wrappers', () => {
|
|
16
|
+
execFileSync(
|
|
17
|
+
path.resolve(__dirname, '../node_modules/.bin/asc'),
|
|
18
|
+
[path.resolve(__dirname, 'fixtures/panoptic-consumer.ts'), '--config', path.resolve(__dirname, '../asconfig.json'), '--noEmit'],
|
|
19
|
+
{
|
|
20
|
+
cwd: path.resolve(__dirname, '..'),
|
|
21
|
+
stdio: 'pipe',
|
|
22
|
+
},
|
|
23
|
+
);
|
|
104
24
|
});
|
|
105
25
|
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
"triggerStyle": "Current Price set distance from center of positions",
|
|
110
|
-
"tickDistanceFromCenter": 100,
|
|
111
|
-
"strategy": "Classic",
|
|
112
|
-
"liquidityShape": "Linear",
|
|
113
|
-
"poolFee": 3000,
|
|
114
|
-
"placementType": "Position over current price",
|
|
115
|
-
"period":6,
|
|
116
|
-
"standardDeviations":2.0,
|
|
117
|
-
"positionSize": 600
|
|
118
|
-
}`
|
|
119
|
-
myModule.initialize(config);
|
|
120
|
-
const positions = '[[257100],[257300],[1]]'
|
|
121
|
-
const currentTick = '257301'
|
|
122
|
-
const timeSinceLastExecution = '5600'
|
|
123
|
-
const result = myModule.execute
|
|
124
|
-
(JSON.stringify(candles2), positions, currentTick, timeSinceLastExecution);
|
|
125
|
-
expect(result).not.toEqual('continue')
|
|
126
|
-
});
|
|
26
|
+
it('preserves json-as runtime compatibility for legacy prices and dynamic Merkle proof keys', () => {
|
|
27
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
28
|
+
const wasmPath = path.join(os.tmpdir(), 'strategy-utils-json-compat.fixture.wasm');
|
|
127
29
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const currentTick = '257251'
|
|
144
|
-
const timeSinceLastExecution = '5600'
|
|
145
|
-
const result = myModule.execute
|
|
146
|
-
(JSON.stringify(candles2), positions, currentTick, timeSinceLastExecution);
|
|
147
|
-
expect(result).toEqual('continue')
|
|
148
|
-
});
|
|
30
|
+
execFileSync(
|
|
31
|
+
path.resolve(repoRoot, 'node_modules/.bin/asc'),
|
|
32
|
+
[
|
|
33
|
+
path.resolve(__dirname, 'fixtures/json-compat.ts'),
|
|
34
|
+
'--config',
|
|
35
|
+
path.resolve(repoRoot, 'asconfig.json'),
|
|
36
|
+
'--outFile',
|
|
37
|
+
wasmPath,
|
|
38
|
+
'--exportRuntime',
|
|
39
|
+
],
|
|
40
|
+
{
|
|
41
|
+
cwd: repoRoot,
|
|
42
|
+
stdio: 'pipe',
|
|
43
|
+
},
|
|
44
|
+
);
|
|
149
45
|
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
"elapsedTendTime": 604800,
|
|
153
|
-
"triggerStyle": "Price moves percentage of active range away",
|
|
154
|
-
"percentageOfPositionRangeToTrigger": 1,
|
|
155
|
-
"strategy": "Classic",
|
|
156
|
-
"liquidityShape": "Linear",
|
|
157
|
-
"poolFee": 3000,
|
|
158
|
-
"placementType": "Position over current price",
|
|
159
|
-
"period":6,
|
|
160
|
-
"standardDeviations":2.0,
|
|
161
|
-
"positionSize": 600
|
|
162
|
-
}`
|
|
163
|
-
myModule.initialize(config);
|
|
164
|
-
const positions = '[[257100],[257300],[1]]'
|
|
165
|
-
const currentTick = '257301'
|
|
166
|
-
const timeSinceLastExecution = '5600'
|
|
167
|
-
const result = myModule.execute
|
|
168
|
-
(JSON.stringify(candles2), positions, currentTick, timeSinceLastExecution);
|
|
169
|
-
expect(result).not.toEqual('continue')
|
|
170
|
-
});
|
|
46
|
+
const wasm = loader.instantiateSync(fs.readFileSync(wasmPath), {});
|
|
47
|
+
const { parseStringifiedPrices, roundTripMerkleProofActions, __getString, __newString } = wasm.exports;
|
|
171
48
|
|
|
172
|
-
|
|
173
|
-
const config = `{
|
|
174
|
-
"elapsedTendTime": 604800,
|
|
175
|
-
"triggerStyle": "Price moves one way past positions",
|
|
176
|
-
"triggerWhenOver": true,
|
|
177
|
-
"poolFee": 3000,
|
|
178
|
-
"placementType": "Position over current price",
|
|
179
|
-
"period":6,
|
|
180
|
-
"standardDeviations":2.0,
|
|
181
|
-
"positionSize": 600
|
|
182
|
-
}`
|
|
183
|
-
myModule.initialize(config);
|
|
184
|
-
const positions = '[[257100],[257300],[1]]'
|
|
185
|
-
const currentTick = '257251'
|
|
186
|
-
const timeSinceLastExecution = '5600'
|
|
187
|
-
const result = myModule.execute
|
|
188
|
-
(JSON.stringify(candles2), positions, currentTick, timeSinceLastExecution);
|
|
189
|
-
expect(result).toEqual('continue')
|
|
190
|
-
});
|
|
49
|
+
expect(__getString(parseStringifiedPrices())).toBe('ok');
|
|
191
50
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
"period":6,
|
|
203
|
-
"standardDeviations":2.0,
|
|
204
|
-
"positionSize": 600
|
|
205
|
-
}`
|
|
206
|
-
myModule.initialize(config);
|
|
207
|
-
const positions = '[[257100],[257300],[1]]'
|
|
208
|
-
const currentTick = '257000'
|
|
209
|
-
const timeSinceLastExecution = '5600'
|
|
210
|
-
const result = myModule.execute
|
|
211
|
-
(JSON.stringify(candles2), positions, currentTick, timeSinceLastExecution);
|
|
212
|
-
expect(result).toEqual('continue')
|
|
213
|
-
});
|
|
51
|
+
const input = JSON.stringify({
|
|
52
|
+
leafs: [],
|
|
53
|
+
proofsByAction: {
|
|
54
|
+
approveToken: ['0xaaa'],
|
|
55
|
+
rebalanceVault: ['0xbbb', '0xccc'],
|
|
56
|
+
},
|
|
57
|
+
proofsByDigest: {
|
|
58
|
+
'0x111': ['0xddd'],
|
|
59
|
+
},
|
|
60
|
+
});
|
|
214
61
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
"triggerStyle": "Price moves one way past positions",
|
|
219
|
-
"triggerWhenOver": true,
|
|
220
|
-
"strategy": "Bollinger Band",
|
|
221
|
-
"liquidityShape": "Linear",
|
|
222
|
-
"poolFee": 500,
|
|
223
|
-
"placementType": "Position over current price",
|
|
224
|
-
"triggerWhenOver": true,
|
|
225
|
-
"lookback": 12,
|
|
226
|
-
"period":6,
|
|
227
|
-
"standardDeviations":2.0,
|
|
228
|
-
}`
|
|
229
|
-
myModule.initialize(config);
|
|
230
|
-
const positions = '[[257100],[257300],[1]]'
|
|
231
|
-
const currentTick = '257301'
|
|
232
|
-
const timeSinceLastExecution = '5600'
|
|
233
|
-
const result = myModule.execute
|
|
234
|
-
(JSON.stringify([...candles2]), positions, currentTick, timeSinceLastExecution);
|
|
235
|
-
expect(result).not.toEqual('continue')
|
|
62
|
+
const output = JSON.parse(__getString(roundTripMerkleProofActions(__newString(input))));
|
|
63
|
+
expect(output.approveToken).toStrictEqual(['0xaaa']);
|
|
64
|
+
expect(output.rebalanceVault).toStrictEqual(['0xbbb', '0xccc']);
|
|
236
65
|
});
|
|
237
|
-
|
|
238
|
-
});
|
|
66
|
+
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { Candle } from "./types/Candle";
|
|
2
|
-
import { RawTradeData } from "./types/RawTradeData";
|
|
3
|
-
|
|
4
|
-
// CandlestickConverter class provides a static method to convert raw trade data into OHLCV format
|
|
5
|
-
export class CandlestickConverter {
|
|
6
|
-
// convertToOHLCV method accepts raw trade data and a period (default is 3600 seconds or 1 hour)
|
|
7
|
-
// and returns the data in OHLCV format
|
|
8
|
-
static convertToOHLCV(rawData: RawTradeData[], period: i32 = 3600): Candle[] {
|
|
9
|
-
// If the raw data is empty, an error is thrown
|
|
10
|
-
if (rawData.length === 0) {
|
|
11
|
-
throw new Error("Input data is empty");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Array to hold the converted data
|
|
15
|
-
let ohlcvData: Candle[] = [];
|
|
16
|
-
|
|
17
|
-
// Initialize variables
|
|
18
|
-
let i: i32 = 0;
|
|
19
|
-
let currentTimestamp: i32 = Math.floor(rawData[0].timestamp / period) * period;
|
|
20
|
-
let open: f64 = rawData[0].price;
|
|
21
|
-
let high: f64 = rawData[0].price;
|
|
22
|
-
let low: f64 = rawData[0].price;
|
|
23
|
-
let close: f64 = rawData[0].price;
|
|
24
|
-
let volume: f64 = 0.0;
|
|
25
|
-
|
|
26
|
-
// Loop through all raw data
|
|
27
|
-
while (i < rawData.length) {
|
|
28
|
-
// For each period, set the initial values of open, high, low to the closing price of the last period
|
|
29
|
-
// and reset the volume to 0
|
|
30
|
-
open = close;
|
|
31
|
-
high = close;
|
|
32
|
-
low = close;
|
|
33
|
-
volume = 0.0;
|
|
34
|
-
|
|
35
|
-
// Process all raw data within the current period
|
|
36
|
-
while (i < rawData.length && rawData[i].timestamp < currentTimestamp + period) {
|
|
37
|
-
// Set the open price to the price of the first trade in the period
|
|
38
|
-
open = i == 0 ? rawData[i].price : open;
|
|
39
|
-
// Update high and low prices
|
|
40
|
-
high = Math.max(high, rawData[i].price);
|
|
41
|
-
low = Math.min(low, rawData[i].price);
|
|
42
|
-
// Update close price to the price of the last trade in the period
|
|
43
|
-
close = rawData[i].price;
|
|
44
|
-
// Update the total volume
|
|
45
|
-
volume += rawData[i].volume;
|
|
46
|
-
i++;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Create a new Candle object with the calculated OHLCV values and add it to the array
|
|
50
|
-
let ohlcv: Candle = new Candle(currentTimestamp, open, high, low, close, volume);
|
|
51
|
-
ohlcvData.push(ohlcv);
|
|
52
|
-
|
|
53
|
-
// Move to the next period
|
|
54
|
-
currentTimestamp += period;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Return the converted data
|
|
58
|
-
return ohlcvData;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
class Candle {
|
|
2
|
-
timestamp: f64;
|
|
3
|
-
open: f64;
|
|
4
|
-
high: f64;
|
|
5
|
-
close: f64;
|
|
6
|
-
low: f64;
|
|
7
|
-
volume: f64;
|
|
8
|
-
|
|
9
|
-
constructor(
|
|
10
|
-
timestamp: f64,
|
|
11
|
-
open: f64,
|
|
12
|
-
high: f64,
|
|
13
|
-
close: f64,
|
|
14
|
-
low: f64,
|
|
15
|
-
volume: f64
|
|
16
|
-
) {
|
|
17
|
-
this.timestamp = timestamp;
|
|
18
|
-
this.open = open;
|
|
19
|
-
this.high = high;
|
|
20
|
-
this.close = close;
|
|
21
|
-
this.low = low;
|
|
22
|
-
this.volume = volume;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
class MarketFeedAggregator {
|
|
27
|
-
data: Array<Candle>;
|
|
28
|
-
|
|
29
|
-
constructor(data: Array<Candle>) {
|
|
30
|
-
this.data = data;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
geometricMean(): Candle {
|
|
34
|
-
let n: f64 = f64(this.data.length);
|
|
35
|
-
let logSum = new Candle(0, 0, 0, 0, 0, 0);
|
|
36
|
-
|
|
37
|
-
for (let i = 0; i < this.data.length; i++) {
|
|
38
|
-
let candle = this.data[i];
|
|
39
|
-
logSum.timestamp += Mathf.log(candle.timestamp);
|
|
40
|
-
logSum.open += Mathf.log(candle.open);
|
|
41
|
-
logSum.high += Mathf.log(candle.high);
|
|
42
|
-
logSum.close += Mathf.log(candle.close);
|
|
43
|
-
logSum.low += Mathf.log(candle.low);
|
|
44
|
-
logSum.volume += Mathf.log(candle.volume);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return new Candle(
|
|
48
|
-
Mathf.exp(logSum.timestamp / n),
|
|
49
|
-
Mathf.exp(logSum.open / n),
|
|
50
|
-
Mathf.exp(logSum.high / n),
|
|
51
|
-
Mathf.exp(logSum.close / n),
|
|
52
|
-
Mathf.exp(logSum.low / n),
|
|
53
|
-
Mathf.exp(logSum.volume / n)
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
timeWeightedAveragePrice(): f64 {
|
|
58
|
-
let sumProduct: f64 = 0;
|
|
59
|
-
let sumTime: f64 = 0;
|
|
60
|
-
|
|
61
|
-
for (let i = 0; i < this.data.length; i++) {
|
|
62
|
-
let candle = this.data[i];
|
|
63
|
-
let midPrice: f64 = (candle.high + candle.low) / 2;
|
|
64
|
-
sumProduct += midPrice * candle.timestamp;
|
|
65
|
-
sumTime += candle.timestamp;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return sumProduct / sumTime;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
volumeWeightedAveragePrice(): f64 {
|
|
72
|
-
let sumProduct: f64 = 0;
|
|
73
|
-
let sumVolume: f64 = 0;
|
|
74
|
-
|
|
75
|
-
for (let i = 0; i < this.data.length; i++) {
|
|
76
|
-
let candle = this.data[i];
|
|
77
|
-
let midPrice: f64 = (candle.high + candle.low) / 2;
|
|
78
|
-
sumProduct += midPrice * candle.volume;
|
|
79
|
-
sumVolume += candle.volume;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return sumProduct / sumVolume;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
weightedMean(weights: Candle): Candle {
|
|
86
|
-
let sumWeights: f64 = 0;
|
|
87
|
-
let sumProduct: Candle = new Candle(0, 0, 0, 0, 0, 0);
|
|
88
|
-
|
|
89
|
-
for (let i = 0; i < this.data.length; i++) {
|
|
90
|
-
let candle = this.data[i];
|
|
91
|
-
sumProduct.timestamp += weights.timestamp * candle.timestamp;
|
|
92
|
-
sumProduct.open += weights.open * candle.open;
|
|
93
|
-
sumProduct.high += weights.high * candle.high;
|
|
94
|
-
sumProduct.close += weights.close * candle.close;
|
|
95
|
-
sumProduct.low += weights.low * candle.low;
|
|
96
|
-
sumProduct.volume += weights.volume * candle.volume;
|
|
97
|
-
|
|
98
|
-
sumWeights +=
|
|
99
|
-
weights.timestamp +
|
|
100
|
-
weights.open +
|
|
101
|
-
weights.high +
|
|
102
|
-
weights.close +
|
|
103
|
-
weights.low +
|
|
104
|
-
weights.volume;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return new Candle(
|
|
108
|
-
sumProduct.timestamp / sumWeights,
|
|
109
|
-
sumProduct.open / sumWeights,
|
|
110
|
-
sumProduct.high / sumWeights,
|
|
111
|
-
sumProduct.close / sumWeights,
|
|
112
|
-
sumProduct.low / sumWeights,
|
|
113
|
-
sumProduct.volume / sumWeights
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
arithmeticMean(): Candle {
|
|
118
|
-
let n: f64 = f64(this.data.length);
|
|
119
|
-
let sum: Candle = new Candle(0, 0, 0, 0, 0, 0);
|
|
120
|
-
|
|
121
|
-
for (let i = 0; i < this.data.length; i++) {
|
|
122
|
-
let candle = this.data[i];
|
|
123
|
-
sum.timestamp += candle.timestamp;
|
|
124
|
-
sum.open += candle.open;
|
|
125
|
-
sum.high += candle.high;
|
|
126
|
-
sum.close += candle.close;
|
|
127
|
-
sum.low += candle.low;
|
|
128
|
-
sum.volume += candle.volume;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return new Candle(
|
|
132
|
-
sum.timestamp / n,
|
|
133
|
-
sum.open / n,
|
|
134
|
-
sum.high / n,
|
|
135
|
-
sum.close / n,
|
|
136
|
-
sum.low / n,
|
|
137
|
-
sum.volume / n
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export class SlidingWindow<T> {
|
|
2
|
-
private data: Array<T>;
|
|
3
|
-
private windowSize: i32;
|
|
4
|
-
private formula: (window: Array<T>) => T;
|
|
5
|
-
private cursor: i32;
|
|
6
|
-
|
|
7
|
-
constructor(windowSize: i32, formula: (window: Array<T>) => T) {
|
|
8
|
-
if (windowSize < 1) {
|
|
9
|
-
throw new Error("windowSize must be greater than 0");
|
|
10
|
-
}
|
|
11
|
-
if (formula === null) {
|
|
12
|
-
throw new Error("formula function must be provided");
|
|
13
|
-
}
|
|
14
|
-
this.windowSize = windowSize;
|
|
15
|
-
this.formula = formula;
|
|
16
|
-
this.data = new Array<T>(windowSize);
|
|
17
|
-
this.cursor = 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
addValue(value: T): void {
|
|
21
|
-
this.data[this.cursor] = value;
|
|
22
|
-
this.cursor = (this.cursor + 1) % this.windowSize;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
getLastValue(): T {
|
|
26
|
-
let index = this.cursor === 0 ? this.windowSize - 1 : this.cursor - 1;
|
|
27
|
-
return this.data[index];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
clear(): void {
|
|
31
|
-
this.data.fill(null);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
setWindowSize(size: i32): void {
|
|
35
|
-
if (size < 1) {
|
|
36
|
-
throw new Error("windowSize must be greater than 0");
|
|
37
|
-
}
|
|
38
|
-
this.windowSize = size;
|
|
39
|
-
this.data = new Array<T>(size);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getWindow(): Array<T> {
|
|
43
|
-
let result = new Array<T>(this.windowSize);
|
|
44
|
-
for (let i = 0; i < this.windowSize; i++) {
|
|
45
|
-
let index = (this.cursor + i) % this.windowSize;
|
|
46
|
-
result[i] = this.data[index];
|
|
47
|
-
}
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getFormulaResult(): T {
|
|
52
|
-
let window = this.getWindow();
|
|
53
|
-
return this.formula(window);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
isStable(): bool {
|
|
57
|
-
return this.data.length >= this.windowSize;
|
|
58
|
-
}
|
|
59
|
-
}
|
package/assembly/utils/env.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export declare function generateCandles(data: string, candleSize: string): string;
|
|
2
|
-
@external("env", "ccxt_fetchOHLCV")
|
|
3
|
-
declare function _ccxt_fetchOHLCV(exchangeId: string, symbol: string, timeframe: string, limit: number, since: number): StaticArray<StaticArray<f64>>;
|
|
4
|
-
|
|
5
|
-
// Required for asyncify
|
|
6
|
-
// @ts-ignore: Global should exist here
|
|
7
|
-
@global let __ASYNCIFY_INITIALIZED = false;
|
|
8
|
-
@external("env", "_initAsyncify")
|
|
9
|
-
declare function _initAsyncify(asyncify_data_ptr: usize, stack_pointer: usize): void;
|
|
10
|
-
|
|
11
|
-
export function ccxt_fetchOHLCV(exchangeId: string, symbol: string, timeframe: string, limit: number, since: number): StaticArray<StaticArray<f64>> {
|
|
12
|
-
if (!__ASYNCIFY_INITIALIZED) {
|
|
13
|
-
// We need to initialize space for Asyncify to work.
|
|
14
|
-
// Asyncify will create a full - duplex communication channel through this bit of memory.
|
|
15
|
-
// For every asyncify-enabled function, make sure to add this
|
|
16
|
-
// memory.data() reserves a section of data that is not touched by the Garbage Collector
|
|
17
|
-
// We can only do this once or else we will cause a memory leak and eventual overflow
|
|
18
|
-
// It will not grow past the stack pointer which is where real data starts.
|
|
19
|
-
_initAsyncify(memory.data(8, 16), __stack_pointer);
|
|
20
|
-
__ASYNCIFY_INITIALIZED = true;
|
|
21
|
-
}
|
|
22
|
-
return _ccxt_fetchOHLCV(exchangeId, symbol, timeframe, limit, since);
|
|
23
|
-
}
|