Skip to content

Commit ce3f7a3

Browse files
authored
Merge pull request #820 from c9s/feature/risk-cal-funcs
feature: add risk functions
2 parents affe466 + 5bbccac commit ce3f7a3

File tree

2 files changed

+196
-0
lines changed

2 files changed

+196
-0
lines changed

pkg/risk/leverage.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package risk
2+
3+
import (
4+
"github.com/c9s/bbgo/pkg/fixedpoint"
5+
"github.com/c9s/bbgo/pkg/types"
6+
)
7+
8+
// How to Calculate Cost Required to Open a Position in Perpetual Futures Contracts
9+
//
10+
// See <https://www.binance.com/en/support/faq/87fa7ee33b574f7084d42bd2ce2e463b>
11+
//
12+
// For Long Position:
13+
// = Number of Contract * Absolute Value {min[0, direction of order x (mark price - order price)]}
14+
//
15+
// For short position:
16+
// = Number of Contract * Absolute Value {min[0, direction of order x (mark price - order price)]}
17+
func CalculateOpenLoss(numContract, markPrice, orderPrice fixedpoint.Value, side types.SideType) fixedpoint.Value {
18+
var d = fixedpoint.One
19+
if side == types.SideTypeSell {
20+
d = fixedpoint.NegOne
21+
}
22+
23+
var openLoss = numContract.Mul(fixedpoint.Min(fixedpoint.Zero, d.Mul(markPrice.Sub(orderPrice))).Abs())
24+
return openLoss
25+
}
26+
27+
// CalculateMarginCost calculate the margin cost of the given notional position by price * quantity
28+
func CalculateMarginCost(price, quantity, leverage fixedpoint.Value) fixedpoint.Value {
29+
var notionalValue = price.Mul(quantity)
30+
var cost = notionalValue.Div(leverage)
31+
return cost
32+
}
33+
34+
func CalculatePositionCost(markPrice, orderPrice, quantity, leverage fixedpoint.Value, side types.SideType) fixedpoint.Value {
35+
var marginCost = CalculateMarginCost(orderPrice, quantity, leverage)
36+
var openLoss = CalculateOpenLoss(quantity, markPrice, orderPrice, side)
37+
return marginCost.Add(openLoss)
38+
}
39+
40+
// CalculateMaxPosition calculates the maximum notional value of the position and return the max quantity you can use.
41+
func CalculateMaxPosition(price, availableMargin, leverage fixedpoint.Value) fixedpoint.Value {
42+
var maxNotionalValue = availableMargin.Mul(leverage)
43+
var maxQuantity = maxNotionalValue.Div(price)
44+
return maxQuantity
45+
}
46+
47+
// CalculateMinRequiredLeverage calculates the leverage of the given position (price and quantity)
48+
func CalculateMinRequiredLeverage(price, quantity, availableMargin fixedpoint.Value) fixedpoint.Value {
49+
var notional = price.Mul(quantity)
50+
return notional.Div(availableMargin)
51+
}

pkg/risk/leverage_test.go

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package risk
2+
3+
import (
4+
"testing"
5+
6+
"github.com/c9s/bbgo/pkg/fixedpoint"
7+
"github.com/c9s/bbgo/pkg/types"
8+
)
9+
10+
func TestCalculateMarginCost(t *testing.T) {
11+
type args struct {
12+
price fixedpoint.Value
13+
quantity fixedpoint.Value
14+
leverage fixedpoint.Value
15+
}
16+
tests := []struct {
17+
name string
18+
args args
19+
want fixedpoint.Value
20+
}{
21+
{
22+
name: "simple",
23+
args: args{
24+
price: fixedpoint.NewFromFloat(9000.0),
25+
quantity: fixedpoint.NewFromFloat(2.0),
26+
leverage: fixedpoint.NewFromFloat(3.0),
27+
},
28+
want: fixedpoint.NewFromFloat(9000.0 * 2.0 / 3.0),
29+
},
30+
}
31+
for _, tt := range tests {
32+
t.Run(tt.name, func(t *testing.T) {
33+
if got := CalculateMarginCost(tt.args.price, tt.args.quantity, tt.args.leverage); got.String() != tt.want.String() {
34+
t.Errorf("CalculateMarginCost() = %v, want %v", got, tt.want)
35+
}
36+
})
37+
}
38+
}
39+
40+
func TestCalculatePositionCost(t *testing.T) {
41+
type args struct {
42+
markPrice fixedpoint.Value
43+
orderPrice fixedpoint.Value
44+
quantity fixedpoint.Value
45+
leverage fixedpoint.Value
46+
side types.SideType
47+
}
48+
tests := []struct {
49+
name string
50+
args args
51+
want fixedpoint.Value
52+
}{
53+
{
54+
// long position does not have openLoss
55+
name: "long",
56+
args: args{
57+
markPrice: fixedpoint.NewFromFloat(9050.0),
58+
orderPrice: fixedpoint.NewFromFloat(9000.0),
59+
quantity: fixedpoint.NewFromFloat(2.0),
60+
leverage: fixedpoint.NewFromFloat(3.0),
61+
side: types.SideTypeBuy,
62+
},
63+
want: fixedpoint.NewFromFloat(6000.0),
64+
},
65+
{
66+
// long position does not have openLoss
67+
name: "short",
68+
args: args{
69+
markPrice: fixedpoint.NewFromFloat(9050.0),
70+
orderPrice: fixedpoint.NewFromFloat(9000.0),
71+
quantity: fixedpoint.NewFromFloat(2.0),
72+
leverage: fixedpoint.NewFromFloat(3.0),
73+
side: types.SideTypeSell,
74+
},
75+
want: fixedpoint.NewFromFloat(6100.0),
76+
},
77+
}
78+
for _, tt := range tests {
79+
t.Run(tt.name, func(t *testing.T) {
80+
if got := CalculatePositionCost(tt.args.markPrice, tt.args.orderPrice, tt.args.quantity, tt.args.leverage, tt.args.side); got.String() != tt.want.String() {
81+
t.Errorf("CalculatePositionCost() = %v, want %v", got, tt.want)
82+
}
83+
})
84+
}
85+
}
86+
87+
func TestCalculateMaxPosition(t *testing.T) {
88+
type args struct {
89+
price fixedpoint.Value
90+
availableMargin fixedpoint.Value
91+
leverage fixedpoint.Value
92+
}
93+
tests := []struct {
94+
name string
95+
args args
96+
want fixedpoint.Value
97+
}{
98+
{
99+
name: "3x",
100+
args: args{
101+
price: fixedpoint.NewFromFloat(9000.0),
102+
availableMargin: fixedpoint.NewFromFloat(300.0),
103+
leverage: fixedpoint.NewFromFloat(3.0),
104+
},
105+
want: fixedpoint.NewFromFloat(0.1),
106+
},
107+
}
108+
for _, tt := range tests {
109+
t.Run(tt.name, func(t *testing.T) {
110+
if got := CalculateMaxPosition(tt.args.price, tt.args.availableMargin, tt.args.leverage); got.String() != tt.want.String() {
111+
t.Errorf("CalculateMaxPosition() = %v, want %v", got, tt.want)
112+
}
113+
})
114+
}
115+
}
116+
117+
func TestCalculateMinRequiredLeverage(t *testing.T) {
118+
type args struct {
119+
price fixedpoint.Value
120+
quantity fixedpoint.Value
121+
availableMargin fixedpoint.Value
122+
}
123+
tests := []struct {
124+
name string
125+
args args
126+
want fixedpoint.Value
127+
}{
128+
{
129+
name: "30x",
130+
args: args{
131+
price: fixedpoint.NewFromFloat(9000.0),
132+
quantity: fixedpoint.NewFromFloat(10.0),
133+
availableMargin: fixedpoint.NewFromFloat(3000.0),
134+
},
135+
want: fixedpoint.NewFromFloat(30.0),
136+
},
137+
}
138+
for _, tt := range tests {
139+
t.Run(tt.name, func(t *testing.T) {
140+
if got := CalculateMinRequiredLeverage(tt.args.price, tt.args.quantity, tt.args.availableMargin); got.String() != tt.want.String() {
141+
t.Errorf("CalculateMinRequiredLeverage() = %v, want %v", got, tt.want)
142+
}
143+
})
144+
}
145+
}

0 commit comments

Comments
 (0)