-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request c9s#820 from c9s/feature/risk-cal-funcs
feature: add risk functions
- Loading branch information
Showing
2 changed files
with
196 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package risk | ||
|
||
import ( | ||
"github.com/c9s/bbgo/pkg/fixedpoint" | ||
"github.com/c9s/bbgo/pkg/types" | ||
) | ||
|
||
// How to Calculate Cost Required to Open a Position in Perpetual Futures Contracts | ||
// | ||
// See <https://www.binance.com/en/support/faq/87fa7ee33b574f7084d42bd2ce2e463b> | ||
// | ||
// For Long Position: | ||
// = Number of Contract * Absolute Value {min[0, direction of order x (mark price - order price)]} | ||
// | ||
// For short position: | ||
// = Number of Contract * Absolute Value {min[0, direction of order x (mark price - order price)]} | ||
func CalculateOpenLoss(numContract, markPrice, orderPrice fixedpoint.Value, side types.SideType) fixedpoint.Value { | ||
var d = fixedpoint.One | ||
if side == types.SideTypeSell { | ||
d = fixedpoint.NegOne | ||
} | ||
|
||
var openLoss = numContract.Mul(fixedpoint.Min(fixedpoint.Zero, d.Mul(markPrice.Sub(orderPrice))).Abs()) | ||
return openLoss | ||
} | ||
|
||
// CalculateMarginCost calculate the margin cost of the given notional position by price * quantity | ||
func CalculateMarginCost(price, quantity, leverage fixedpoint.Value) fixedpoint.Value { | ||
var notionalValue = price.Mul(quantity) | ||
var cost = notionalValue.Div(leverage) | ||
return cost | ||
} | ||
|
||
func CalculatePositionCost(markPrice, orderPrice, quantity, leverage fixedpoint.Value, side types.SideType) fixedpoint.Value { | ||
var marginCost = CalculateMarginCost(orderPrice, quantity, leverage) | ||
var openLoss = CalculateOpenLoss(quantity, markPrice, orderPrice, side) | ||
return marginCost.Add(openLoss) | ||
} | ||
|
||
// CalculateMaxPosition calculates the maximum notional value of the position and return the max quantity you can use. | ||
func CalculateMaxPosition(price, availableMargin, leverage fixedpoint.Value) fixedpoint.Value { | ||
var maxNotionalValue = availableMargin.Mul(leverage) | ||
var maxQuantity = maxNotionalValue.Div(price) | ||
return maxQuantity | ||
} | ||
|
||
// CalculateMinRequiredLeverage calculates the leverage of the given position (price and quantity) | ||
func CalculateMinRequiredLeverage(price, quantity, availableMargin fixedpoint.Value) fixedpoint.Value { | ||
var notional = price.Mul(quantity) | ||
return notional.Div(availableMargin) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package risk | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/c9s/bbgo/pkg/fixedpoint" | ||
"github.com/c9s/bbgo/pkg/types" | ||
) | ||
|
||
func TestCalculateMarginCost(t *testing.T) { | ||
type args struct { | ||
price fixedpoint.Value | ||
quantity fixedpoint.Value | ||
leverage fixedpoint.Value | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want fixedpoint.Value | ||
}{ | ||
{ | ||
name: "simple", | ||
args: args{ | ||
price: fixedpoint.NewFromFloat(9000.0), | ||
quantity: fixedpoint.NewFromFloat(2.0), | ||
leverage: fixedpoint.NewFromFloat(3.0), | ||
}, | ||
want: fixedpoint.NewFromFloat(9000.0 * 2.0 / 3.0), | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := CalculateMarginCost(tt.args.price, tt.args.quantity, tt.args.leverage); got.String() != tt.want.String() { | ||
t.Errorf("CalculateMarginCost() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestCalculatePositionCost(t *testing.T) { | ||
type args struct { | ||
markPrice fixedpoint.Value | ||
orderPrice fixedpoint.Value | ||
quantity fixedpoint.Value | ||
leverage fixedpoint.Value | ||
side types.SideType | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want fixedpoint.Value | ||
}{ | ||
{ | ||
// long position does not have openLoss | ||
name: "long", | ||
args: args{ | ||
markPrice: fixedpoint.NewFromFloat(9050.0), | ||
orderPrice: fixedpoint.NewFromFloat(9000.0), | ||
quantity: fixedpoint.NewFromFloat(2.0), | ||
leverage: fixedpoint.NewFromFloat(3.0), | ||
side: types.SideTypeBuy, | ||
}, | ||
want: fixedpoint.NewFromFloat(6000.0), | ||
}, | ||
{ | ||
// long position does not have openLoss | ||
name: "short", | ||
args: args{ | ||
markPrice: fixedpoint.NewFromFloat(9050.0), | ||
orderPrice: fixedpoint.NewFromFloat(9000.0), | ||
quantity: fixedpoint.NewFromFloat(2.0), | ||
leverage: fixedpoint.NewFromFloat(3.0), | ||
side: types.SideTypeSell, | ||
}, | ||
want: fixedpoint.NewFromFloat(6100.0), | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := CalculatePositionCost(tt.args.markPrice, tt.args.orderPrice, tt.args.quantity, tt.args.leverage, tt.args.side); got.String() != tt.want.String() { | ||
t.Errorf("CalculatePositionCost() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestCalculateMaxPosition(t *testing.T) { | ||
type args struct { | ||
price fixedpoint.Value | ||
availableMargin fixedpoint.Value | ||
leverage fixedpoint.Value | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want fixedpoint.Value | ||
}{ | ||
{ | ||
name: "3x", | ||
args: args{ | ||
price: fixedpoint.NewFromFloat(9000.0), | ||
availableMargin: fixedpoint.NewFromFloat(300.0), | ||
leverage: fixedpoint.NewFromFloat(3.0), | ||
}, | ||
want: fixedpoint.NewFromFloat(0.1), | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := CalculateMaxPosition(tt.args.price, tt.args.availableMargin, tt.args.leverage); got.String() != tt.want.String() { | ||
t.Errorf("CalculateMaxPosition() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestCalculateMinRequiredLeverage(t *testing.T) { | ||
type args struct { | ||
price fixedpoint.Value | ||
quantity fixedpoint.Value | ||
availableMargin fixedpoint.Value | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want fixedpoint.Value | ||
}{ | ||
{ | ||
name: "30x", | ||
args: args{ | ||
price: fixedpoint.NewFromFloat(9000.0), | ||
quantity: fixedpoint.NewFromFloat(10.0), | ||
availableMargin: fixedpoint.NewFromFloat(3000.0), | ||
}, | ||
want: fixedpoint.NewFromFloat(30.0), | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := CalculateMinRequiredLeverage(tt.args.price, tt.args.quantity, tt.args.availableMargin); got.String() != tt.want.String() { | ||
t.Errorf("CalculateMinRequiredLeverage() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |