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"` // 委托状态 }