Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client/{mm, core}: Simple Arbitrage #2480

Merged
merged 6 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions client/cmd/testbinance/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package main

/*
* Starts an http server that responds with a hardcoded result to the binance API's
* "/sapi/v1/capital/config/getall" endpoint. Binance's testnet does not support the
* "sapi" endpoints, and this is the only "sapi" endpoint that we use.
*/

import (
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"

"decred.org/dcrdex/client/websocket"
"decred.org/dcrdex/dex"
)

const (
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
)

var (
log = dex.StdOutLogger("TBNC", dex.LevelDebug)
)

func main() {
if err := mainErr(); err != nil {
fmt.Fprint(os.Stderr, err, "\n")
os.Exit(1)
}
os.Exit(0)
}

func mainErr() error {
f := &fakeBinance{
wsServer: websocket.New(nil, log.SubLogger("WS")),
balances: map[string]*balance{
"eth": {
free: 1000.123432,
locked: 0,
},
"btc": {
free: 1000.21314123,
locked: 0,
},
"ltc": {
free: 1000.8689444,
locked: 0,
},
"bch": {
free: 1000.2358249,
locked: 0,
},
"dcr": {
free: 1000.2358249,
locked: 0,
},
},
}
http.HandleFunc("/sapi/v1/capital/config/getall", f.handleWalletCoinsReq)

return http.ListenAndServe(":37346", nil)
}

type balance struct {
free float64
locked float64
}

type fakeBinance struct {
wsServer *websocket.Server

balanceMtx sync.RWMutex
balances map[string]*balance
}

func (f *fakeBinance) handleWalletCoinsReq(w http.ResponseWriter, r *http.Request) {
ci := f.coinInfo()
writeJSONWithStatus(w, ci, http.StatusOK)
}

type fakeBinanceNetworkInfo struct {
Coin string `json:"coin"`
MinConfirm int `json:"minConfirm"`
Network string `json:"network"`
UnLockConfirm int `json:"unLockConfirm"`
WithdrawEnable bool `json:"withdrawEnable"`
WithdrawFee string `json:"withdrawFee"`
WithdrawIntegerMultiple string `json:"withdrawIntegerMultiple"`
WithdrawMax string `json:"withdrawMax"`
WithdrawMin string `json:"withdrawMin"`
}

type fakeBinanceCoinInfo struct {
Coin string `json:"coin"`
Free string `json:"free"`
Locked string `json:"locked"`
Withdrawing string `json:"withdrawing"`
NetworkList []*fakeBinanceNetworkInfo `json:"networkList"`
}

func (f *fakeBinance) coinInfo() (coins []*fakeBinanceCoinInfo) {
f.balanceMtx.Lock()
for symbol, bal := range f.balances {
bigSymbol := strings.ToUpper(symbol)
coins = append(coins, &fakeBinanceCoinInfo{
Coin: bigSymbol,
Free: strconv.FormatFloat(bal.free, 'f', 8, 64),
Locked: strconv.FormatFloat(bal.locked, 'f', 8, 64),
Withdrawing: "0",
NetworkList: []*fakeBinanceNetworkInfo{
{
Coin: bigSymbol,
Network: bigSymbol,
MinConfirm: 1,
WithdrawEnable: true,
WithdrawFee: strconv.FormatFloat(0.00000800, 'f', 8, 64),
WithdrawIntegerMultiple: strconv.FormatFloat(0.00000001, 'f', 8, 64),
WithdrawMax: strconv.FormatFloat(1000, 'f', 8, 64),
WithdrawMin: strconv.FormatFloat(0.01, 'f', 8, 64),
},
},
})
}
f.balanceMtx.Unlock()
return
}

// writeJSON marshals the provided interface and writes the bytes to the
// ResponseWriter with the specified response code.
func writeJSONWithStatus(w http.ResponseWriter, thing interface{}, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
b, err := json.Marshal(thing)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Errorf("JSON encode error: %v", err)
return
}
w.WriteHeader(code)
_, err = w.Write(append(b, byte('\n')))
if err != nil {
log.Errorf("Write error: %v", err)
}
}
14 changes: 8 additions & 6 deletions client/comms/wsconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ type WsCfg struct {
// RawHandler overrides the msgjson parsing and forwards all messages to
// the provided function.
RawHandler func([]byte)

ConnectHeaders http.Header
}

// wsConn represents a client websocket connection.
Expand Down Expand Up @@ -231,7 +233,7 @@ func (conn *wsConn) connect(ctx context.Context) error {
dialer.Proxy = http.ProxyFromEnvironment
}

ws, _, err := dialer.DialContext(ctx, conn.cfg.URL, nil)
ws, _, err := dialer.DialContext(ctx, conn.cfg.URL, conn.cfg.ConnectHeaders)
if err != nil {
if isErrorInvalidCert(err) {
conn.setConnectionStatus(InvalidCert)
Expand Down Expand Up @@ -291,7 +293,6 @@ func (conn *wsConn) connect(ctx context.Context) error {
} else {
conn.read(ctx)
}

}()

return nil
Expand Down Expand Up @@ -357,17 +358,17 @@ func (conn *wsConn) close() {

func (conn *wsConn) readRaw(ctx context.Context) {
for {
if ctx.Err() != nil {
return
}

// Lock since conn.ws may be set by connect.
conn.wsMtx.Lock()
ws := conn.ws
conn.wsMtx.Unlock()

// Block until a message is received or an error occurs.
_, msgBytes, err := ws.ReadMessage()
// Drop the read error on context cancellation.
if ctx.Err() != nil {
return
}
if err != nil {
conn.handleReadError(err)
return
Expand Down Expand Up @@ -601,6 +602,7 @@ func (conn *wsConn) Request(msg *msgjson.Message, f func(*msgjson.Message)) erro
// For example, to wait on a response or timeout:
//
// errChan := make(chan error, 1)
//
// err := conn.RequestWithTimeout(reqMsg, func(msg *msgjson.Message) {
// errChan <- msg.UnmarshalResult(responseStructPointer)
// }, timeout, func() {
Expand Down
1 change: 0 additions & 1 deletion client/core/trade.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,6 @@ func (t *trackedTrade) coreOrderInternal() *Order {
counterConfs, int64(t.metaData.ToSwapConf),
int64(mt.redemptionConfs), int64(mt.redemptionConfsReq)))
}

corder.AllFeesConfirmed = allFeesConfirmed

return corder
Expand Down
17 changes: 11 additions & 6 deletions client/mm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,23 @@ import (
type MarketMakingWithCEXConfig struct {
}

// ArbitrageConfig is the configuration for an arbitrage bot that only places
// when there is a profitable arbitrage opportunity.
type ArbitrageConfig struct {
}

type BalanceType uint8

const (
Percentage BalanceType = iota
Amount
)

// CEXConfig is a configuration for connecting to a CEX API.
type CEXConfig struct {
// CEXName is the name of the cex.
CEXName string `json:"cexName"`
// APIKey is the API key for the CEX.
APIKey string `json:"apiKey"`
// APISecret is the API secret for the CEX.
APISecret string `json:"apiSecret"`
}

// BotConfig is the configuration for a market making bot.
// The balance fields are the initial amounts that will be reserved to use for
// this bot. As the bot trades, the amounts reserved for it will be updated.
Expand All @@ -40,7 +45,7 @@ type BotConfig struct {
// Only one of the following configs should be set
MMCfg *MarketMakingConfig `json:"marketMakingConfig,omitempty"`
MMWithCEXCfg *MarketMakingWithCEXConfig `json:"marketMakingWithCEXConfig,omitempty"`
ArbCfg *ArbitrageConfig `json:"arbitrageConfig,omitempty"`
ArbCfg *SimpleArbConfig `json:"arbConfig,omitempty"`

Disabled bool `json:"disabled"`
}
Expand Down
Loading