From a49a4df70fbeaaacb8ee11f47370b5d841e47cc7 Mon Sep 17 00:00:00 2001
From: frankrap <2201441955@qq.com>
Date: Fri, 15 May 2020 18:01:27 +0800
Subject: [PATCH] Update
---
backtest/ReportHistoryTemplate.html | 35 +++---
backtest/backtest.go | 83 +++++++++----
backtest/report.go | 10 +-
backtest/statik/statik.go | 2 +-
exchanges/deribitsim/deribitsim.go | 186 +++++++++++++++++++++-------
models.go | 2 +
6 files changed, 232 insertions(+), 86 deletions(-)
diff --git a/backtest/ReportHistoryTemplate.html b/backtest/ReportHistoryTemplate.html
index 20d07d3..e833165 100644
--- a/backtest/ReportHistoryTemplate.html
+++ b/backtest/ReportHistoryTemplate.html
@@ -81,7 +81,7 @@
|
- |
+ |
|
|
|
@@ -91,8 +91,8 @@
|
|
|
- |
- |
+ |
+ |
|
@@ -103,15 +103,17 @@
Order |
Symbol |
Type |
- Volume |
+ Amount |
Price |
Avg Price |
- SL/TP |
- Time |
+ Profit |
+ Commission |
+ Balance |
+ Time |
State |
- Comment |
+ Position |
-
+
|
@@ -119,20 +121,21 @@
Deals |
- Time |
- Deal |
+ Open Time |
+ Order |
Symbol |
Type |
- Direction |
- Volume |
+ Amount |
Price |
- Order |
- Commission |
- Swap |
+ Avg Price |
Profit |
+ Commission |
Balance |
- Comment |
+ Time |
+ State |
+ Position |
+
", stats.RunDuration.String())
html = strings.ReplaceAll(html, "", fmt.Sprintf("%.8f", stats.BaHReturn))
html = strings.ReplaceAll(html, "", fmt.Sprintf("%.4f", stats.BaHReturnPnt*100))
+ s := b.buildSOrders(sOrders)
+ html = strings.Replace(html, ``, s, -1)
+ s = b.buildSOrders(dealOrders)
+ html = strings.Replace(html, ``, s, -1)
+ return
+}
+
+func (b *Backtest) buildSOrders(sOrders []*SOrder) string {
s := bytes.Buffer{}
- for i := 0; i < len(orders); i++ {
- order := orders[i].Order
+ for i := 0; i < len(sOrders); i++ {
+ sOrder := sOrders[i]
+ order := sOrders[i].Order
bgColor := "#FFFFFF"
if i%2 != 0 {
bgColor = "#F7F7F7"
}
- s.WriteString(fmt.Sprintf(``, bgColor)) // #FFFFFF
- s.WriteString(fmt.Sprintf(`%v | `, order.Time.Format("2006.01.02 15:04:05"))) // 2018.07.06 11:08:44
- s.WriteString(fmt.Sprintf(`%v | `, order.ID)) // 11573668
+ price := fmt.Sprintf("%v", order.Price)
+ orderType := strings.ToLower(order.Direction.String())
+ if order.Type == OrderTypeMarket {
+ price = "market"
+ } else {
+ orderType += " " + strings.ToLower(order.Type.String())
+ }
+ if order.PostOnly {
+ orderType += " postOnly"
+ }
+ if order.ReduceOnly {
+ orderType += " reduceOnly"
+ }
+ positions := ""
+ sort.Slice(sOrder.Positions, func(i, j int) bool {
+ return sOrder.Positions[i].Size > sOrder.Positions[j].Size
+ })
+ for _, v := range sOrder.Positions {
+ if positions != "" {
+ positions += " / "
+ }
+ positions += fmt.Sprintf("%v", v.Size)
+ }
+ s.WriteString(fmt.Sprintf(`
`, bgColor)) // #FFFFFF
+ s.WriteString(fmt.Sprintf(`%v | `, order.Time.Format("2006.01.02 15:04:05.000"))) // 2018.07.06 11:08:44
+ s.WriteString(fmt.Sprintf(`%v | `, order.ID)) // 11573668
s.WriteString(fmt.Sprintf(`%v | `, order.Symbol))
- s.WriteString(fmt.Sprintf(`%v %v | `,
- strings.ToLower(order.Direction.String()),
- strings.ToLower(order.Type.String()))) // buy limit
- s.WriteString(fmt.Sprintf(`%v / %v | `, order.Amount, order.FilledAmount)) // 0.20 / 0.00
- s.WriteString(fmt.Sprintf(`%v | `, order.Price)) // 1.16673
+ s.WriteString(fmt.Sprintf(`%v | `, orderType)) // buy limit/buy
+ s.WriteString(fmt.Sprintf(`%v / %v | `, order.Amount, order.FilledAmount)) // 0.20 / 0.00
+ s.WriteString(fmt.Sprintf(`%v | `, price)) // 1.16673
if order.AvgPrice > 0 {
s.WriteString(fmt.Sprintf(`%v | `, order.AvgPrice))
} else {
s.WriteString(` | `)
}
- s.WriteString(` | `)
- s.WriteString(fmt.Sprintf(`%v | `, order.UpdateTime.Format("2006.01.02 15:04:05")))
+ s.WriteString(fmt.Sprintf(`%.8f | `, order.Pnl))
+ s.WriteString(fmt.Sprintf(`%.8f | `, order.Commission))
+ s.WriteString(fmt.Sprintf(`%v | `, sOrder.Balance))
+ s.WriteString(fmt.Sprintf(`%v | `, order.UpdateTime.Format("2006.01.02 15:04:05.000")))
s.WriteString(fmt.Sprintf(`%v | `, order.Status.String())) // canceled
- s.WriteString(` | `)
+ s.WriteString(fmt.Sprintf(`%v | `, positions))
s.WriteString(`
`)
}
- html = strings.Replace(html, ``, s.String(), -1)
- return
+ return s.String()
}
func (b *Backtest) readTradeLog(path string) (orders []*SOrder, dealOrders []*SOrder, err error) {
@@ -400,12 +432,15 @@ func (b *Backtest) parseSOrder(s string) (event string, so *SOrder, err error) {
if eventValue := ret.Get("event"); eventValue.Exists() {
var order Order
var orderbook OrderBook
+ var positions []*Position
event = eventValue.String()
tsString := ret.Get("ts").String() // 2019-10-01T08:00:00.143+0800
msg := ret.Get("msg").String()
orderJson := ret.Get("order").String()
orderbookJson := ret.Get("orderbook").String()
+ positionsJson := ret.Get("positions").String()
+ balance := ret.Get("balance").Float()
err = json.Unmarshal([]byte(orderJson), &order)
if err != nil {
@@ -415,6 +450,10 @@ func (b *Backtest) parseSOrder(s string) (event string, so *SOrder, err error) {
if err != nil {
return
}
+ err = json.Unmarshal([]byte(positionsJson), &positions)
+ if err != nil {
+ return
+ }
var ts time.Time
ts, err = time.Parse("2006-01-02T15:04:05.000Z0700", tsString)
if err != nil {
@@ -424,6 +463,8 @@ func (b *Backtest) parseSOrder(s string) (event string, so *SOrder, err error) {
Ts: ts,
Order: &order,
OrderBook: &orderbook,
+ Positions: positions,
+ Balance: balance,
Comment: msg,
}
}
diff --git a/backtest/report.go b/backtest/report.go
index cc26182..09a6fb5 100644
--- a/backtest/report.go
+++ b/backtest/report.go
@@ -7,8 +7,10 @@ import (
// SOrder "event":"order"/"deal"
type SOrder struct {
- Ts time.Time // ts: 2019-10-02T07:03:53.584+0800
- Order *Order // order
- OrderBook *OrderBook // orderbook
- Comment string // msg: Place order/Match order
+ Ts time.Time // ts: 2019-10-02T07:03:53.584+0800
+ Order *Order // order
+ OrderBook *OrderBook // orderbook
+ Positions []*Position // positions
+ Balance float64 // balance
+ Comment string // msg: Place order/Match order
}
diff --git a/backtest/statik/statik.go b/backtest/statik/statik.go
index 16b807b..3f3d6e5 100644
--- a/backtest/statik/statik.go
+++ b/backtest/statik/statik.go
@@ -7,6 +7,6 @@ import (
)
func init() {
- data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x10\x06\xafP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x00ReportHistoryTemplate.htmlUT\x05\x00\x01\xe1\xe6\xbd^\xec\x9a[o\xdb\xc6\x12\xc7\xdf\x03\xe4;Lh\x9c\xf3\x14\x92KQ\xd4\x85\xa6xN\xe2K\x1d m\x0c[iQ\xd4}X\x89ki\x01\xde\xb2\\Y\x16\x0c\x7f\xf7\x82\x17I\x94\x96\x14\x97\x8aZ\xb4@\x93\x07\x93\xcb\xd9\x99\xd9\xdf\xfc\x87\\\x9av\xde]~\xb9\x18\xffz{\x05s\x1e\xf8p\xfb\xf5\xe3\xe7O\x17\xa0\xa8\xba\xfe\x8by\xa1\xeb\x97\xe3K\xb8\x19\xff\xf8\x19\xba\x1a2`\xccp\x98PN\xa3\x10\xfb\xba~\xf5\x93\x02\xca\x9c\xf3\xd8\xd6\xf5\xe5r\xa9-M-b3}|\xa7\xa7\xae\xba\xba\x1fE \xd1<\xee)\xee\xdb7N:\xe6\xbe}\x03\xe0\xcc \xf6\xb2#\x00' \x1cC\xeaC%\xdf\x16\xf4i\xa4L\xa3\x90\x93\x90\xab|\x15\x13\x05\x8a\xb3\x91\xc2\xc93\xcf\xdc\x9e\xc3t\x8eYB\xf8\xe8\xeb\xf8Z\x1d(kG\x9cr\x9f\xb8c\x86=\x0274\xe1\x11[\xc1\x1d\x89#\xc6\x1d=\xbfV\x8e\x18\xe2\x80\x8c\x94\x19 \xc3y\xe4\xdb,\xc0\xe1\xf3m\x1a\xa6\x02a\xb4d8^\x1b3:\x9bse\x9d\xd5\x92z|nC\xa7\x83\xe2\xe7s\x98\x93\xf4\xa2\x0d\xe9\x99\xe2\xa6u\xb3\x1d\x9d\xcfk\\\x1bh\xdfw\x96\x88\x8ckg\xe2vP\x07i\xc8\xd2\x8c.\x18\x86mX\xd9\xaa7\xc1\xb2\xb5\xae\xab[Zj\x9e}-\xf1u\xecu4#\x8fV\x85p}P\xe5e\xaf\xbe\x15eD\xbbe\xbc'\x9c\xd3p\x96\xe4\x8b\x90,[\xc5Z\n\x9c\xa5\xea\xb9W\xcf1a\xdc\xde\xfa\xab1M\xab\xb1\xa3\x07g\xe2\xfe\xaf\x80\xea\xb5\x86Z\x91\xc8\xfd*\x98D\xfeq\x89\xa4+\x7f\xc9\x1d\xbc\xaa\xaa[\x91\xd6q9\xdd\x12F#\xef;r\xca\x1d\x9c6'\xccp@8a\xc9\xf1ym}\x1c\x9d\xd9\xdf\xa1-\xeeH\xb2\xf0ySW\xb4\x05\xfc)\xa4\x9cb\x1f\xae\xbe-(_\xd5A^\xa3\xdc\xb5\x16p\x1ej<\xca%c\x94L[\x04\xb8\\0\x9cn\x96\x9a\xbc\xaf\xed\x8e\x96BE\xec;\xc2\x17\xac1rn\xd5bI\xf9\x04\xf8\xed?\xbf\xcb\xb9N-\xdb\xb8_\x84 K\xadl{Jr\x1f\x17+\xf8/\xdcD\xbe\x07r\x10\x85 -\x16,\xcc\x95A[9\xe90\x82\xaaM3\x96CIa~\xf7=S\xdc\xd3f4\x87\xdfq\xcf\xac\xa1i6\xd3\xdc\xbc\xa1\x96\xb7\xec\xf2\xbf\xac\x1c\x08\xef\x9b\xe6\xa1_\xdb\xa5\xef\x0e\xf9\n\x0e\xbe^H\x98\xe4K;hRBp\xc0N\x17__\xe4>_\x1d\xfd\xf1\xe3\xb4>\x1d=\xfb\xc6\x9c\x1d\xa5/\xee\xe9\xcf\xf57\xe9\xec\xcf\x15\xdc?\x02\x00\x00\xff\xffPK\x07\x08\xf1.\xc8F\xbc\x05\x00\x00`!\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x10\x06\xafP\xf1.\xc8F\xbc\x05\x00\x00`!\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb6\x81\x00\x00\x00\x00ReportHistoryTemplate.htmlUT\x05\x00\x01\xe1\xe6\xbd^PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00Q\x00\x00\x00\x0d\x06\x00\x00\x00\x00"
+ data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\xef+\xafP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x00ReportHistoryTemplate.htmlUT\x05\x00\x012)\xbe^\xec\x9a\xdfo\x9b\xc8\x13\xc0\xdf+\xf5\x7f\x98\x12}\xbfO\xc5,\xc6\xf8\x07\xc1\xdc\xb5\xf9q\xa9\xd4\xbbF\x89\xab\xd3\xe9r\x0fk\xb3\xb1W\x02\x96.\xeb$V\x94\xff\xfd\xc4/\xc7\xf6\x82Y\\\x9f\xd4\x87\xb6\x0f\x86avf\xf633\xcb\x02q\xdf\x9d\x7f9\x9b\xfcu}\x01\x0b\x11\x06p\xfd\xf5\xe3\xe7Og\xa0\xe9\x86\xf1\xa7uf\x18\xe7\x93s\xb8\x9a\xfc\xfe\x19z\x1dd\xc2\x84\xe3(\xa1\x82\xb2\x08\x07\x86q\xf1\x87\x06\xdaB\x88\xd81\x8c\xc7\xc7\xc7\xce\xa3\xd5a|nLn\x8c\xd4T\xcf\x08\x18KH\xc7\x17\xbe\xe6\xbd}\xe3\xa62\xef\xed\x1b\x00wA\xb0\x9f\x1d\x01\xb8!\x11\x18R\x1b:\xf9\xb6\xa4\x0fcm\xc6\"A\"\xa1\x8bUL4(\xce\xc6\x9a O\"3{\n\xb3\x05\xe6 \x11\xe3\xaf\x93K}\xa8\x95\x86\x04\x15\x01\xf1&\x1c\xfb\x04\xaeh\"\x18_\xc1\x0d\x89\x19\x17\xae\x91_\xdb\xf4\x18\xe1\x90\x8c\xb59\x89\x08\xc7\x82\xf1\x0dG\xb3\x80\x92H\x80 <\xa4\x11\x0e\xd6\xf6\x13\xb1\n\x08\xa4Q\x15\xc1\xcc\x92d}\xf1\x9d\xae\xe7G\xbf\x86\xc4\xa7\x18\x92\x19'$\x82\xe7\\\x08 |x\x86{\x16 \x07\x86\xb1\x00\x98\xe0\x05\x0b\xf1\xfb\x0f\x9c\xe2\xe0\x14^\xd6j\x8b\xb5\x9a\x89bQ\xad\xf6\xb2\xe5)\xe64\x12\x95\x8e\x06u\x06\xb6\xfc\x8c\x1a\xdct\xc2\xc4\xc7\x82\xc03\x84 \xd3\xa3e8%\\\xbfg<\xc4\xc2\xd1~\xcb\xf8\x05p\x8e\x05\xd1N7\x86\xa4s\xac\x1crwr\xf7\xfe\xee\xe4\xee\x04\xddu\x10:]{\x992\x7f\x05\xcf!\xe6s\x1a9f\xfctZ\xc8\x0dC\xd7K\xc6F\x96\x81\xbc\x82\x8c\xa2\x84\xdct`\xfa\xeb\xd3\x07\xc0\x01\x9dGcmF\"AxVr\x02O\x03\x023\x12\x04I\x8cg4\x9a\x8f5S\xcb\xcec\xec\xfb\xd9\xb9\xa5\xc1\x94q\x9f\xf0\xb1\x86\x8at\xba\x82\xcb\xb6rp\xae\xf0a\xc6Rk\xd1X3-\xcd\xcb\x1cg\x81\x8d\xb5\"q\xbd5Q\xcds\xa75\x159\xf5\xdc)\xf7\\\xc3\xa7\x0f\x9ek\x88\xa2\x1d\\C\xf0\xe2\xe8\x9d\xaeo\xc4\x11\x90{\xf1\x1a\x05\xb8b\xf1\x1a\x86\xa5A\xc4\x1e9\x8eKeN\xe7\x0b\xa1\x95Q=R_,\x1c\xe8vQ\xfct\n\x0b\x92^t =\xd3\xbc4o\x8ek\x88E\x8di\x13\xed\xda\xce\x02Q1\xedN\xbd.\xea\xa2\x0e\xb2;f\x0fL\xd31\xedl\xd6kg\xd9\\\xcb\xecnL5\x8f\xbe\x96x\xe9\xbb\xf4f\xe6\xde\xaa\x10\x96\x07UVv\xf2[\x91F\xb4\x9d\xc6[\"\x04\x8d\xe6I> \xc5\xb4U\xcc\xa5\xc0\xb9\x91=\xef\xe2)&\\8\xaf\xf6jT\xd3ll\xd5\x83;\xf5~)\xa0\xfa\xad\xa1V\x04r\xbb\n\xa7,8,\x90t\xe6\xcf\xb9\x81\x17]\xf7*\xc2:,\xa6k\xc2)\xf3\xbf#\xa6\xdc\xc0qc\xc2\x1c\x87D\x10\x9e\x1c\x1e\xd7\xab\x8d\x83#\xfb\x11\xda\xe2\x86$\xcb@4uE[\xc0\x9f\"*(\x0e\xe0\xe2\xdb\x92\x8aU\x1d\xe4\x12\xe5\xb6\xb6\x84s_\xe3Q\xa1\xe8cC\xb5\x85\x83\xf3%\xc7\xe9f\xa9\xc9z\xa9wp)T\xf8\xbe!b\xc9\x1b=\xe7Z-\xa6\x94\x0f\x80\xbf\xff\xf7\x8f\x9a\xe9T\xb3\x8d\xf9e\x04\xaa\xd46u\x8fI\xee\xe3r\x05\xff\x87+\x16\xf8\xa0\x06Q\x1a\xd0b\xc2\xd2X\x15\xb4\x95\x83\xf6#\xa8\x8ac\xfb6nf\xb7\xf1\xba\x05d\xdf\xc8~:\xf0\xa7r\xa1<\xf81\x94\xf3t\xaaj\xf7\xda(\x9bhW\xbb\xb2\xdb\xe4\xcd\xf3b\xef=\xabkg%\xd7x\xcf\xf9\x92n\xd7\xb7o9\x8b\xe60`:\x9f\xb1\x80\xf1\xb1vra_\xa2\xcb\xb3\xcau`'$\xab\xdc\xcb~\x89I\x04\x13\x1a\x92=}\xbd\x8e\xadA'\xdf%5(MVq\x93\xaf\x0f![F\xa2A\xe9\x9a\xd3Y\xa3\xa1\x879\xa8\xe8]svO\x9b\x1c\x9e\xb10\xa4IBY\xd4\xa0\xf8\x11\x078j\xf4\xa9\xc0\xfcV`\xd1\xa4s\xcd\xf2\xd7\x16\xb5+d\xbaU\xcc\x9e\x03u\xce\x1e\x93\x97\x8d=\xb4B\x99\xec\xddm\xfd\xd7\xedpNp\xf0\xb3\x1b~v\xc3\xb1\xbb\xc1'8\xd0\xabZ\xa2|\xd9\x95V\xd5k\x19]f\xff\xb4\x9d\xad\xd6\x86\xdf.2\x87\x1d\xd4\xef \x13\x90\xe5\xa0\xa1c[\x99\xef\x0d\x15\x13Y]d\x8f\x86\xbb\xf2\xdd\xf3i k\xbfZ\xdbs\xd4AHEf#@\xa8\xd5\x85\xfc|\xab\x1f7\xc8\x0d\xd2\xffM\xe4\xccn\x07Y\x80FN\xcfv\xac\x9eLn8B}{\xb0+\xffz{~vu\xb9+MH\x10\xec\xcah$O\xbd[\x81c4\xea\xf7G\x92{s4\xec\x99\xb6\x12=U\xd9\x810\x95\xca\xf0\x15\xe6\xc8Af5\xccAW\x0d\xe6t\xb9\xda\x15\xb1\xa5P\x87)\xe5,\x87\xd9\xb7U U\xcbzu0\xe5\x0b\xc7\xacL\x1b9\xb6\\\x1a9L\xa9\xd7\x95a\xb6(L[JY\xc1Rb\xac\xce\xb2\xb60\xdb\xb2lW\x98\xb6Y\xdb\xe5\x83\xbe\x1a\xcb\xaa.oU\x99rcd4\x07\x92\\\x95\x9c\xd9\x19Jc3\x9a\xddNW\xbap\xd4\xca\xb4\x9c^\x0dM9\"u\x9amJSJZ\x01S\x92\xab\xc2\xac-\xcd\xf60[\x96f\xcf1\xe5F\xcba\x1e\xde\xe6m*\xd3\x96\xd6\xc6\x02\xa6\xb4sP\x05W\xe5\xaa\x80\xd9k \xb3ee\xf6\x9d^\xcd\x9a9\x94\xe4\xca0\xdb\x14\xa6\xc4,g9<\xfe\x9a\xd9\x9ee\xcb\xc2\x1c8H\xbe\x9bf,G\x8a\x85\xf9\xddk\xa6\xbc\xa7\xcdh\x8e\xbec\xcd\xac\xa1i5\xd3\xac\xdc\xb2\xab\xbf\x0b\x1dJ\xcf\xa4\xd6\xbe\xb7\x82\xe9\x93E>\x83\xbd\x0f\x1f\n*\xf9\xd4\xf6\xaal \xd8\xa3g\xc8\xcf6j_\xc7\x0e\xfe\xb6r\\\x9b\xae\x91}\xc2\xce\x8e\xd2\x87\xfb\xf4\xb7\xfc\xe4\x9d\xfd5\x84\xf7o\x00\x00\x00\xff\xffPK\x07\x08Z\x81\xe6a\xa1\x05\x00\x00\xbf!\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xef+\xafPZ\x81\xe6a\xa1\x05\x00\x00\xbf!\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb6\x81\x00\x00\x00\x00ReportHistoryTemplate.htmlUT\x05\x00\x012)\xbe^PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00Q\x00\x00\x00\xf2\x05\x00\x00\x00\x00"
fs.Register(data)
}
diff --git a/exchanges/deribitsim/deribitsim.go b/exchanges/deribitsim/deribitsim.go
index 4f97268..2a948d4 100644
--- a/exchanges/deribitsim/deribitsim.go
+++ b/exchanges/deribitsim/deribitsim.go
@@ -140,12 +140,7 @@ func (b *DeribitSim) PlaceOrder(symbol string, direction Direction, orderType Or
b.orders[id] = order
result = order
- b.eLog.Infow("Place order",
- SimEventKey, SimEventOrder,
- "order", order,
- "orderbook", ob,
- "balance", b.balance,
- "position", b.positions)
+ b.logOrderInfo("Place order", SimEventOrder, order)
return
}
@@ -193,6 +188,8 @@ func (b *DeribitSim) matchMarketOrder(order *Order) (changed bool, err error) {
// 需要满足: sizeCurrency <= margin * 100
// 可开仓数量: <= margin * 100 * price(ask/bid)
var maxSize float64
+ var filledAmount float64
+ var avgPrice float64
// 市价成交
if order.Direction == Buy {
@@ -202,41 +199,41 @@ func (b *DeribitSim) matchMarketOrder(order *Order) (changed bool, err error) {
return
}
- price := ob.AskPrice()
- size := order.Amount
+ filledAmount, avgPrice = b.matchBid(order.Amount, ob.Asks...)
// trade fee
- fee := size / price * b.takerFeeRate
+ fee := filledAmount / avgPrice * b.takerFeeRate
// Update balance
b.addBalance(-fee)
// Update position
- b.updatePosition(order.Symbol, size, price)
-
- order.AvgPrice = price
+ pnl := b.updatePosition(order.Symbol, filledAmount, avgPrice)
+ order.Pnl += pnl
+ order.Commission += fee
+ order.AvgPrice = avgPrice
} else if order.Direction == Sell {
maxSize = margin * 100 * ob.BidPrice()
if order.Amount > maxSize {
- err = errors.New(fmt.Sprintf("Rejected, maximum size of future position is %v", maxSize))
+ err = errors.New(fmt.Sprintf("rejected, maximum size of future position is %v", maxSize))
return
}
- price := ob.BidPrice()
- size := order.Amount
+ filledAmount, avgPrice = b.matchBid(order.Amount, ob.Bids...)
// trade fee
- fee := size / price * b.takerFeeRate
+ fee := filledAmount / avgPrice * b.takerFeeRate
// Update balance
b.addBalance(-fee)
// Update position
- b.updatePosition(order.Symbol, -size, price)
-
- order.AvgPrice = price
+ pnl := b.updatePosition(order.Symbol, -filledAmount, avgPrice)
+ order.Pnl += pnl
+ order.Commission += fee
+ order.AvgPrice = avgPrice
}
- order.FilledAmount = order.Amount
+ order.FilledAmount = filledAmount
order.UpdateTime = ob.Time
order.Status = OrderStatusFilled
changed = true
@@ -250,7 +247,9 @@ func (b *DeribitSim) matchLimitOrder(order *Order, immediate bool) (changed bool
ob := b.data.GetOrderBook()
if order.Direction == Buy { // Bid order
- if order.Price < ob.AskPrice() {
+ filledAmount, avgPrice := b.matchBid(order.Amount, ob.Asks...)
+ //if order.Price < ob.AskPrice() {
+ if filledAmount == 0 {
return
}
@@ -262,29 +261,31 @@ func (b *DeribitSim) matchLimitOrder(order *Order, immediate bool) (changed bool
}
// match trade
- size := order.Amount
var fee float64
// trade fee
if immediate {
- fee = size / order.Price * b.takerFeeRate
+ fee = filledAmount / avgPrice * b.takerFeeRate
} else {
- fee = size / order.Price * b.makerFeeRate
+ fee = filledAmount / avgPrice * b.makerFeeRate
}
// Update balance
b.addBalance(-fee)
// Update position
- b.updatePosition(order.Symbol, size, order.Price)
+ b.updatePosition(order.Symbol, filledAmount, avgPrice)
- order.AvgPrice = order.Price
- order.FilledAmount = order.Amount
+ order.Commission -= fee
+ order.AvgPrice = avgPrice
+ order.FilledAmount = filledAmount
order.UpdateTime = ob.Time
order.Status = OrderStatusFilled
changed = true
} else { // Ask order
- if order.Price > ob.BidPrice() {
+ filledAmount, avgPrice := b.matchBid(order.Amount, ob.Asks...)
+ //if order.Price > ob.BidPrice() {
+ if filledAmount == 0 {
return
}
@@ -296,24 +297,24 @@ func (b *DeribitSim) matchLimitOrder(order *Order, immediate bool) (changed bool
}
// match trade
- size := order.Amount
var fee float64
// trade fee
if immediate {
- fee = size / order.Price * b.takerFeeRate
+ fee = filledAmount / avgPrice * b.takerFeeRate
} else {
- fee = size / order.Price * b.makerFeeRate
+ fee = filledAmount / avgPrice * b.makerFeeRate
}
// Update balance
b.addBalance(-fee)
// Update position
- b.updatePosition(order.Symbol, -size, order.Price)
+ b.updatePosition(order.Symbol, -filledAmount, avgPrice)
- order.AvgPrice = order.Price
- order.FilledAmount = order.Amount
+ order.Commission -= fee
+ order.AvgPrice = avgPrice
+ order.FilledAmount = filledAmount
order.UpdateTime = ob.Time
order.Status = OrderStatusFilled
changed = true
@@ -321,18 +322,107 @@ func (b *DeribitSim) matchLimitOrder(order *Order, immediate bool) (changed bool
return
}
+func (b *DeribitSim) matchBid(size float64, asks ...Item) (filledSize float64, avgPrice float64) {
+ type item = struct {
+ Amount float64
+ Price float64
+ }
+
+ var items []item
+ lSize := size
+ for i := 0; i < len(asks); i++ {
+ if lSize >= asks[i].Amount {
+ items = append(items, item{
+ Amount: asks[i].Amount,
+ Price: asks[i].Price,
+ })
+ lSize -= asks[i].Amount
+ } else {
+ items = append(items, item{
+ Amount: lSize,
+ Price: asks[i].Price,
+ })
+ lSize = 0
+ }
+ if lSize <= 0 {
+ break
+ }
+ }
+
+ if lSize != 0 {
+ return
+ }
+
+ // 计算平均价
+ amount := 0.0
+ for _, v := range items {
+ amount += v.Price * v.Amount
+ filledSize += v.Amount
+ }
+ if filledSize == 0 {
+ return
+ }
+ avgPrice = amount / filledSize
+ return
+}
+
+func (b *DeribitSim) matchAsk(size float64, bids ...Item) (filledSize float64, avgPrice float64) {
+ type item = struct {
+ Amount float64
+ Price float64
+ }
+
+ var items []item
+ lSize := size
+ for i := 0; i < len(bids); i++ {
+ if lSize >= bids[i].Amount {
+ items = append(items, item{
+ Amount: bids[i].Amount,
+ Price: bids[i].Price,
+ })
+ lSize -= bids[i].Amount
+ } else {
+ items = append(items, item{
+ Amount: lSize,
+ Price: bids[i].Price,
+ })
+ lSize = 0
+ }
+ if lSize <= 0 {
+ break
+ }
+ }
+
+ if lSize != 0 {
+ return
+ }
+
+ // 计算平均价
+ amount := 0.0
+ for _, v := range items {
+ amount += v.Price * v.Amount
+ filledSize += v.Amount
+ }
+ if filledSize == 0 {
+ return
+ }
+ avgPrice = amount / filledSize
+ return
+}
+
// 更新持仓
-func (b *DeribitSim) updatePosition(symbol string, size float64, price float64) {
+func (b *DeribitSim) updatePosition(symbol string, size float64, price float64) (pnl float64) {
position := b.getPosition(symbol)
if position == nil {
log.Fatalf("position error symbol=%v", symbol)
}
if position.Size > 0 && size < 0 || position.Size < 0 && size > 0 {
- b.closePosition(position, size, price)
+ pnl, _ = b.closePosition(position, size, price)
} else {
b.addPosition(position, size, price)
}
+ return
}
// 增加持仓
@@ -361,7 +451,7 @@ func (b *DeribitSim) addPosition(position *Position, size float64, price float64
}
// 平仓,超过数量,则开立新仓
-func (b *DeribitSim) closePosition(position *Position, size float64, price float64) (err error) {
+func (b *DeribitSim) closePosition(position *Position, size float64, price float64) (pnl float64, err error) {
if position.Size == 0 {
err = errors.New("当前无持仓")
return
@@ -374,19 +464,19 @@ func (b *DeribitSim) closePosition(position *Position, size float64, price float
if remaining > 0 {
// 先平掉原有持仓
// 计算盈利
- pnl, _ := CalcPnl(position.Side(), math.Abs(position.Size), position.AvgPrice, price)
+ pnl, _ = CalcPnl(position.Side(), math.Abs(position.Size), position.AvgPrice, price)
b.addPnl(pnl)
position.AvgPrice = price
position.Size = position.Size + size
} else if remaining == 0 {
// 完全平仓
- pnl, _ := CalcPnl(position.Side(), math.Abs(size), position.AvgPrice, price)
+ pnl, _ = CalcPnl(position.Side(), math.Abs(size), position.AvgPrice, price)
b.addPnl(pnl)
position.AvgPrice = 0
position.Size = 0
} else {
// 部分平仓
- pnl, _ := CalcPnl(position.Side(), math.Abs(position.Size), position.AvgPrice, price)
+ pnl, _ = CalcPnl(position.Side(), math.Abs(position.Size), position.AvgPrice, price)
b.addPnl(pnl)
//position.AvgPrice = position.AvgPrice
position.Size = position.Size + size
@@ -522,15 +612,23 @@ func (b *DeribitSim) RunEventLoopOnce() (err error) {
for _, order := range b.openOrders {
changed, err = b.matchOrder(order, false)
if changed {
- b.eLog.Warnw("Match order",
- SimEventKey, SimEventDeal,
- "order", order,
- "orderbook", b.data.GetOrderBook())
+ b.logOrderInfo("Match order", SimEventDeal, order)
}
}
return
}
+func (b *DeribitSim) logOrderInfo(msg string, event string, order *Order) {
+ ob := b.data.GetOrderBook()
+ position := b.getPosition(order.Symbol)
+ b.eLog.Infow(msg,
+ SimEventKey, event,
+ "order", order,
+ "orderbook", ob,
+ "balance", b.balance,
+ "positions", []*Position{position})
+}
+
func NewDeribitSim(data *dataloader.Data, cash float64, makerFeeRate float64, takerFeeRate float64) *DeribitSim {
return &DeribitSim{
data: data,
diff --git a/models.go b/models.go
index b70bb82..ac9c06a 100644
--- a/models.go
+++ b/models.go
@@ -131,6 +131,8 @@ type Order struct {
Type OrderType `json:"type"` // 委托类型
PostOnly bool `json:"post_only"` // 只做Maker选项
ReduceOnly bool `json:"reduce_only"` // 只减仓选项
+ Commission float64 `json:"commission"` // 支付的佣金
+ Pnl float64 `json:"pnl"` // 盈亏
UpdateTime time.Time `json:"update_time"` // 更新时间
Status OrderStatus `json:"status"` // 委托状态
}