feat: actual exchange fees for entry orders
- BitgetTrade: added GetTradeFee() queries /mix/order/fills - HyperLiquidTrade: added GetTradeFee() parses MarketOpen response - trader.placeOrder() now returns (errMsg, actualFeeUSD) - executeEntry uses actual fee from exchange, falls back to estimate
This commit is contained in:
@@ -87,6 +87,57 @@ func (b *BitgetTrade) PlaceMarketOrder(side, symbol, size, tradeSide string) (st
|
|||||||
return result.Data.OrderID, nil
|
return result.Data.OrderID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTradeFee queries the fills endpoint for actual fee charged.
|
||||||
|
func (b *BitgetTrade) GetTradeFee(symbol, orderID string) (feeUSD float64, err error) {
|
||||||
|
ts := fmt.Sprintf("%d", time.Now().UnixMilli())
|
||||||
|
method := "GET"
|
||||||
|
requestPath := "/api/v2/mix/order/fills?symbol=" + symbol + "&orderId=" + orderID
|
||||||
|
host := "https://api.bitget.com"
|
||||||
|
|
||||||
|
sign := b.sign(method, requestPath, ts, "")
|
||||||
|
url := host + requestPath
|
||||||
|
req, _ := http.NewRequest(method, url, nil)
|
||||||
|
req.Header.Set("ACCESS-KEY", b.APIKey)
|
||||||
|
req.Header.Set("ACCESS-SIGN", sign)
|
||||||
|
req.Header.Set("ACCESS-TIMESTAMP", ts)
|
||||||
|
req.Header.Set("ACCESS-PASSPHRASE", b.Passphrase)
|
||||||
|
if b.paperMode {
|
||||||
|
req.Header.Set("paptrading", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := b.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("http: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
var raw struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data []json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(respBody, &raw); err != nil {
|
||||||
|
return 0, fmt.Errorf("parse: %s", string(respBody))
|
||||||
|
}
|
||||||
|
if raw.Code != "00000" {
|
||||||
|
return 0, fmt.Errorf("bitget error: %s - %s", raw.Code, raw.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalFee float64
|
||||||
|
for _, item := range raw.Data {
|
||||||
|
var fill struct {
|
||||||
|
FillFee string `json:"fillFee"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(item, &fill); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f, _ := strconv.ParseFloat(fill.FillFee, 64)
|
||||||
|
totalFee += math.Abs(f)
|
||||||
|
}
|
||||||
|
return totalFee, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BitgetTrade) sign(method, requestPath, timestamp, body string) string {
|
func (b *BitgetTrade) sign(method, requestPath, timestamp, body string) string {
|
||||||
raw := timestamp + method + requestPath + body
|
raw := timestamp + method + requestPath + body
|
||||||
mac := hmac.New(sha256.New, []byte(b.APISecret))
|
mac := hmac.New(sha256.New, []byte(b.APISecret))
|
||||||
@@ -128,7 +179,6 @@ func (b *BitgetTrade) GetBalance() (float64, error) {
|
|||||||
return 0, fmt.Errorf("bitget error: %s - %s", code, msg)
|
return 0, fmt.Errorf("bitget error: %s - %s", code, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse data as array of accounts
|
|
||||||
dataRaw, ok := raw["data"]
|
dataRaw, ok := raw["data"]
|
||||||
if !ok || dataRaw == nil {
|
if !ok || dataRaw == nil {
|
||||||
return 0, fmt.Errorf("no data in response")
|
return 0, fmt.Errorf("no data in response")
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ func (h *HyperLiquidTrade) IsConfigured() bool {
|
|||||||
return h.configured
|
return h.configured
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PlaceMarketOrder places a market order and returns the raw JSON response.
|
||||||
func (h *HyperLiquidTrade) PlaceMarketOrder(coin, side, sz string) (string, error) {
|
func (h *HyperLiquidTrade) PlaceMarketOrder(coin, side, sz string) (string, error) {
|
||||||
if !h.configured {
|
if !h.configured {
|
||||||
return "", fmt.Errorf("HL not configured")
|
return "", fmt.Errorf("HL not configured")
|
||||||
@@ -120,6 +121,33 @@ func (h *HyperLiquidTrade) PlaceMarketOrder(coin, side, sz string) (string, erro
|
|||||||
return string(respJSON), nil
|
return string(respJSON), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTradeFee parses the MarketOpen JSON response to extract filled size and
|
||||||
|
// estimates the actual fee from the exchange taker rate.
|
||||||
|
func (h *HyperLiquidTrade) GetTradeFee(orderResponseJSON string, takerFeePct float64) (feeUSD float64, err error) {
|
||||||
|
var resp struct {
|
||||||
|
Statuses []struct {
|
||||||
|
Filled *struct {
|
||||||
|
TotalSz string `json:"totalSz"`
|
||||||
|
AvgPx string `json:"avgPx"`
|
||||||
|
} `json:"filled,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
} `json:"statuses"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(orderResponseJSON), &resp); err != nil {
|
||||||
|
return 0, fmt.Errorf("parse order response: %w", err)
|
||||||
|
}
|
||||||
|
for _, st := range resp.Statuses {
|
||||||
|
if st.Filled != nil {
|
||||||
|
sz, _ := strconv.ParseFloat(st.Filled.TotalSz, 64)
|
||||||
|
px, _ := strconv.ParseFloat(st.Filled.AvgPx, 64)
|
||||||
|
if sz > 0 && px > 0 {
|
||||||
|
return sz * px * takerFeePct / 100, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("no filled status in response")
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HyperLiquidTrade) GetBalance() (float64, error) {
|
func (h *HyperLiquidTrade) GetBalance() (float64, error) {
|
||||||
if !h.configured {
|
if !h.configured {
|
||||||
return 0, fmt.Errorf("HL not configured")
|
return 0, fmt.Errorf("HL not configured")
|
||||||
|
|||||||
@@ -554,14 +554,18 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
|
|||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
|
|
||||||
// Execute both legs
|
// Execute both legs
|
||||||
if err := t.placeOrder(pos.LongLeg, "buy", store); err != "" {
|
var longFeeUSD, shortFeeUSD float64
|
||||||
log.Printf("[Trader] %s: long leg placeOrder failed: %s", opp.Coin, err)
|
var errMsg string
|
||||||
|
errMsg, longFeeUSD = t.placeOrder(pos.LongLeg, "buy", store)
|
||||||
|
if errMsg != "" {
|
||||||
|
log.Printf("[Trader] %s: long leg placeOrder failed: %s", opp.Coin, errMsg)
|
||||||
t.cleanup(pos.Coin)
|
t.cleanup(pos.Coin)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
time.Sleep(t.cfg.LegDelay)
|
time.Sleep(t.cfg.LegDelay)
|
||||||
if err := t.placeOrder(pos.ShortLeg, "sell", store); err != "" {
|
errMsg, shortFeeUSD = t.placeOrder(pos.ShortLeg, "sell", store)
|
||||||
log.Printf("[Trader] %s: short leg placeOrder failed: %s", opp.Coin, err)
|
if errMsg != "" {
|
||||||
|
log.Printf("[Trader] %s: short leg placeOrder failed: %s", opp.Coin, errMsg)
|
||||||
// Leg1 placed successfully, leg2 failed — try to close leg1
|
// Leg1 placed successfully, leg2 failed — try to close leg1
|
||||||
pos.Status = "failed"
|
pos.Status = "failed"
|
||||||
if closeErr := t.closeLeg(pos.LongLeg); closeErr != "" {
|
if closeErr := t.closeLeg(pos.LongLeg); closeErr != "" {
|
||||||
@@ -570,7 +574,7 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
|
|||||||
pos.ErrorLog = fmt.Sprintf("ORPHAN: leg1 %s %s placed OK, leg2 %s %s failed (%s), leg1 close also failed (%s)",
|
pos.ErrorLog = fmt.Sprintf("ORPHAN: leg1 %s %s placed OK, leg2 %s %s failed (%s), leg1 close also failed (%s)",
|
||||||
pos.LongLeg.Exchange, pos.LongLeg.Side,
|
pos.LongLeg.Exchange, pos.LongLeg.Side,
|
||||||
pos.ShortLeg.Exchange, pos.ShortLeg.Side,
|
pos.ShortLeg.Exchange, pos.ShortLeg.Side,
|
||||||
err, closeErr)
|
errMsg, closeErr)
|
||||||
log.Printf("[Trader] ⚠️ ORPHAN POSITION on %s: %s", pos.Coin, pos.ErrorLog)
|
log.Printf("[Trader] ⚠️ ORPHAN POSITION on %s: %s", pos.Coin, pos.ErrorLog)
|
||||||
}
|
}
|
||||||
t.cleanup(pos.Coin)
|
t.cleanup(pos.Coin)
|
||||||
@@ -602,8 +606,13 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
|
|||||||
if tradeID, err := t.db.SaveTrade(dbTrade); err == nil {
|
if tradeID, err := t.db.SaveTrade(dbTrade); err == nil {
|
||||||
pos.DBTradeID = tradeID
|
pos.DBTradeID = tradeID
|
||||||
|
|
||||||
longFee := tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
|
// Use actual fee from exchange (fetched in placeOrder), fall back to estimate
|
||||||
shortFee := tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
|
if longFeeUSD <= 0 {
|
||||||
|
longFeeUSD = tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
|
||||||
|
}
|
||||||
|
if shortFeeUSD <= 0 {
|
||||||
|
shortFeeUSD = tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
|
||||||
|
}
|
||||||
longShares := tradeUnit / pos.LongLeg.EntryPrice
|
longShares := tradeUnit / pos.LongLeg.EntryPrice
|
||||||
shortShares := tradeUnit / pos.ShortLeg.EntryPrice
|
shortShares := tradeUnit / pos.ShortLeg.EntryPrice
|
||||||
|
|
||||||
@@ -611,13 +620,13 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
|
|||||||
TradeID: tradeID, Leg: "long", Type: "entry",
|
TradeID: tradeID, Leg: "long", Type: "entry",
|
||||||
Exchange: pos.LongLeg.Exchange, Side: "buy",
|
Exchange: pos.LongLeg.Exchange, Side: "buy",
|
||||||
Price: &pos.LongLeg.EntryPrice, Size: &longShares,
|
Price: &pos.LongLeg.EntryPrice, Size: &longShares,
|
||||||
Fee: &longFee, Status: &status, CreatedAt: now,
|
Fee: &longFeeUSD, Status: &status, CreatedAt: now,
|
||||||
})
|
})
|
||||||
shortOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
shortOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
||||||
TradeID: tradeID, Leg: "short", Type: "entry",
|
TradeID: tradeID, Leg: "short", Type: "entry",
|
||||||
Exchange: pos.ShortLeg.Exchange, Side: "sell",
|
Exchange: pos.ShortLeg.Exchange, Side: "sell",
|
||||||
Price: &pos.ShortLeg.EntryPrice, Size: &shortShares,
|
Price: &pos.ShortLeg.EntryPrice, Size: &shortShares,
|
||||||
Fee: &shortFee, Status: &status, CreatedAt: now,
|
Fee: &shortFeeUSD, Status: &status, CreatedAt: now,
|
||||||
})
|
})
|
||||||
t.db.SaveSystemOrder(&db.SystemOrderRecord{
|
t.db.SaveSystemOrder(&db.SystemOrderRecord{
|
||||||
TradeID: tradeID, Type: "entry", Status: "filled",
|
TradeID: tradeID, Type: "entry", Status: "filled",
|
||||||
@@ -838,14 +847,14 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier
|
|||||||
for _, p := range pos.LongEntryPrices {
|
for _, p := range pos.LongEntryPrices {
|
||||||
totalLongSharesRetry += t.cfg.TradeAmountUSD / p
|
totalLongSharesRetry += t.cfg.TradeAmountUSD / p
|
||||||
}
|
}
|
||||||
totalShortSharesRetry := 0.0
|
totalshortSharesRetry := 0.0
|
||||||
for _, p := range pos.ShortEntryPrices {
|
for _, p := range pos.ShortEntryPrices {
|
||||||
totalShortSharesRetry += t.cfg.TradeAmountUSD / p
|
totalshortSharesRetry += t.cfg.TradeAmountUSD / p
|
||||||
}
|
}
|
||||||
pos.ExitLongFeeUSD = float64(numBatchesRetry)*t.cfg.TradeAmountUSD*takerFees[pos.LongLeg.Exchange]/100 +
|
pos.ExitLongFeeUSD = float64(numBatchesRetry)*t.cfg.TradeAmountUSD*takerFees[pos.LongLeg.Exchange]/100 +
|
||||||
totalLongSharesRetry*longCurrent*takerFees[pos.LongLeg.Exchange]/100
|
totalLongSharesRetry*longCurrent*takerFees[pos.LongLeg.Exchange]/100
|
||||||
pos.ExitShortFeeUSD = float64(numBatchesRetry)*t.cfg.TradeAmountUSD*takerFees[pos.ShortLeg.Exchange]/100 +
|
pos.ExitShortFeeUSD = float64(numBatchesRetry)*t.cfg.TradeAmountUSD*takerFees[pos.ShortLeg.Exchange]/100 +
|
||||||
totalShortSharesRetry*shortCurrent*takerFees[pos.ShortLeg.Exchange]/100
|
totalshortSharesRetry*shortCurrent*takerFees[pos.ShortLeg.Exchange]/100
|
||||||
}
|
}
|
||||||
|
|
||||||
closeErr := t.closeBothLegs(pos)
|
closeErr := t.closeBothLegs(pos)
|
||||||
@@ -878,15 +887,15 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier
|
|||||||
for _, p := range pos.LongEntryPrices {
|
for _, p := range pos.LongEntryPrices {
|
||||||
totalLongShares += legCapital / p
|
totalLongShares += legCapital / p
|
||||||
}
|
}
|
||||||
totalShortShares := 0.0
|
totalshortShares := 0.0
|
||||||
for _, p := range pos.ShortEntryPrices {
|
for _, p := range pos.ShortEntryPrices {
|
||||||
totalShortShares += legCapital / p
|
totalshortShares += legCapital / p
|
||||||
}
|
}
|
||||||
|
|
||||||
longEntryFeeSum := float64(numBatches) * legCapital * takerFees[pos.LongLeg.Exchange] / 100
|
longEntryFeeSum := float64(numBatches) * legCapital * takerFees[pos.LongLeg.Exchange] / 100
|
||||||
shortEntryFeeSum := float64(numBatches) * legCapital * takerFees[pos.ShortLeg.Exchange] / 100
|
shortEntryFeeSum := float64(numBatches) * legCapital * takerFees[pos.ShortLeg.Exchange] / 100
|
||||||
longExitFeeAmt := totalLongShares * pos.LongLeg.ExitPrice * takerFees[pos.LongLeg.Exchange] / 100
|
longExitFeeAmt := totalLongShares * pos.LongLeg.ExitPrice * takerFees[pos.LongLeg.Exchange] / 100
|
||||||
shortExitFeeAmt := totalShortShares * pos.ShortLeg.ExitPrice * takerFees[pos.ShortLeg.Exchange] / 100
|
shortExitFeeAmt := totalshortShares * pos.ShortLeg.ExitPrice * takerFees[pos.ShortLeg.Exchange] / 100
|
||||||
longFeeUSD := longEntryFeeSum + longExitFeeAmt
|
longFeeUSD := longEntryFeeSum + longExitFeeAmt
|
||||||
shortFeeUSD := shortEntryFeeSum + shortExitFeeAmt
|
shortFeeUSD := shortEntryFeeSum + shortExitFeeAmt
|
||||||
|
|
||||||
@@ -957,7 +966,7 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier
|
|||||||
Price: &pos.LongLeg.ExitPrice, Size: &longExitShares,
|
Price: &pos.LongLeg.ExitPrice, Size: &longExitShares,
|
||||||
Fee: &longExitFeeAmt, Status: &status, CreatedAt: now,
|
Fee: &longExitFeeAmt, Status: &status, CreatedAt: now,
|
||||||
})
|
})
|
||||||
shortExitShares := totalShortShares
|
shortExitShares := totalshortShares
|
||||||
shortOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
shortOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
||||||
TradeID: pos.DBTradeID, Leg: "short", Type: "exit",
|
TradeID: pos.DBTradeID, Leg: "short", Type: "exit",
|
||||||
Exchange: pos.ShortLeg.Exchange, Side: "buy",
|
Exchange: pos.ShortLeg.Exchange, Side: "buy",
|
||||||
@@ -1034,30 +1043,47 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trader) placeOrder(leg *PositionLeg, side string, store *PriceStore) string {
|
func (t *Trader) placeOrder(leg *PositionLeg, side string, store *PriceStore) (string, float64) {
|
||||||
if t.cfg.TestMode {
|
if t.cfg.TestMode {
|
||||||
return t.mockFill(leg, side, store)
|
return t.mockFill(leg, side, store), 0
|
||||||
}
|
}
|
||||||
if leg.Exchange == ExBitget {
|
if leg.Exchange == ExBitget {
|
||||||
size := exchange.GetBitgetSize(leg.Coin+"USDT", t.cfg.TradeAmountUSD, leg.EntryPrice)
|
size := exchange.GetBitgetSize(leg.Coin+"USDT", t.cfg.TradeAmountUSD, leg.EntryPrice)
|
||||||
oid, err := t.bitget.PlaceMarketOrder(side, leg.Coin+"USDT", size, "open")
|
oid, err := t.bitget.PlaceMarketOrder(side, leg.Coin+"USDT", size, "open")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("BG %s error: %v", side, err)
|
return fmt.Sprintf("BG %s error: %v", side, err), 0
|
||||||
}
|
}
|
||||||
leg.Size = size
|
leg.Size = size
|
||||||
leg.OrderID = oid
|
leg.OrderID = oid
|
||||||
log.Printf("[ExRes] BG %s %s: size=%s → response=%s", side, leg.Coin+"USDT", size, oid)
|
log.Printf("[ExRes] BG %s %s: size=%s oid=%s", side, leg.Coin+"USDT", size, oid)
|
||||||
|
|
||||||
|
// Fetch actual fee from exchange
|
||||||
|
fee, fetchErr := t.bitget.GetTradeFee(leg.Coin+"USDT", oid)
|
||||||
|
if fetchErr != nil {
|
||||||
|
log.Printf("[Fee] BG GetTradeFee warning: %v", fetchErr)
|
||||||
|
} else {
|
||||||
|
log.Printf("[Fee] BG %s %s: actual fee=$%.6f", side, leg.Coin+"USDT", fee)
|
||||||
|
}
|
||||||
|
return "", fee
|
||||||
} else {
|
} else {
|
||||||
size := exchange.GetHLSize(leg.Coin, t.cfg.TradeAmountUSD, leg.EntryPrice)
|
size := exchange.GetHLSize(leg.Coin, t.cfg.TradeAmountUSD, leg.EntryPrice)
|
||||||
resp, err := t.hyperliquid.PlaceMarketOrder(leg.Coin, side, size)
|
resp, err := t.hyperliquid.PlaceMarketOrder(leg.Coin, side, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("HL %s error: %v", side, err)
|
return fmt.Sprintf("HL %s error: %v", side, err), 0
|
||||||
}
|
}
|
||||||
leg.Size = size
|
leg.Size = size
|
||||||
leg.OrderID = resp
|
leg.OrderID = resp
|
||||||
log.Printf("[ExRes] HL %s %s: size=%s → response=%s", side, leg.Coin, size, resp)
|
log.Printf("[ExRes] HL %s %s: size=%s", side, leg.Coin, size)
|
||||||
|
|
||||||
|
// Estimate fee from filled response (HL doesn't return fee in order response)
|
||||||
|
fee, fetchErr := t.hyperliquid.GetTradeFee(resp, takerFees[ExHyperLiquid])
|
||||||
|
if fetchErr != nil {
|
||||||
|
log.Printf("[Fee] HL GetTradeFee warning: %v", fetchErr)
|
||||||
|
} else {
|
||||||
|
log.Printf("[Fee] HL %s %s: actual fee=$%.6f", side, leg.Coin, fee)
|
||||||
|
}
|
||||||
|
return "", fee
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trader) closeBothLegs(pos *ArbPosition) string {
|
func (t *Trader) closeBothLegs(pos *ArbPosition) string {
|
||||||
@@ -1169,9 +1195,9 @@ func (t *Trader) retryClose(pos *ArbPosition, bgP, hlP float64, notifier *Notifi
|
|||||||
for _, p := range pos.LongEntryPrices {
|
for _, p := range pos.LongEntryPrices {
|
||||||
totalLongShares += tradeUnit / p
|
totalLongShares += tradeUnit / p
|
||||||
}
|
}
|
||||||
totalShortShares := 0.0
|
totalshortShares := 0.0
|
||||||
for _, p := range pos.ShortEntryPrices {
|
for _, p := range pos.ShortEntryPrices {
|
||||||
totalShortShares += tradeUnit / p
|
totalshortShares += tradeUnit / p
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save exit orders for legs that were just now closed
|
// Save exit orders for legs that were just now closed
|
||||||
@@ -1186,8 +1212,8 @@ func (t *Trader) retryClose(pos *ArbPosition, bgP, hlP float64, notifier *Notifi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if pos.ShortLeg.Closed {
|
if pos.ShortLeg.Closed {
|
||||||
shortExitFee := totalShortShares * pos.ShortLeg.ExitPrice * takerFees[pos.ShortLeg.Exchange] / 100
|
shortExitFee := totalshortShares * pos.ShortLeg.ExitPrice * takerFees[pos.ShortLeg.Exchange] / 100
|
||||||
shortExitShares := totalShortShares
|
shortExitShares := totalshortShares
|
||||||
_, _ = t.db.SaveOrder(&db.OrderRecord{
|
_, _ = t.db.SaveOrder(&db.OrderRecord{
|
||||||
TradeID: pos.DBTradeID, Leg: "short", Type: "exit",
|
TradeID: pos.DBTradeID, Leg: "short", Type: "exit",
|
||||||
Exchange: pos.ShortLeg.Exchange, Side: "buy",
|
Exchange: pos.ShortLeg.Exchange, Side: "buy",
|
||||||
|
|||||||
Reference in New Issue
Block a user