@steerprotocol/strategy-utils 2.2.1 → 3.0.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.
@@ -0,0 +1,469 @@
1
+ import { JSON } from "json-as";
2
+ import { Position } from "./types";
3
+
4
+ // NOTE: Trigger functions return true when action should be taken, if false then the strategy can return 'continue' to skip exeuction
5
+ // Implementation might look like the following:
6
+ // const trigger = getTriggerStyle(configJson.triggerStyle)
7
+ // const triggerObj = new TriggerConfigHelper(configJson.triggerWhenOver, configJson.tickPriceTrigger, configJson.percentageOfPositionRangeToTrigger, configJson.tickDistanceFromCenter, configJson.elapsedTendTime)
8
+ // if (!shouldTriggerExecution(trigger, triggerObj, _positions, _currentTick, _timeSinceLastExecution)) return 'continue'
9
+
10
+ // Gets active range from the output of LM.getPositions()
11
+ export function parseActiveRange(_positions: string): Position {
12
+ // _positions will be '[[#,#,#],[#,#,#],[#,#,#]]' presumably. lower, upper, weight
13
+ // clean up our list by removing spaces and brackets
14
+ let positions = _positions.replaceAll(' ','');
15
+ positions = positions.replaceAll('[','');
16
+ let rangeArray = positions.split(']',2);
17
+ let startTick = rangeArray[0].split(',')[0];
18
+ let endRange = rangeArray[1].split(']',2);
19
+ const endTicks = endRange[0].split(',');
20
+ let endTick = endTicks[endTicks.length-1]
21
+ // if trailing comma
22
+ if (endTick == '') endTick = endTicks[endTicks.length-2]
23
+ const strArrays = [startTick, endTick];
24
+ // check null
25
+ if (strArrays[0] == '' || strArrays[0] == null || strArrays[1] == '' || strArrays[1] == null) {
26
+ return new Position(0, 0, 0);
27
+ }
28
+ // else return normal
29
+ const lowerTick = i32(parseInt(strArrays[0]));
30
+ const upperTick = i32(parseInt(strArrays[1]));
31
+ // weights shouldn't matter in this context, we just want the total active range
32
+ return new Position(lowerTick, upperTick, 100);
33
+ }
34
+
35
+ export function emptyCurrentPosition(currentPosition: Position): boolean {
36
+ // return true if current position ticks are not the same
37
+ return currentPosition.startTick == currentPosition.endTick
38
+ }
39
+
40
+ // where rebalance width is how far off on either side the trigger should activate
41
+ export function triggerFromDistance(currentPosition: Position, rebalanceWidth: i64, currentTick: i64): boolean {
42
+ if (emptyCurrentPosition(currentPosition)) return true
43
+ const centerPosition = (currentPosition.endTick + currentPosition.startTick) / 2
44
+ const upperTrigger = centerPosition + (rebalanceWidth)
45
+ const lowerTrigger = centerPosition - (rebalanceWidth)
46
+ // In bounds? return false to continue (skip exec), returns true to execute
47
+ return !((currentTick <= upperTrigger && currentTick >= lowerTrigger))
48
+ }
49
+
50
+ // tick moves % away from center of position range
51
+ export function triggerFromPercentage(currentPosition: Position, rebalancePercentage: f64, currentTick: i64): boolean {
52
+ if (emptyCurrentPosition(currentPosition)) return true
53
+ const side = (currentPosition.endTick - currentPosition.startTick) / 2
54
+ const centerPosition = (currentPosition.endTick + currentPosition.startTick) / 2
55
+ const triggerDiff = i64(Math.round(f64(side) * rebalancePercentage))
56
+ const upperTrigger = centerPosition + triggerDiff
57
+ const lowerTrigger = centerPosition - triggerDiff
58
+ // In bounds? return false to continue (skip exec), returns true to execute
59
+ return !((currentTick <= upperTrigger && currentTick >= lowerTrigger))
60
+ }
61
+
62
+ // when current tick is no longer in the current position range
63
+ export function triggerPositionsInactive(currentPosition: Position, currentTick: i64): boolean {
64
+ // return JSON.stringify(currentPosition)
65
+ if (emptyCurrentPosition(currentPosition)) return true
66
+
67
+ return (!((currentTick <= currentPosition.endTick && currentTick >= currentPosition.startTick)))
68
+ }
69
+
70
+ // when tick goes over or under specified tick, trigger rebalance
71
+ // export function triggerFromSpecifiedPrice(triggerTick: i64, currentTick: i64, triggerOver: boolean): boolean {
72
+ // // if trigger over, return true when currentTick > triggerTick
73
+ // if (triggerOver) {
74
+ // return currentTick > triggerTick
75
+ // }
76
+ // // trigger under, if current price is less than trigger tick return true
77
+ // return triggerTick > currentTick
78
+ // }
79
+
80
+ // when tick goes over or under current positions, trigger rebalance, only moves one way
81
+ export function triggerPricePastPositions(currentPosition: Position, currentTick: i64, triggerOver: boolean): boolean {
82
+ if (emptyCurrentPosition(currentPosition)) return true
83
+ if (triggerOver) {
84
+ // if the currentTick is over the endTick, rebal
85
+ return currentTick > i64(currentPosition.endTick)
86
+ }
87
+ // if current tick is under start tick
88
+ return currentTick < i64(currentPosition.startTick)
89
+ }
90
+
91
+ export const enum TriggerStyle {
92
+ DistanceFromCenterOfPositions,
93
+ PercentageChangeFromPositionRange,
94
+ PositionsInactive,
95
+ // SpecificPrice,
96
+ PricePastPositions,
97
+ None,
98
+ }
99
+
100
+ // export function TriggerStyleLookup(triggerStyle: TriggerStyle): string {
101
+ // switch (triggerStyle) {
102
+ // case TriggerStyle.DistanceFromCenterOfPositions:
103
+ // return "Distance from center of position(s)";
104
+ // case TriggerStyle.PercentageChangeFromPositionRange:
105
+ // return "Percentage of position(s)";
106
+ // case TriggerStyle.PositionsInactive:
107
+ // return "Positions Inactive";
108
+ // // case TriggerStyle.SpecificPrice:
109
+ // // return "Specific Price";
110
+ // case TriggerStyle.PricePastPositions:
111
+ // return "Price moved past position(s)";
112
+ // case TriggerStyle.None:
113
+ // return 'None'
114
+
115
+ // default:
116
+ // throw new Error(`Unknown trigger style: ${triggerStyle}`);
117
+ // }
118
+ // }
119
+
120
+ // export class DistanceFromCenterOfPositionsOptions {
121
+ // tickDistanceFromCenter: i64 = 0;
122
+ // requiredDataTypes: string[] = ['Liquidity Manager Positions', 'V3 Pool Current Tick'];
123
+ // }
124
+
125
+ // export class PercentageChangeFromPositionRangeOptions {
126
+ // percentageOfPositionRangeToTrigger: f64 = 0.0;
127
+ // requiredDataTypes: string[] = ['Liquidity Manager Positions', 'V3 Pool Current Tick'];
128
+ // }
129
+
130
+ // export class PositionsInactiveOptions {
131
+ // requiredDataTypes: string[] = ['Liquidity Manager Positions', 'V3 Pool Current Tick'];
132
+ // }
133
+
134
+ // export class SpecificPriceOptions {
135
+ // tickPriceTrigger: i64 = 0;
136
+ // triggerWhenOver: boolean = false;
137
+ // requiredDataTypes: string[] = ['V3 Pool Current Tick'];
138
+ // }
139
+
140
+ // export class PricePastPositionsOptions {
141
+ // triggerWhenOver: boolean = false;
142
+ // requiredDataTypes: string[] = ['Liquidity Manager Positions', 'V3 Pool Current Tick'];
143
+ // }
144
+
145
+ // @ts-ignore
146
+ @json
147
+ export class TriggerConfigHelper {
148
+ // triggerType: string = "Price leaves active range";
149
+ triggerWhenOver: boolean = false;
150
+ tickPriceTrigger: i64 = 0;
151
+ percentageOfPositionRangeToTrigger: f64 = 0.0;
152
+ tickDistanceFromCenter: i64 = 0;
153
+ elapsedTendTime: i64 = 0;
154
+ constructor( t: boolean, tpt: i64, poptrr:f64, tdfc: i64, ett: i64) {
155
+ if(t) this.triggerWhenOver = t
156
+ if(tpt) this.tickPriceTrigger = tpt
157
+ if(poptrr) this.percentageOfPositionRangeToTrigger = poptrr
158
+ if(tdfc) this.tickDistanceFromCenter = tdfc
159
+ if(ett) this.elapsedTendTime = ett
160
+ }
161
+ }
162
+
163
+ export function getTriggerExpectedDataTypes(triggerStyle: TriggerStyle): string[] {
164
+ // currently we use only this list
165
+ const typicalTypes = ["Liquidity Manager Positions", "V3 Pool Current Tick", "Time Since Last Execution"]
166
+ switch (triggerStyle) {
167
+ case TriggerStyle.DistanceFromCenterOfPositions:
168
+ return typicalTypes;
169
+ case TriggerStyle.PercentageChangeFromPositionRange:
170
+ return typicalTypes;
171
+ case TriggerStyle.PositionsInactive:
172
+ return typicalTypes;
173
+ // case TriggerStyle.SpecificPrice:
174
+ // return "Specific Price";
175
+ case TriggerStyle.PricePastPositions:
176
+ return typicalTypes;
177
+ case TriggerStyle.None:
178
+ // return no data connectors for none
179
+ return [];
180
+
181
+ default:
182
+ throw new Error(`Unknown trigger style: ${triggerStyle}`);
183
+ }
184
+ }
185
+
186
+
187
+ export function getExpectedDataTypes(strategyDataConnectors: string[], triggerStyle: string): string {
188
+ const style = getTriggerStyle(triggerStyle)
189
+ const triggerDatas = getTriggerExpectedDataTypes(style)
190
+ const dataList = strategyDataConnectors.concat(triggerDatas)
191
+ let completeList = '['
192
+ for (let i: i32 = 0; i < dataList.length; i++) {
193
+ completeList += (' \"' + dataList[i] + '\"')
194
+ if (i != dataList.length-1) completeList += ','
195
+ }
196
+ completeList += ']'
197
+ return completeList
198
+ }
199
+
200
+ // no AS func overloading yet :(
201
+ // export function shouldTriggerExecution_1(triggerStyle: TriggerStyle, triggerOptions: TriggerConfigHelper, dataConnector1: string) : boolean {
202
+ // // parse options
203
+ // switch (triggerStyle) {
204
+ // case TriggerStyle.SpecificPrice:
205
+ // // parse current tick from dc1
206
+ // const currentTick = parseInt(dataConnector1)
207
+ // if (!currentTick) return true
208
+ // // tick and over
209
+ // return triggerFromSpecifiedPrice(triggerOptions.tickPriceTrigger, currentTick, triggerOptions.triggerWhenOver)
210
+ // default:
211
+ // return true
212
+ // }
213
+ // }
214
+
215
+ // currently implemented as :: ulm positions [0], current tick [1], time since last execution [2]
216
+ // as more types are added this logic path with be redone
217
+ export function shouldTriggerExecution(
218
+ _triggerStyle: string,
219
+ triggerOptions: TriggerConfigHelper,
220
+ dataConnector1: string,
221
+ dataConnector2: string,
222
+ dataConnector3: string) : boolean {
223
+
224
+ // possible dc inputs
225
+ let currentPositionRange: Position;
226
+ let currentTick: i64;
227
+ const triggerStyle = getTriggerStyle(_triggerStyle)
228
+
229
+
230
+
231
+ let timeSinceLastExecution: i64;
232
+
233
+ switch (triggerStyle) {
234
+ case TriggerStyle.DistanceFromCenterOfPositions:
235
+ // parse ulm positions [0], current tick [1]
236
+ // @ts-ignore
237
+ timeSinceLastExecution = i64(parseInt(dataConnector3))
238
+ if (timeSinceLastExecution >= i64(triggerOptions.elapsedTendTime)) return true
239
+ currentPositionRange = parseActiveRange(dataConnector1)
240
+ currentTick = i64(parseInt(dataConnector2))
241
+ return triggerFromDistance(currentPositionRange, triggerOptions.tickDistanceFromCenter, currentTick)
242
+
243
+ case TriggerStyle.PercentageChangeFromPositionRange:
244
+ // parse ulm positions [0], current tick [1]
245
+ timeSinceLastExecution = i64(parseInt(dataConnector3))
246
+ if (timeSinceLastExecution >= i64(triggerOptions.elapsedTendTime)) return true
247
+ currentPositionRange= parseActiveRange(dataConnector1)
248
+ currentTick = i64(parseInt(dataConnector2))
249
+ return triggerFromPercentage(currentPositionRange, triggerOptions.percentageOfPositionRangeToTrigger, currentTick)
250
+
251
+ case TriggerStyle.PositionsInactive:
252
+ // parse ulm positions [0], current tick [1]
253
+ timeSinceLastExecution = i64(parseInt(dataConnector3))
254
+ if (timeSinceLastExecution >= i64(triggerOptions.elapsedTendTime)) return true
255
+ currentPositionRange = parseActiveRange(dataConnector1)
256
+ currentTick = i64(parseInt(dataConnector2))
257
+ return triggerPositionsInactive(currentPositionRange, currentTick)
258
+
259
+ case TriggerStyle.PricePastPositions:
260
+ // parse ulm positions [0], current tick [1]
261
+ timeSinceLastExecution = i64(parseInt(dataConnector3))
262
+ if (timeSinceLastExecution >= i64(triggerOptions.elapsedTendTime)) return true
263
+ currentPositionRange = parseActiveRange(dataConnector1)
264
+ currentTick = i64(parseInt(dataConnector2))
265
+ return triggerPricePastPositions(currentPositionRange, currentTick, triggerOptions.triggerWhenOver)
266
+ default:
267
+ return true
268
+ }
269
+ }
270
+
271
+ export function getTriggerStyle(trigger: string): TriggerStyle {
272
+ if (trigger === 'Current Price set distance from center of positions') {
273
+ return TriggerStyle.DistanceFromCenterOfPositions;
274
+ } else if (trigger === 'Price leaves active range') {
275
+ return TriggerStyle.PositionsInactive;
276
+ } else if (trigger === 'Price moves percentage of active range away') {
277
+ return TriggerStyle.PercentageChangeFromPositionRange;
278
+ } else if (trigger === 'Price moves one way past positions') {
279
+ return TriggerStyle.PricePastPositions;
280
+ } else {
281
+ return TriggerStyle.None;
282
+ }
283
+ }
284
+
285
+ function getTriggerName(trigger: TriggerStyle): string {
286
+ if (trigger === TriggerStyle.DistanceFromCenterOfPositions) {
287
+ return 'Current Price set distance from center of positions';
288
+ } else if (trigger === TriggerStyle.PositionsInactive) {
289
+ return 'Price leaves active range';
290
+ } else if (trigger === TriggerStyle.PercentageChangeFromPositionRange) {
291
+ return 'Price moves percentage of active range away';
292
+ } else if (trigger === TriggerStyle.PricePastPositions) {
293
+ return 'Price moves one way past positions';
294
+ } else {
295
+ return 'None';
296
+ }
297
+ }
298
+
299
+
300
+ export function triggerPropertyHelper(omit: TriggerStyle[] = []): string {
301
+ const triggerList = [
302
+ TriggerStyle.DistanceFromCenterOfPositions,
303
+ TriggerStyle.None,
304
+ TriggerStyle.PercentageChangeFromPositionRange,
305
+ TriggerStyle.PositionsInactive,
306
+ TriggerStyle.PricePastPositions,
307
+ ];
308
+
309
+ const filteredTriggers: string[] = [
310
+ 'Current Price set distance from center of positions',
311
+ 'Price leaves active range',
312
+ 'Price moves percentage of active range away',
313
+ 'Price moves one way past positions',
314
+ 'None'
315
+ ];
316
+
317
+
318
+ return `"triggerStyle": {
319
+ "enum": ${JSON.stringify(filteredTriggers)},
320
+ "title": "Logic to trigger new positions",
321
+ "type": "string",
322
+ "default": "None"
323
+ }`;
324
+ }
325
+
326
+ export function allOfTrigger(strategyDataConnectors: string[]): string {
327
+ return `{
328
+ "if": {
329
+ "properties": {
330
+ "triggerStyle": {
331
+ "const": "None"
332
+ }
333
+ }
334
+ },
335
+ "then": {
336
+ "properties": {
337
+ "expectedDataTypes": {
338
+ "const": ${getExpectedDataTypes(strategyDataConnectors, "None")},
339
+ "default": ${getExpectedDataTypes(strategyDataConnectors, "None")},
340
+ "hidden": true,
341
+ "type": "string"
342
+ }
343
+ },
344
+ "required": ["expectedDataTypes"]
345
+ }
346
+ },
347
+ {
348
+ "if": {
349
+ "properties": {
350
+ "triggerStyle": {
351
+ "const": "Current Price set distance from center of positions"
352
+ }
353
+ }
354
+ },
355
+ "then": {
356
+ "properties": {
357
+ "tickDistanceFromCenter": {
358
+ "type": "integer",
359
+ "title": "Tick Distance",
360
+ "description": "The number of ticks (basis points) from center price of positions to trigger setting new positions",
361
+ "detailedDescription": "The static number of ticks from the center of the active range to trigger: if our position goes from 0-100, and we have a tick distance of 75, we will go out 75 ticks both ways from the center of our positions (50). This means we will skip execution only if the current tick is between -25 and 125. Future positions will determine where the center of the trigger range is located."
362
+ },
363
+ "expectedDataTypes": {
364
+ "const": ${getExpectedDataTypes(strategyDataConnectors, "Current Price set distance from center of positions")},
365
+ "default": ${getExpectedDataTypes(strategyDataConnectors, "Current Price set distance from center of positions")},
366
+ "hidden": true,
367
+ "type": "string"
368
+ },
369
+ "elapsedTendTime": {
370
+ "type": "number",
371
+ "title": "Max time between tends",
372
+ "description": "If trigger conditions have not been met for this period of time, the strategy will execute regardless of trigger logic to update vault accounting.",
373
+ "default": 1209600
374
+ }
375
+ },
376
+ "required": ["tickDistanceFromCenter", "expectedDataTypes", "elapsedTendTime"]
377
+ }
378
+ },
379
+ {
380
+ "if": {
381
+ "properties": {
382
+ "triggerStyle": {
383
+ "const": "Price leaves active range"
384
+ }
385
+ }
386
+ },
387
+ "then": {
388
+ "properties": {
389
+ "expectedDataTypes": {
390
+ "const": ${getExpectedDataTypes(strategyDataConnectors, "Price leaves active range")},
391
+ "default": ${getExpectedDataTypes(strategyDataConnectors, "Price leaves active range")},
392
+ "hidden": true,
393
+ "type": "string"
394
+ },
395
+ "elapsedTendTime": {
396
+ "type": "number",
397
+ "title": "Max time between tends",
398
+ "description": "If trigger conditions have not been met for this period of time, the strategy will execute regardless of trigger logic to update vault accounting.",
399
+ "default": 1209600
400
+ }
401
+ },
402
+ "required": ["expectedDataTypes", "elapsedTendTime"]
403
+ }
404
+ },
405
+ {
406
+ "if": {
407
+ "properties": {
408
+ "triggerStyle": {
409
+ "const": "Price moves percentage of active range away"
410
+ }
411
+ }
412
+ },
413
+ "then": {
414
+ "properties": {
415
+ "percentageOfPositionRangeToTrigger": {
416
+ "type": "number",
417
+ "title": "Percentage of Range",
418
+ "description": "The percentage of the range away to trigger new positions, 100% or 1 would be at the bounds of the range",
419
+ "detailedDescription": "If you have a simple position ranging from ticks 0 - 100, and you set this value to 1, the trigger range will be the outer bounds. Using 0.5 would make the trigger range 25-75, 2 would make the range -50 - 150."
420
+ },
421
+ "expectedDataTypes": {
422
+ "const": ${getExpectedDataTypes(strategyDataConnectors, "Price moves percentage of active range away")},
423
+ "default": ${getExpectedDataTypes(strategyDataConnectors, "Price moves percentage of active range away")},
424
+ "hidden": true,
425
+ "type": "string"
426
+ },
427
+ "elapsedTendTime": {
428
+ "type": "number",
429
+ "title": "Max time between tends",
430
+ "description": "If trigger conditions have not been met for this period of time, the strategy will execute regardless of trigger logic to update vault accounting.",
431
+ "default": 1209600
432
+ }
433
+ },
434
+ "required": ["percentageOfPositionRangeToTrigger", "expectedDataTypes", "elapsedTendTime"]
435
+ }
436
+ },
437
+ {
438
+ "if": {
439
+ "properties": {
440
+ "triggerStyle": {
441
+ "const": "Price moves one way past positions"
442
+ }
443
+ }
444
+ },
445
+ "then": {
446
+ "properties": {
447
+ "triggerWhenOver": {
448
+ "type": "boolean",
449
+ "title": "Price Moves Higher",
450
+ "description": "True for if the strategy should set new positions when the price (tick) is higher than the current positions, false for lower",
451
+ "detailedDescription": "If our current position ranges from ticks 0 - 100, true will make our bundle execute only when the current tick is higher. Any other case (current tick less than 100) will result in a continue recommendation."
452
+ },
453
+ "expectedDataTypes": {
454
+ "const": ${getExpectedDataTypes(strategyDataConnectors, "Price moves one way past positions")},
455
+ "default": ${getExpectedDataTypes(strategyDataConnectors, "Price moves one way past positions")},
456
+ "hidden": true,
457
+ "type": "string"
458
+ },
459
+ "elapsedTendTime": {
460
+ "type": "number",
461
+ "title": "Max time between tends",
462
+ "description": "If trigger conditions have not been met for this period of time, the strategy will execute regardless of trigger logic to update vault accounting.",
463
+ "default": 1209600
464
+ }
465
+ },
466
+ "required": ["triggerWhenOver", "expectedDataTypes", "elapsedTendTime"]
467
+ }
468
+ }`;
469
+ }
package/index.js CHANGED
@@ -1,14 +1,14 @@
1
- const fs = require("fs");
2
- import * as AsBind from "as-bind/dist/as-bind.cjs.js";
1
+ // const fs = require("fs");
2
+ // import * as AsBind from "as-bind/dist/as-bind.cjs.js";
3
3
 
4
- const imports = {
5
- console: { // File which you are injecting
6
- log(strPtr) {
7
- console.log(strPtr)
8
- }
9
- }
10
- };
4
+ // const imports = {
5
+ // console: { // File which you are injecting
6
+ // log(strPtr) {
7
+ // console.log(strPtr)
8
+ // }
9
+ // }
10
+ // };
11
11
 
12
- const asBindInstance = AsBind.instantiateSync(fs.readFileSync(__dirname + "/build/untouched.wasm"), imports)
12
+ // const asBindInstance = AsBind.instantiateSync(fs.readFileSync(__dirname + "/build/untouched.wasm"), imports)
13
13
 
14
- module.exports = asBindInstance.exports;
14
+ // module.exports = asBindInstance.exports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerprotocol/strategy-utils",
3
- "version": "2.2.1",
3
+ "version": "3.0.0",
4
4
  "description": "Strategy utilities library for Steer Protocol strategies",
5
5
  "main": "assembly/index.ts",
6
6
  "types": "assembly/index.ts",
@@ -13,7 +13,8 @@
13
13
  "docs": "typedoc --tsconfig ./tsconfig.json",
14
14
  "strategy": "yarn asbuild && yarn test",
15
15
  "semantic-release": "semantic-release",
16
- "commit": "cz"
16
+ "commit": "cz",
17
+ "postinstall": "patch-package"
17
18
  },
18
19
  "author": "Derek Barrera <derekbarrera@gmail.com>",
19
20
  "license": "ISC",
@@ -21,11 +22,13 @@
21
22
  "@babel/preset-typescript": "^7.14.5",
22
23
  "@semantic-release/changelog": "^5.0.1",
23
24
  "@semantic-release/git": "^9.0.0",
25
+ "@steerprotocol/app-loader": "^0.2.2",
24
26
  "@steerprotocol/base-strategy": "0.1.0",
25
27
  "as-bignum": "^0.2.23",
26
28
  "commitizen": "^4.2.4",
27
29
  "conventional-changelog-conventionalcommits": "^4.6.0",
28
- "json-as": "^0.5.20",
30
+ "json-as": "^0.5.43",
31
+ "patch-package": "^8.0.0",
29
32
  "typescript": "^4.3.5",
30
33
  "visitor-as": "^0.11.4"
31
34
  },