shufflecom-calculations 6.12.1 → 6.14.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/lib/games/stonks/constants.d.ts +1 -1
- package/lib/games/stonks/constants.js +2 -2
- package/lib/games/stonks/stonks-rules.d.ts +6 -5
- package/lib/games/stonks/stonks-rules.js +19 -17
- package/lib/games/stonks/stonks-rules.js.map +1 -1
- package/lib/sports/sports-bet.types.d.ts +4 -1
- package/lib/sports/sports.types.d.ts +2 -1
- package/lib/sports/sports.types.js +1 -0
- package/lib/sports/sports.types.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/games/stonks/constants.ts +1 -1
- package/src/games/stonks/stonks-rules.spec.ts +56 -49
- package/src/games/stonks/stonks-rules.ts +27 -24
- package/src/sports/sports-bet.types.ts +5 -1
- package/src/sports/sports.types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shufflecom-calculations",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.14.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
},
|
|
20
20
|
"author": "",
|
|
21
21
|
"license": "ISC",
|
|
22
|
-
"gitHead": "
|
|
22
|
+
"gitHead": "bb40b80986c6a996592546bb531b86a6992fa184"
|
|
23
23
|
}
|
|
@@ -44,15 +44,22 @@ describe('StonksRules', () => {
|
|
|
44
44
|
it('inverts multiplierAtPrice without depending on amount', () => {
|
|
45
45
|
const bet = openBet({ amount: number(0), leverage: number(10) });
|
|
46
46
|
|
|
47
|
-
const impliedPrice = StonksRules.impliedPriceForMultiplier(bet, number(1.
|
|
47
|
+
const impliedPrice = StonksRules.impliedPriceForMultiplier(bet, number('1.47'));
|
|
48
48
|
|
|
49
|
-
expect(StonksRules.multiplierAtPrice(bet, impliedPrice)).toBigNumberEqual(1.
|
|
49
|
+
expect(StonksRules.multiplierAtPrice(bet, impliedPrice)).toBigNumberEqual('1.47');
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it('moves in the opposite price direction for DOWN bets', () => {
|
|
53
53
|
const bet = openBet({ direction: StonksDirection.DOWN, leverage: number(10) });
|
|
54
54
|
|
|
55
|
-
expect(StonksRules.impliedPriceForMultiplier(bet, number(1.
|
|
55
|
+
expect(StonksRules.impliedPriceForMultiplier(bet, number('1.47'))).toBigNumberEqual(95);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('treats the multiplier as net of the fee, so 1x leverage needs more than a 100% move to reach 2x', () => {
|
|
59
|
+
const bet = openBet({ leverage: number(1) });
|
|
60
|
+
|
|
61
|
+
expect(StonksRules.impliedPriceForMultiplier(bet, number('1.96'))).toBigNumberEqual(200);
|
|
62
|
+
expect(StonksRules.impliedPriceForMultiplier(bet, number(2))).toBigNumberGreaterThan(200);
|
|
56
63
|
});
|
|
57
64
|
});
|
|
58
65
|
|
|
@@ -122,12 +129,22 @@ describe('StonksRules', () => {
|
|
|
122
129
|
});
|
|
123
130
|
});
|
|
124
131
|
|
|
125
|
-
describe('
|
|
126
|
-
it('
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
expect(StonksRules.
|
|
132
|
+
describe('hasReachedTakeProfit', () => {
|
|
133
|
+
it('compares the net multiplier at the price against the take-profit, inclusive, in the direction of profit', () => {
|
|
134
|
+
const up = openBet({ direction: StonksDirection.UP, leverage: number(10) });
|
|
135
|
+
const down = openBet({ direction: StonksDirection.DOWN, leverage: number(10) });
|
|
136
|
+
|
|
137
|
+
expect(StonksRules.hasReachedTakeProfit(up, number(105), number('1.47'))).toBe(true);
|
|
138
|
+
expect(StonksRules.hasReachedTakeProfit(up, number('104.99'), number('1.47'))).toBe(false);
|
|
139
|
+
expect(StonksRules.hasReachedTakeProfit(down, number(95), number('1.47'))).toBe(true);
|
|
140
|
+
expect(StonksRules.hasReachedTakeProfit(down, number('95.01'), number('1.47'))).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('needs the price to clear the fee: a 10% move at 10x reaches 1.96x, not 2x', () => {
|
|
144
|
+
const bet = openBet({ leverage: number(10) });
|
|
145
|
+
|
|
146
|
+
expect(StonksRules.hasReachedTakeProfit(bet, number(110), number('1.96'))).toBe(true);
|
|
147
|
+
expect(StonksRules.hasReachedTakeProfit(bet, number(110), number(2))).toBe(false);
|
|
131
148
|
});
|
|
132
149
|
});
|
|
133
150
|
|
|
@@ -141,10 +158,11 @@ describe('StonksRules', () => {
|
|
|
141
158
|
});
|
|
142
159
|
|
|
143
160
|
describe('multiplierAtPrice', () => {
|
|
144
|
-
it('is
|
|
161
|
+
it('is net of the fee at entry and floors at zero once loss would exceed the wager', () => {
|
|
145
162
|
const bet = openBet({ entryPrice: number(100), leverage: number(10), direction: StonksDirection.UP });
|
|
146
163
|
|
|
147
|
-
expect(StonksRules.multiplierAtPrice(bet, number(100))).toBigNumberEqual(
|
|
164
|
+
expect(StonksRules.multiplierAtPrice(bet, number(100))).toBigNumberEqual('0.98');
|
|
165
|
+
expect(StonksRules.multiplierAtPrice(bet, number(110))).toBigNumberEqual('1.96');
|
|
148
166
|
expect(StonksRules.multiplierAtPrice(bet, number(80))).toBigNumberEqual(0);
|
|
149
167
|
});
|
|
150
168
|
});
|
|
@@ -185,11 +203,11 @@ describe('StonksRules', () => {
|
|
|
185
203
|
});
|
|
186
204
|
});
|
|
187
205
|
|
|
188
|
-
it('fires MAX_PAYOUT once the
|
|
206
|
+
it('fires MAX_PAYOUT once the net payout reaches the live cap, even overshooting it', () => {
|
|
189
207
|
const bet = openBet({ leverage: number(100) });
|
|
190
208
|
const cap = number(150);
|
|
191
209
|
|
|
192
|
-
// At price 101 the
|
|
210
|
+
// At price 101 the net payout (196) overshoots the cap (150) — MAX_PAYOUT still fires,
|
|
193
211
|
// and the caller settles at this real tick price regardless, not a price back-solved from the cap.
|
|
194
212
|
expect(StonksRules.evaluateTriggers(bet, number(101), cap)).toEqual({
|
|
195
213
|
fired: true,
|
|
@@ -212,7 +230,7 @@ describe('StonksRules', () => {
|
|
|
212
230
|
const reason = { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap: cap } as const;
|
|
213
231
|
|
|
214
232
|
expect(StonksRules.evaluateTriggers(bet, impliedPrice, cap)).toEqual({ fired: true, reason });
|
|
215
|
-
expect(StonksRules.
|
|
233
|
+
expect(StonksRules.netPayoutForReason(bet, reason, impliedPrice)).toBigNumberEqual(250);
|
|
216
234
|
});
|
|
217
235
|
|
|
218
236
|
// The mirror of the case above, and the reason the cap cannot simply be checked before the
|
|
@@ -222,12 +240,12 @@ describe('StonksRules', () => {
|
|
|
222
240
|
const takeProfitMultiplier = number(2);
|
|
223
241
|
const bet = openBet({ takeProfitMultiplier });
|
|
224
242
|
const cap = number(250);
|
|
225
|
-
// One tick jumps to 130, well past the TP-implied 110: the price-derived payout there
|
|
226
|
-
// and clears the cap, but the agreed take-profit payout is only 100 × 2 = 200, under it.
|
|
243
|
+
// One tick jumps to 130, well past the TP-implied ~110.4: the price-derived net payout there
|
|
244
|
+
// is 392 and clears the cap, but the agreed take-profit payout is only 100 × 2 = 200, under it.
|
|
227
245
|
const reason = { kind: StonksBetTerminalReason.TAKE_PROFIT, takeProfitMultiplier } as const;
|
|
228
246
|
|
|
229
247
|
expect(StonksRules.evaluateTriggers(bet, number(130), cap)).toEqual({ fired: true, reason });
|
|
230
|
-
expect(StonksRules.
|
|
248
|
+
expect(StonksRules.netPayoutForReason(bet, reason, number(130))).toBigNumberEqual(200);
|
|
231
249
|
});
|
|
232
250
|
|
|
233
251
|
it('fires TAKE_PROFIT, not MAX_PAYOUT, when the agreed take-profit payout exactly equals the live cap', () => {
|
|
@@ -254,38 +272,32 @@ describe('StonksRules', () => {
|
|
|
254
272
|
});
|
|
255
273
|
});
|
|
256
274
|
|
|
257
|
-
describe('
|
|
275
|
+
describe('netPayoutForReason', () => {
|
|
258
276
|
const bet = openBet({ leverage: number(10) });
|
|
259
277
|
|
|
260
278
|
it('TAKE_PROFIT credits wager × multiplier exactly, ignoring price', () => {
|
|
261
279
|
const takeProfitMultiplier = number('1.5');
|
|
262
280
|
|
|
263
281
|
expect(
|
|
264
|
-
StonksRules.
|
|
282
|
+
StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.TAKE_PROFIT, takeProfitMultiplier }, number(999)),
|
|
265
283
|
).toBigNumberEqual(150);
|
|
266
284
|
});
|
|
267
285
|
|
|
268
286
|
it('LIQUIDATION is exact zero regardless of price', () => {
|
|
269
|
-
expect(StonksRules.
|
|
287
|
+
expect(StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.LIQUIDATION }, number(999))).toBigNumberEqual(0);
|
|
270
288
|
});
|
|
271
289
|
|
|
272
290
|
it('MAX_PAYOUT is exactly the applied cap, ignoring price', () => {
|
|
273
291
|
const appliedCap = number(150);
|
|
274
292
|
|
|
275
293
|
expect(
|
|
276
|
-
StonksRules.
|
|
294
|
+
StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap }, number(999)),
|
|
277
295
|
).toBigNumberEqual(150);
|
|
278
296
|
});
|
|
279
297
|
|
|
280
|
-
it('CASHOUT and ROUND_END fall back to the price-derived
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
expect(StonksRules.grossPayoutForReason(bet, { kind: StonksBetTerminalReason.CASHOUT }, number(110))).toBigNumberEqual(
|
|
284
|
-
priceDerivedPayout,
|
|
285
|
-
);
|
|
286
|
-
expect(StonksRules.grossPayoutForReason(bet, { kind: StonksBetTerminalReason.ROUND_END }, number(110))).toBigNumberEqual(
|
|
287
|
-
priceDerivedPayout,
|
|
288
|
-
);
|
|
298
|
+
it('CASHOUT and ROUND_END fall back to the price-derived payout, net of the fee', () => {
|
|
299
|
+
expect(StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.CASHOUT }, number(110))).toBigNumberEqual(196);
|
|
300
|
+
expect(StonksRules.netPayoutForReason(bet, { kind: StonksBetTerminalReason.ROUND_END }, number(110))).toBigNumberEqual(196);
|
|
289
301
|
});
|
|
290
302
|
});
|
|
291
303
|
|
|
@@ -302,34 +314,29 @@ describe('StonksRules', () => {
|
|
|
302
314
|
it.each([
|
|
303
315
|
[StonksDirection.UP, number(101)],
|
|
304
316
|
[StonksDirection.DOWN, number(99)],
|
|
305
|
-
])('every other reason falls back to the price-derived multiplier, independently of wager (%s)', (direction, price) => {
|
|
317
|
+
])('every other reason falls back to the price-derived net multiplier, independently of wager (%s)', (direction, price) => {
|
|
306
318
|
const bet = openBet({ direction, amount: number(0) });
|
|
307
319
|
|
|
308
|
-
expect(StonksRules.multiplierForReason(bet, { kind: StonksBetTerminalReason.CASHOUT }, price)).toBigNumberEqual(1.
|
|
309
|
-
expect(StonksRules.multiplierForReason(bet, { kind: StonksBetTerminalReason.LIQUIDATION }, price)).toBigNumberEqual(1.
|
|
320
|
+
expect(StonksRules.multiplierForReason(bet, { kind: StonksBetTerminalReason.CASHOUT }, price)).toBigNumberEqual('1.078');
|
|
321
|
+
expect(StonksRules.multiplierForReason(bet, { kind: StonksBetTerminalReason.LIQUIDATION }, price)).toBigNumberEqual('1.078');
|
|
310
322
|
});
|
|
311
323
|
});
|
|
312
324
|
|
|
313
|
-
describe('
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
expect(StonksRules.
|
|
318
|
-
expect(StonksRules.
|
|
319
|
-
expect(StonksRules.netPayout(wager, wager)).toBigNumberEqual(wager);
|
|
325
|
+
describe('netOfFee / grossOfFee', () => {
|
|
326
|
+
it('takes a flat 2% fee on every payout, including losses and breakeven', () => {
|
|
327
|
+
expect(StonksRules.netOfFee(number(90))).toBigNumberEqual('88.2');
|
|
328
|
+
expect(StonksRules.netOfFee(number(100))).toBigNumberEqual(98);
|
|
329
|
+
expect(StonksRules.netOfFee(number(101))).toBigNumberEqual('98.98');
|
|
330
|
+
expect(StonksRules.netOfFee(number(200))).toBigNumberEqual(196);
|
|
320
331
|
});
|
|
321
332
|
|
|
322
|
-
it('
|
|
323
|
-
|
|
324
|
-
expect(StonksRules.netPayout(number(101), wager)).toBigNumberEqual('100.95');
|
|
325
|
-
// gross 200 = wager + 100 profit; net = 100 + 95 = 195
|
|
326
|
-
expect(StonksRules.netPayout(number(200), wager)).toBigNumberEqual(195);
|
|
333
|
+
it('leaves a liquidated payout at zero', () => {
|
|
334
|
+
expect(StonksRules.netOfFee(number(0))).toBigNumberEqual(0);
|
|
327
335
|
});
|
|
328
336
|
|
|
329
|
-
it('
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
expect(net).toBigNumberGreaterThan(wager);
|
|
337
|
+
it('grossOfFee inverts netOfFee', () => {
|
|
338
|
+
expect(StonksRules.grossOfFee(number(98))).toBigNumberEqual(100);
|
|
339
|
+
expect(StonksRules.grossOfFee(StonksRules.netOfFee(number('123.456')))).toBigNumberEqual('123.456');
|
|
333
340
|
});
|
|
334
341
|
});
|
|
335
342
|
});
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
STONKS_INTERNAL_PRICE_DECIMAL_PLACES,
|
|
5
5
|
STONKS_MAX_STALE_CLIENT_TICKS,
|
|
6
6
|
STONKS_MIN_TAKE_PROFIT_MULTIPLIER,
|
|
7
|
-
|
|
7
|
+
STONKS_PAYOUT_FEE_RATE,
|
|
8
8
|
STONKS_ROUND_DURATION_MS,
|
|
9
9
|
STONKS_ROUND_TICK_COUNT,
|
|
10
10
|
STONKS_TICK_DURATION_MS,
|
|
@@ -38,7 +38,6 @@ export interface StonksTerminalBetState extends StonksOpenBetState {
|
|
|
38
38
|
readonly terminalTick: number;
|
|
39
39
|
readonly terminalPrice: BigNumber;
|
|
40
40
|
readonly multiplier: BigNumber;
|
|
41
|
-
readonly grossPayout: BigNumber;
|
|
42
41
|
readonly netPayout: BigNumber;
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -67,7 +66,7 @@ export class StonksRules {
|
|
|
67
66
|
targetMultiplier: BigNumber,
|
|
68
67
|
): BigNumber {
|
|
69
68
|
const directionSign = bet.direction === StonksDirection.UP ? 1 : -1;
|
|
70
|
-
const multiplierDelta = targetMultiplier.minus(1);
|
|
69
|
+
const multiplierDelta = StonksRules.grossOfFee(targetMultiplier).minus(1);
|
|
71
70
|
const priceChange = BigNumber(directionSign).multipliedBy(multiplierDelta).dividedBy(bet.leverage);
|
|
72
71
|
return bet.entryPrice.multipliedBy(BigNumber(1).plus(priceChange));
|
|
73
72
|
}
|
|
@@ -114,8 +113,12 @@ export class StonksRules {
|
|
|
114
113
|
return new Date(startedAtMs + tick * STONKS_TICK_DURATION_MS);
|
|
115
114
|
}
|
|
116
115
|
|
|
117
|
-
static
|
|
118
|
-
|
|
116
|
+
static hasReachedTakeProfit(
|
|
117
|
+
bet: Pick<StonksOpenBetState, 'leverage' | 'entryPrice' | 'direction'>,
|
|
118
|
+
price: BigNumber,
|
|
119
|
+
takeProfitMultiplier: BigNumber,
|
|
120
|
+
): boolean {
|
|
121
|
+
return StonksRules.multiplierAtPrice(bet, price).isGreaterThanOrEqualTo(takeProfitMultiplier);
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
static hasReachedBust(direction: StonksDirection, price: BigNumber, bustPrice: BigNumber): boolean {
|
|
@@ -127,11 +130,7 @@ export class StonksRules {
|
|
|
127
130
|
|
|
128
131
|
if (bet.takeProfitMultiplier != null) {
|
|
129
132
|
const takeProfitMultiplier = bet.takeProfitMultiplier;
|
|
130
|
-
const hitTakeProfit = StonksRules.
|
|
131
|
-
bet.direction,
|
|
132
|
-
tickPrice,
|
|
133
|
-
StonksRules.impliedPriceForMultiplier(bet, takeProfitMultiplier),
|
|
134
|
-
);
|
|
133
|
+
const hitTakeProfit = StonksRules.hasReachedTakeProfit(bet, tickPrice, takeProfitMultiplier);
|
|
135
134
|
if (hitTakeProfit && hitBust) {
|
|
136
135
|
throw new Error('StonksRules.evaluateTriggers: take profit and liquidation both fired');
|
|
137
136
|
}
|
|
@@ -141,7 +140,7 @@ export class StonksRules {
|
|
|
141
140
|
// A take-profit is the one reason whose payout is a fixed wager × multiplier rather than
|
|
142
141
|
// something the tick price bounds, and the cap is read live every batch — so it can be
|
|
143
142
|
// lowered under an already-agreed multiplier. Nothing downstream re-checks it.
|
|
144
|
-
if (StonksRules.
|
|
143
|
+
if (StonksRules.netPayoutForReason(bet, reason, tickPrice).isGreaterThan(liveMaxPayoutCap)) {
|
|
145
144
|
return { fired: true, reason: { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap: liveMaxPayoutCap } };
|
|
146
145
|
}
|
|
147
146
|
return { fired: true, reason };
|
|
@@ -152,19 +151,19 @@ export class StonksRules {
|
|
|
152
151
|
return { fired: true, reason: { kind: StonksBetTerminalReason.LIQUIDATION } };
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
if (StonksRules.
|
|
154
|
+
if (StonksRules.netPayoutAtPrice(bet, tickPrice).isGreaterThanOrEqualTo(liveMaxPayoutCap)) {
|
|
156
155
|
return { fired: true, reason: { kind: StonksBetTerminalReason.MAX_PAYOUT, appliedCap: liveMaxPayoutCap } };
|
|
157
156
|
}
|
|
158
157
|
return { fired: false };
|
|
159
158
|
}
|
|
160
159
|
|
|
161
|
-
static multiplierAtPrice(bet: StonksOpenBetState, price: BigNumber): BigNumber {
|
|
160
|
+
static multiplierAtPrice(bet: Pick<StonksOpenBetState, 'leverage' | 'entryPrice' | 'direction'>, price: BigNumber): BigNumber {
|
|
162
161
|
const directionSign = bet.direction === StonksDirection.UP ? 1 : -1;
|
|
163
162
|
const percentagePriceChange = price.minus(bet.entryPrice).dividedBy(bet.entryPrice);
|
|
164
163
|
const leveragedPriceChange = BigNumber(directionSign).multipliedBy(bet.leverage).multipliedBy(percentagePriceChange);
|
|
165
164
|
|
|
166
|
-
const
|
|
167
|
-
return BigNumber.maximum(0,
|
|
165
|
+
const grossMultiplier = BigNumber(1).plus(leveragedPriceChange);
|
|
166
|
+
return StonksRules.netOfFee(BigNumber.maximum(0, grossMultiplier));
|
|
168
167
|
}
|
|
169
168
|
|
|
170
169
|
static grossPayoutAtPrice(bet: StonksOpenBetState, price: BigNumber): BigNumber {
|
|
@@ -173,7 +172,11 @@ export class StonksRules {
|
|
|
173
172
|
return BigNumber.maximum(0, bet.amount.plus(pnl));
|
|
174
173
|
}
|
|
175
174
|
|
|
176
|
-
static
|
|
175
|
+
static netPayoutAtPrice(bet: StonksOpenBetState, price: BigNumber): BigNumber {
|
|
176
|
+
return StonksRules.netOfFee(StonksRules.grossPayoutAtPrice(bet, price));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static netPayoutForReason(bet: StonksOpenBetState, reason: StonksSettlementReason, price: BigNumber): BigNumber {
|
|
177
180
|
switch (reason.kind) {
|
|
178
181
|
case StonksBetTerminalReason.TAKE_PROFIT:
|
|
179
182
|
return calculatePayoutBN(bet.amount, reason.takeProfitMultiplier);
|
|
@@ -183,10 +186,10 @@ export class StonksRules {
|
|
|
183
186
|
return reason.appliedCap;
|
|
184
187
|
case StonksBetTerminalReason.CASHOUT:
|
|
185
188
|
case StonksBetTerminalReason.ROUND_END:
|
|
186
|
-
return StonksRules.
|
|
189
|
+
return StonksRules.netPayoutAtPrice(bet, price);
|
|
187
190
|
default:
|
|
188
191
|
reason satisfies never;
|
|
189
|
-
throw new Error(`StonksRules.
|
|
192
|
+
throw new Error(`StonksRules.netPayoutForReason: unhandled reason`);
|
|
190
193
|
}
|
|
191
194
|
}
|
|
192
195
|
|
|
@@ -197,11 +200,11 @@ export class StonksRules {
|
|
|
197
200
|
return StonksRules.multiplierAtPrice(bet, price);
|
|
198
201
|
}
|
|
199
202
|
|
|
200
|
-
static
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return
|
|
203
|
+
static netOfFee(gross: BigNumber): BigNumber {
|
|
204
|
+
return gross.multipliedBy(BigNumber(1).minus(STONKS_PAYOUT_FEE_RATE));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static grossOfFee(net: BigNumber): BigNumber {
|
|
208
|
+
return net.dividedBy(BigNumber(1).minus(STONKS_PAYOUT_FEE_RATE));
|
|
206
209
|
}
|
|
207
210
|
}
|
|
@@ -147,6 +147,7 @@ export interface SportsBetCashoutInfoV3 {
|
|
|
147
147
|
/** Non-null exactly when playerCashoutEnabled is ENABLED. */
|
|
148
148
|
cashoutOddsDecimal: string | null;
|
|
149
149
|
playerCashoutEnabled: SportsBetCashoutButtonState;
|
|
150
|
+
partialCashoutEnabled: boolean;
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
export interface CashoutSportsBetPayloadV2 {
|
|
@@ -154,7 +155,10 @@ export interface CashoutSportsBetPayloadV2 {
|
|
|
154
155
|
currency: Currency;
|
|
155
156
|
amount: string;
|
|
156
157
|
status: SportsBetStatus;
|
|
157
|
-
|
|
158
|
+
// null after a partial cashout, the bet stays pending until the remaining stake is resulted
|
|
159
|
+
settlement: { payoutOddsDecimal: string; payout: string } | null;
|
|
160
|
+
partialCashoutBps: number | null;
|
|
161
|
+
partialCashoutPayout: string | null;
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
export interface SportsBetUpdatedPayloadV2 {
|
|
@@ -103,6 +103,7 @@ export enum SportsCashoutFailedReason {
|
|
|
103
103
|
SPORTS_CASHOUT_DISABLED = 'SPORTS_CASHOUT_DISABLED',
|
|
104
104
|
SPORTS_MAINTENANCE = 'SPORTS_MAINTENANCE',
|
|
105
105
|
CASHOUT_MARGIN_CAP_HIT = 'CASHOUT_MARGIN_CAP_HIT',
|
|
106
|
+
ALREADY_PARTIALLY_CASHED_OUT = 'ALREADY_PARTIALLY_CASHED_OUT',
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
export enum SportsBetCashoutButtonState {
|