-
Notifications
You must be signed in to change notification settings - Fork 112
/
threshold_rule.go
184 lines (150 loc) · 5.66 KB
/
threshold_rule.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package liquidity
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/loop/swap"
)
var (
// errInvalidLiquidityThreshold is returned when a liquidity threshold
// has an invalid value.
errInvalidLiquidityThreshold = errors.New("liquidity threshold must " +
"be in [0:100)")
// errInvalidThresholdSum is returned when the sum of the percentages
// provided for a threshold rule is >= 100.
errInvalidThresholdSum = errors.New("sum of incoming and outgoing " +
"percentages must be < 100")
)
// SwapRule is a liquidity rule with a specific swap type.
type SwapRule struct {
*ThresholdRule
swap.Type
}
// ThresholdRule is a liquidity rule that implements minimum incoming and
// outgoing liquidity threshold.
type ThresholdRule struct {
// MinimumIncoming is the percentage of incoming liquidity that we do
// not want to drop below.
MinimumIncoming int
// MinimumOutgoing is the percentage of outgoing liquidity that we do
// not want to drop below.
MinimumOutgoing int
}
// NewThresholdRule returns a new threshold rule.
func NewThresholdRule(minIncoming, minOutgoing int) *ThresholdRule {
return &ThresholdRule{
MinimumIncoming: minIncoming,
MinimumOutgoing: minOutgoing,
}
}
// String returns a string representation of a rule.
func (r *ThresholdRule) String() string {
return fmt.Sprintf("threshold rule: minimum incoming: %v%%, minimum "+
"outgoing: %v%%", r.MinimumIncoming, r.MinimumOutgoing)
}
// validate validates the parameters that a rule was created with.
func (r *ThresholdRule) validate() error {
if r.MinimumIncoming < 0 || r.MinimumIncoming > 100 {
return errInvalidLiquidityThreshold
}
if r.MinimumOutgoing < 0 || r.MinimumOutgoing > 100 {
return errInvalidLiquidityThreshold
}
if r.MinimumIncoming+r.MinimumOutgoing >= 100 {
return errInvalidThresholdSum
}
return nil
}
// swapAmount suggests a swap based on the liquidity thresholds configured,
// returning zero if no swap is recommended.
func (r *ThresholdRule) swapAmount(channel *balances,
restrictions *Restrictions, swapType swap.Type) btcutil.Amount {
var (
// For loop out swaps, we want to adjust our incoming liquidity
// so the channel's incoming balance is our target.
targetBalance = channel.incoming
// For loop out swaps, we target a minimum amount of incoming
// liquidity, so the minimum incoming threshold is our target
// percentage.
targetPercentage = uint64(r.MinimumIncoming)
// For loop out swaps, we may want to preserve some of our
// outgoing balance, so the channel's outgoing balance is our
// reserve.
reserveBalance = channel.outgoing
// For loop out swaps, we may want to preserve some percentage
// of our outgoing balance, so the minimum outgoing threshold
// is our reserve percentage.
reservePercentage = uint64(r.MinimumOutgoing)
)
// For loop in swaps, we reverse our target and reserve values.
if swapType == swap.TypeIn {
targetBalance = channel.outgoing
targetPercentage = uint64(r.MinimumOutgoing)
reserveBalance = channel.incoming
reservePercentage = uint64(r.MinimumIncoming)
}
// Examine our total balance and required ratios to decide whether we
// need to swap.
amount := calculateSwapAmount(
targetBalance, reserveBalance, channel.capacity,
targetPercentage, reservePercentage,
)
// Limit our swap amount by the minimum/maximum thresholds set.
switch {
case amount < restrictions.Minimum:
return 0
case amount > restrictions.Maximum:
return restrictions.Maximum
default:
return amount
}
}
// calculateSwapAmount calculates amount for a swap based on thresholds.
// This function can be used for loop out or loop in, but the concept is the
// same - we want liquidity in one (target) direction, while preserving some
// minimum in the other (reserve) direction.
// - target: this is the side of the channel(s) where we want to acquire some
// liquidity. We aim for this liquidity to reach the threshold amount set.
// - reserve: this is the side of the channel(s) that we will move liquidity
// away from. This may not drop below a certain reserve threshold.
func calculateSwapAmount(targetAmount, reserveAmount,
capacity btcutil.Amount, targetThresholdPercentage,
reserveThresholdPercentage uint64) btcutil.Amount {
targetGoal := btcutil.Amount(
uint64(capacity) * targetThresholdPercentage / 100,
)
reserveMinimum := btcutil.Amount(
uint64(capacity) * reserveThresholdPercentage / 100,
)
switch {
// If we have sufficient target capacity, we do not need to swap.
case targetAmount >= targetGoal:
return 0
// If we are already below the threshold set for reserve capacity, we
// cannot take any further action.
case reserveAmount <= reserveMinimum:
return 0
}
// Express our minimum reserve amount as a maximum target amount.
// We will use this value to limit the amount that we swap, so that we
// do not dip below our reserve threshold.
maximumTarget := capacity - reserveMinimum
// Calculate the midpoint between our minimum and maximum target values.
// We will aim to swap this amount so that we do not tip our reserve
// balance beneath the desired level.
midpoint := (targetGoal + maximumTarget) / 2
// Calculate the amount of target balance we need to shift to reach
// this desired midpoint.
required := midpoint - targetAmount
// Since we can have pending htlcs on our channel, we check the amount
// of reserve capacity that we can shift before we fall below our
// threshold.
available := reserveAmount - reserveMinimum
// If we do not have enough balance available to reach our midpoint, we
// take no action. This is the case when we have a large portion of
// pending htlcs.
if available < required {
return 0
}
return required
}