├── .gitignore ├── README.md ├── clean ├── conf └── config_sample.json ├── etc ├── broker.thrift └── trader_2016_06_29.sql ├── install ├── lib └── src │ └── trade_service │ ├── GoUnusedProtection__.go │ ├── broker-consts.go │ ├── broker.go │ └── trade_service-remote │ └── trade_service-remote.go └── src ├── broker └── main.go ├── chbtc ├── api.go ├── api_test.go ├── marketAPI.go └── tradeAPI.go ├── common └── common.go ├── config ├── config.go ├── env.go └── env_test.go ├── db ├── account.go ├── amount_config.go ├── db.go ├── depth.go ├── exchange_config.go ├── site_order.go ├── ticker.go ├── trade_order.go └── tx.go ├── haobtc ├── api.go ├── api_test.go ├── marketAPI.go └── tradeAPI.go ├── huobi ├── api.go ├── api_test.go ├── marketAPI.go └── tradeapi.go ├── logger └── logger.go ├── mocks ├── TradeAPI.go ├── depth_test.go ├── fund_test.go ├── setup.go ├── tradebuy_test.go └── tradesell_test.go ├── okcoin ├── api.go ├── api_test.go ├── marketAPI.go └── tradeAPI.go ├── strategy ├── amount_limit.go ├── dbtask.go ├── dispatch.go ├── exchange.go ├── handler.go ├── match.go ├── orderbook.go ├── price_risk.go ├── processor.go ├── ticker_timer.go ├── time_weighted.go ├── trade_center.go └── trader_timer.go ├── trade_client ├── trade_client.go └── trade_client.py ├── trade_server ├── trade_handler.go └── trade_server.go └── util ├── buffer.go ├── convert.go ├── crypto.go ├── httpclient.go ├── httprequest.go ├── map.go ├── mapsort.go ├── string.go ├── tool.go ├── utils.go └── validate.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | *.bak 4 | *DS_Store 5 | local_config.py 6 | local_options.py 7 | local_settings.py 8 | certs 9 | .DS_Store 10 | dbdump 11 | dump 12 | tmp 13 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 14 | *.o 15 | *.a 16 | *.so 17 | 18 | # Folders 19 | _obj 20 | _test 21 | 22 | # Architecture specific extensions/prefixes 23 | *.[568vq] 24 | [568vq].out 25 | 26 | *.cgo1.go 27 | *.cgo2.c 28 | _cgo_defun.c 29 | _cgo_gotypes.go 30 | _cgo_export.* 31 | 32 | _testmain.go 33 | 34 | .idea 35 | *.exe 36 | bin 37 | log 38 | pkg 39 | thirdparty 40 | conf/config.json 41 | djhaobtc/static 42 | media 43 | djhaobtc/media 44 | Messenger/ios/Messenger.xcodeproj/project.xcworkspace/xcuserdata/* 45 | 46 | # OSX 47 | # 48 | .DS_Store 49 | 50 | # Xcode 51 | # 52 | build/ 53 | *.pbxuser 54 | !default.pbxuser 55 | *.mode1v3 56 | !default.mode1v3 57 | *.mode2v3 58 | !default.mode2v3 59 | *.perspectivev3 60 | !default.perspectivev3 61 | xcuserdata 62 | *.xccheckout 63 | *.moved-aside 64 | DerivedData 65 | *.hmap 66 | *.ipa 67 | *.xcuserstate 68 | project.xcworkspace 69 | 70 | # node.js 71 | # 72 | node_modules/ 73 | npm-debug.log 74 | *.map 75 | 76 | .pgpass 77 | mysql2pgsql.yml 78 | 79 | db.sqlite3 80 | djhaobtc/common/static/release/ 81 | 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## BTC trading market broker 2 | 3 | 作为经纪商角色,提供thrift标准服务接口。 4 | 5 | 提供合并交易所深度的标准价格模型, 6 | 大并发订单内部自动撮合冲销, 7 | 智能自动路由订单并拆大单为小单到不同交易所, 8 | 失败订单自动重试处理。 9 | 10 | 支持haobtc,okcoin,huobi,chbtc等交易所。 11 | 12 | # 本地搭建 # 13 | 14 | 1、安装golang开发运行环境 15 | 16 | http://golang.org/doc/install 17 | 18 | 2、下载安装依赖库并编译 broker 19 | 20 | ./install 21 | 22 | 3、导入数据库表结构 23 | 24 | 导入etc目录下的*.sql文件到PostgresDB 25 | 26 | 4、修改配置 27 | 28 | 修改conf/config_sample.json 为 conf/config.json 29 | 30 | 5、运行 broker 31 | 32 | ./bin/broker 33 | 34 | 一切顺利的话,broker应该就启动了。 35 | -------------------------------------------------------------------------------- /clean: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ ! -f clean ]; then 6 | echo 'clean must be run within its container folder' 1>&2 7 | exit 1 8 | fi 9 | 10 | CURDIR=`pwd` 11 | OLDGOPATH="$GOPATH" 12 | export GOPATH="$CURDIR:$CURDIR/thirdparty:$CURDIR/lib" 13 | 14 | go clean -i -r trader 15 | 16 | rm -rf pid 17 | rm -rf bin 18 | rm -rf pkg 19 | rm -rf log 20 | rm -rf thirdparty 21 | rm -rf lib/pkg 22 | 23 | 24 | export GOPATH="$OLDGOPATH" 25 | export PATH="$OLDPATH" 26 | 27 | echo 'finished' -------------------------------------------------------------------------------- /conf/config_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "ch_api_url":"https://trade.chbtc.com/api/", 3 | "ch_ticker_url": "http://api.chbtc.com/data/ticker", 4 | "ch_depth_url": "http://api.chbtc.com/data/depth", 5 | 6 | "hb_api_url": "https://api.huobi.com/apiv3", 7 | "hb_ticker_url": "http://api.huobi.com/staticmarket/ticker_%s_json.js", 8 | "hb_depth_url": "http://api.huobi.com/staticmarket/depth_%s_%d.js", 9 | 10 | "ok_api_userinfo":"https://www.okcoin.cn/api/v1/userinfo.do", 11 | "ok_api_order_info":"https://www.okcoin.cn/api/v1/order_info.do", 12 | "ok_api_cancelorder": "https://www.okcoin.cn/api/v1/cancel_order.do", 13 | "ok_api_trade":"https://www.okcoin.cn/api/v1/trade.do", 14 | "ok_depth_url":"https://www.okcoin.cn/api/v1/depth.do?symbol=%s&size=%d", 15 | "ok_ticker_url":"https://www.okcoin.cn/api/v1/ticker.do?symbol=%s", 16 | 17 | "haobtc_api_userinfo":"https://api.haobtc.com/exchange/api/v1/account_info", 18 | "haobtc_api_order_info":"https://api.haobtc.com/exchange/api/v1/order_info", 19 | "haobtc_api_cancelorder": "https://api.haobtc.com/exchange/api/v1/cancel_order", 20 | "haobtc_api_trade":"https://api.haobtc.com/exchange/api/v1/trade", 21 | "haobtc_depth_url":"https://api.haobtc.com/exchange/api/v1/depth?size=%d", 22 | "haobtc_ticker_url":"https://api.haobtc.com/exchange/api/v1/ticker", 23 | 24 | "loglevel": "nodebug", 25 | "env": "product", 26 | "symbol": "btc_cny", 27 | "sqlconn" : "postgres://root:root@localhost/broker?sslmode=disable", 28 | "thriftserver": "127.0.0.1:18030", 29 | "unit_min_amount" : "0.01", 30 | "unit_max_amount" : "5", 31 | "buy_queue_amount" : "200", 32 | "sell_queue_amount" : "200", 33 | "price_threshold":"3", 34 | "ticker_compare_count": "3" 35 | } 36 | -------------------------------------------------------------------------------- /etc/broker.thrift: -------------------------------------------------------------------------------- 1 | 2 | namespace go trade_service 3 | namespace py trade_service 4 | 5 | const string EX_INTERNAL_ERROR = "trader internal error"; 6 | const string EX_PRICE_OUT_OF_SCOPE = "price is out of scope"; 7 | const string EX_NO_USABLE_FUND = "no usable exchange fund by now"; 8 | const string EX_NO_USABLE_DEPTH = "no usable exchange depth by now"; 9 | const string EX_TRADE_QUEUE_FULL = "trade queue is full"; 10 | const string EX_DEPTH_INSUFFICIENT = "exchange depth is insufficient"; 11 | const string EX_PRICE_NOT_SYNC = "price is not in tickers"; 12 | const string EX_EXIST_ERROR_ORDERS = "exist error status orders"; 13 | 14 | 15 | exception TradeException { 16 | 1: string reason, 17 | } 18 | 19 | struct ExchangeStatus { 20 | 1: bool canbuy, 21 | 2: bool cansell, 22 | } 23 | 24 | struct ExchangeConfig{ 25 | 1: string exchange, 26 | 2: string access_key, 27 | 3: string secret_key, 28 | } 29 | 30 | struct AmountConfig{ 31 | 1: double max_cny, 32 | 2: double max_btc, 33 | } 34 | 35 | struct Account { 36 | 1: string exchange , 37 | 2: double available_cny , 38 | 3: double available_btc , 39 | 4: double frozen_cny , 40 | 5: double frozen_btc , 41 | 6: bool pause_trade, 42 | } 43 | 44 | struct Ticker { 45 | 1: double ask, 46 | 2: double bid, 47 | } 48 | 49 | struct Trade{ 50 | 1: string client_id, 51 | 2: double amount, 52 | 3: double price, 53 | } 54 | 55 | 56 | enum TradeType{ 57 | BUY, 58 | SELL, 59 | } 60 | 61 | enum OrderStatus{ 62 | TIME_WEIGHTED, 63 | SPLIT, 64 | READY, 65 | ORDERED, 66 | SUCCESS, 67 | ERROR, 68 | CANCELED, 69 | MATCH 70 | } 71 | 72 | 73 | struct TradeOrder { 74 | 1:i64 id 75 | 2:i64 site_order_id 76 | 3:string exchange 77 | 4:double price 78 | 5:TradeType trade_type 79 | 6:OrderStatus order_status, 80 | 7:double estimate_cny 81 | 8:double estimate_btc 82 | 9:double estimate_price 83 | 10:double deal_cny 84 | 11:double deal_btc 85 | 12:double deal_price 86 | 13:double price_margin 87 | 14:string order_id 88 | 15:string created 89 | 16:string update_at 90 | 17:i64 try_times 91 | 18:string info 92 | 19:string memo 93 | 20:i64 match_id 94 | } 95 | 96 | service TradeService { 97 | oneway void ping(), 98 | oneway void config_keys(1: list exchange_configs), 99 | oneway void config_amount(1: AmountConfig amount_config), 100 | ExchangeStatus get_exchange_status(), 101 | void check_price(1:double price, 2:TradeType trade_type) throws (1:TradeException tradeException), 102 | void buy(1:Trade trade) throws (1:TradeException tradeException), 103 | void sell(1:Trade trade) throws (1:TradeException tradeException), 104 | Ticker get_ticker(), 105 | list get_account(), 106 | void get_alert_orders() throws (1:TradeException tradeException) //client will retry every minute, then send email/sms 107 | } 108 | //save all depth 109 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ ! -f install ]; then 6 | echo 'install must be run within its container folder' 1>&2 7 | exit 1 8 | fi 9 | 10 | CURDIR=`pwd` 11 | OLDGOPATH="$GOPATH" 12 | export GOPATH="$CURDIR/thirdparty" 13 | 14 | echo 'install gpcode...' 15 | go get github.com/nsf/gocode 16 | echo 'install testify...' 17 | go get github.com/stretchr/testify 18 | echo 'install simplejson...' 19 | go get github.com/bitly/go-simplejson 20 | echo 'install pg...' 21 | go get github.com/lib/pq 22 | echo 'install gorm...' 23 | go get github.com/jinzhu/gorm 24 | echo 'install thrift...' 25 | go get github.com/apache/thrift/lib/go/thrift 26 | 27 | echo 'install logrus...' 28 | go get github.com/sirupsen/logrus 29 | echo 'install lfshook...' 30 | go get github.com/rifflock/lfshook 31 | 32 | # echo 'generate thrift...' 33 | thrift -r -out "./lib/src" --gen go:thrift_import=github.com/apache/thrift/lib/go/thrift ./etc/broker.thrift 34 | # thrift -r -out "../djhaobtc/broker/" --gen py ../etc/thrift/broker.thrift 35 | 36 | export GOPATH="$CURDIR:$CURDIR/thirdparty:$CURDIR/lib" 37 | 38 | rm -rf lib/pkg 39 | echo 'install broker...' 40 | go install broker 41 | 42 | # mockery -output="./src/mocks" -name=TradeAPI 43 | 44 | gofmt -w src 45 | 46 | if [ ! -d log ]; then 47 | mkdir log 48 | fi 49 | 50 | export GOPATH="$OLDGOPATH" 51 | export PATH="$OLDPATH" 52 | 53 | echo 'finished' 54 | -------------------------------------------------------------------------------- /lib/src/trade_service/GoUnusedProtection__.go: -------------------------------------------------------------------------------- 1 | // Autogenerated by Thrift Compiler (0.10.0) 2 | // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 3 | 4 | package trade_service 5 | 6 | var GoUnusedProtection__ int; 7 | 8 | -------------------------------------------------------------------------------- /lib/src/trade_service/broker-consts.go: -------------------------------------------------------------------------------- 1 | // Autogenerated by Thrift Compiler (0.10.0) 2 | // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 3 | 4 | package trade_service 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "github.com/apache/thrift/lib/go/thrift" 10 | ) 11 | 12 | // (needed to ensure safety because of naive import list construction.) 13 | var _ = thrift.ZERO 14 | var _ = fmt.Printf 15 | var _ = bytes.Equal 16 | 17 | const EX_INTERNAL_ERROR = "trader internal error" 18 | const EX_PRICE_OUT_OF_SCOPE = "price is out of scope" 19 | const EX_NO_USABLE_FUND = "no usable exchange fund by now" 20 | const EX_NO_USABLE_DEPTH = "no usable exchange depth by now" 21 | const EX_TRADE_QUEUE_FULL = "trade queue is full" 22 | const EX_DEPTH_INSUFFICIENT = "exchange depth is insufficient" 23 | const EX_PRICE_NOT_SYNC = "price is not in tickers" 24 | const EX_EXIST_ERROR_ORDERS = "exist error status orders" 25 | 26 | func init() { 27 | } 28 | 29 | -------------------------------------------------------------------------------- /lib/src/trade_service/trade_service-remote/trade_service-remote.go: -------------------------------------------------------------------------------- 1 | // Autogenerated by Thrift Compiler (0.10.0) 2 | // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "math" 10 | "net" 11 | "net/url" 12 | "os" 13 | "strconv" 14 | "strings" 15 | "github.com/apache/thrift/lib/go/thrift" 16 | "trade_service" 17 | ) 18 | 19 | 20 | func Usage() { 21 | fmt.Fprintln(os.Stderr, "Usage of ", os.Args[0], " [-h host:port] [-u url] [-f[ramed]] function [arg1 [arg2...]]:") 22 | flag.PrintDefaults() 23 | fmt.Fprintln(os.Stderr, "\nFunctions:") 24 | fmt.Fprintln(os.Stderr, " void ping()") 25 | fmt.Fprintln(os.Stderr, " void config_keys( exchange_configs)") 26 | fmt.Fprintln(os.Stderr, " void config_amount(AmountConfig amount_config)") 27 | fmt.Fprintln(os.Stderr, " ExchangeStatus get_exchange_status()") 28 | fmt.Fprintln(os.Stderr, " void check_price(double price, TradeType trade_type)") 29 | fmt.Fprintln(os.Stderr, " void buy(Trade trade)") 30 | fmt.Fprintln(os.Stderr, " void sell(Trade trade)") 31 | fmt.Fprintln(os.Stderr, " Ticker get_ticker()") 32 | fmt.Fprintln(os.Stderr, " get_account()") 33 | fmt.Fprintln(os.Stderr, " void get_alert_orders()") 34 | fmt.Fprintln(os.Stderr) 35 | os.Exit(0) 36 | } 37 | 38 | func main() { 39 | flag.Usage = Usage 40 | var host string 41 | var port int 42 | var protocol string 43 | var urlString string 44 | var framed bool 45 | var useHttp bool 46 | var parsedUrl url.URL 47 | var trans thrift.TTransport 48 | _ = strconv.Atoi 49 | _ = math.Abs 50 | flag.Usage = Usage 51 | flag.StringVar(&host, "h", "localhost", "Specify host and port") 52 | flag.IntVar(&port, "p", 9090, "Specify port") 53 | flag.StringVar(&protocol, "P", "binary", "Specify the protocol (binary, compact, simplejson, json)") 54 | flag.StringVar(&urlString, "u", "", "Specify the url") 55 | flag.BoolVar(&framed, "framed", false, "Use framed transport") 56 | flag.BoolVar(&useHttp, "http", false, "Use http") 57 | flag.Parse() 58 | 59 | if len(urlString) > 0 { 60 | parsedUrl, err := url.Parse(urlString) 61 | if err != nil { 62 | fmt.Fprintln(os.Stderr, "Error parsing URL: ", err) 63 | flag.Usage() 64 | } 65 | host = parsedUrl.Host 66 | useHttp = len(parsedUrl.Scheme) <= 0 || parsedUrl.Scheme == "http" 67 | } else if useHttp { 68 | _, err := url.Parse(fmt.Sprint("http://", host, ":", port)) 69 | if err != nil { 70 | fmt.Fprintln(os.Stderr, "Error parsing URL: ", err) 71 | flag.Usage() 72 | } 73 | } 74 | 75 | cmd := flag.Arg(0) 76 | var err error 77 | if useHttp { 78 | trans, err = thrift.NewTHttpClient(parsedUrl.String()) 79 | } else { 80 | portStr := fmt.Sprint(port) 81 | if strings.Contains(host, ":") { 82 | host, portStr, err = net.SplitHostPort(host) 83 | if err != nil { 84 | fmt.Fprintln(os.Stderr, "error with host:", err) 85 | os.Exit(1) 86 | } 87 | } 88 | trans, err = thrift.NewTSocket(net.JoinHostPort(host, portStr)) 89 | if err != nil { 90 | fmt.Fprintln(os.Stderr, "error resolving address:", err) 91 | os.Exit(1) 92 | } 93 | if framed { 94 | trans = thrift.NewTFramedTransport(trans) 95 | } 96 | } 97 | if err != nil { 98 | fmt.Fprintln(os.Stderr, "Error creating transport", err) 99 | os.Exit(1) 100 | } 101 | defer trans.Close() 102 | var protocolFactory thrift.TProtocolFactory 103 | switch protocol { 104 | case "compact": 105 | protocolFactory = thrift.NewTCompactProtocolFactory() 106 | break 107 | case "simplejson": 108 | protocolFactory = thrift.NewTSimpleJSONProtocolFactory() 109 | break 110 | case "json": 111 | protocolFactory = thrift.NewTJSONProtocolFactory() 112 | break 113 | case "binary", "": 114 | protocolFactory = thrift.NewTBinaryProtocolFactoryDefault() 115 | break 116 | default: 117 | fmt.Fprintln(os.Stderr, "Invalid protocol specified: ", protocol) 118 | Usage() 119 | os.Exit(1) 120 | } 121 | client := trade_service.NewTradeServiceClientFactory(trans, protocolFactory) 122 | if err := trans.Open(); err != nil { 123 | fmt.Fprintln(os.Stderr, "Error opening socket to ", host, ":", port, " ", err) 124 | os.Exit(1) 125 | } 126 | 127 | switch cmd { 128 | case "ping": 129 | if flag.NArg() - 1 != 0 { 130 | fmt.Fprintln(os.Stderr, "Ping requires 0 args") 131 | flag.Usage() 132 | } 133 | fmt.Print(client.Ping()) 134 | fmt.Print("\n") 135 | break 136 | case "config_keys": 137 | if flag.NArg() - 1 != 1 { 138 | fmt.Fprintln(os.Stderr, "ConfigKeys requires 1 args") 139 | flag.Usage() 140 | } 141 | arg18 := flag.Arg(1) 142 | mbTrans19 := thrift.NewTMemoryBufferLen(len(arg18)) 143 | defer mbTrans19.Close() 144 | _, err20 := mbTrans19.WriteString(arg18) 145 | if err20 != nil { 146 | Usage() 147 | return 148 | } 149 | factory21 := thrift.NewTSimpleJSONProtocolFactory() 150 | jsProt22 := factory21.GetProtocol(mbTrans19) 151 | containerStruct0 := trade_service.NewTradeServiceConfigKeysArgs() 152 | err23 := containerStruct0.ReadField1(jsProt22) 153 | if err23 != nil { 154 | Usage() 155 | return 156 | } 157 | argvalue0 := containerStruct0.ExchangeConfigs 158 | value0 := argvalue0 159 | fmt.Print(client.ConfigKeys(value0)) 160 | fmt.Print("\n") 161 | break 162 | case "config_amount": 163 | if flag.NArg() - 1 != 1 { 164 | fmt.Fprintln(os.Stderr, "ConfigAmount requires 1 args") 165 | flag.Usage() 166 | } 167 | arg24 := flag.Arg(1) 168 | mbTrans25 := thrift.NewTMemoryBufferLen(len(arg24)) 169 | defer mbTrans25.Close() 170 | _, err26 := mbTrans25.WriteString(arg24) 171 | if err26 != nil { 172 | Usage() 173 | return 174 | } 175 | factory27 := thrift.NewTSimpleJSONProtocolFactory() 176 | jsProt28 := factory27.GetProtocol(mbTrans25) 177 | argvalue0 := trade_service.NewAmountConfig() 178 | err29 := argvalue0.Read(jsProt28) 179 | if err29 != nil { 180 | Usage() 181 | return 182 | } 183 | value0 := argvalue0 184 | fmt.Print(client.ConfigAmount(value0)) 185 | fmt.Print("\n") 186 | break 187 | case "get_exchange_status": 188 | if flag.NArg() - 1 != 0 { 189 | fmt.Fprintln(os.Stderr, "GetExchangeStatus requires 0 args") 190 | flag.Usage() 191 | } 192 | fmt.Print(client.GetExchangeStatus()) 193 | fmt.Print("\n") 194 | break 195 | case "check_price": 196 | if flag.NArg() - 1 != 2 { 197 | fmt.Fprintln(os.Stderr, "CheckPrice requires 2 args") 198 | flag.Usage() 199 | } 200 | argvalue0, err30 := (strconv.ParseFloat(flag.Arg(1), 64)) 201 | if err30 != nil { 202 | Usage() 203 | return 204 | } 205 | value0 := argvalue0 206 | tmp1, err := (strconv.Atoi(flag.Arg(2))) 207 | if err != nil { 208 | Usage() 209 | return 210 | } 211 | argvalue1 := trade_service.TradeType(tmp1) 212 | value1 := argvalue1 213 | fmt.Print(client.CheckPrice(value0, value1)) 214 | fmt.Print("\n") 215 | break 216 | case "buy": 217 | if flag.NArg() - 1 != 1 { 218 | fmt.Fprintln(os.Stderr, "Buy requires 1 args") 219 | flag.Usage() 220 | } 221 | arg31 := flag.Arg(1) 222 | mbTrans32 := thrift.NewTMemoryBufferLen(len(arg31)) 223 | defer mbTrans32.Close() 224 | _, err33 := mbTrans32.WriteString(arg31) 225 | if err33 != nil { 226 | Usage() 227 | return 228 | } 229 | factory34 := thrift.NewTSimpleJSONProtocolFactory() 230 | jsProt35 := factory34.GetProtocol(mbTrans32) 231 | argvalue0 := trade_service.NewTrade() 232 | err36 := argvalue0.Read(jsProt35) 233 | if err36 != nil { 234 | Usage() 235 | return 236 | } 237 | value0 := argvalue0 238 | fmt.Print(client.Buy(value0)) 239 | fmt.Print("\n") 240 | break 241 | case "sell": 242 | if flag.NArg() - 1 != 1 { 243 | fmt.Fprintln(os.Stderr, "Sell requires 1 args") 244 | flag.Usage() 245 | } 246 | arg37 := flag.Arg(1) 247 | mbTrans38 := thrift.NewTMemoryBufferLen(len(arg37)) 248 | defer mbTrans38.Close() 249 | _, err39 := mbTrans38.WriteString(arg37) 250 | if err39 != nil { 251 | Usage() 252 | return 253 | } 254 | factory40 := thrift.NewTSimpleJSONProtocolFactory() 255 | jsProt41 := factory40.GetProtocol(mbTrans38) 256 | argvalue0 := trade_service.NewTrade() 257 | err42 := argvalue0.Read(jsProt41) 258 | if err42 != nil { 259 | Usage() 260 | return 261 | } 262 | value0 := argvalue0 263 | fmt.Print(client.Sell(value0)) 264 | fmt.Print("\n") 265 | break 266 | case "get_ticker": 267 | if flag.NArg() - 1 != 0 { 268 | fmt.Fprintln(os.Stderr, "GetTicker requires 0 args") 269 | flag.Usage() 270 | } 271 | fmt.Print(client.GetTicker()) 272 | fmt.Print("\n") 273 | break 274 | case "get_account": 275 | if flag.NArg() - 1 != 0 { 276 | fmt.Fprintln(os.Stderr, "GetAccount requires 0 args") 277 | flag.Usage() 278 | } 279 | fmt.Print(client.GetAccount()) 280 | fmt.Print("\n") 281 | break 282 | case "get_alert_orders": 283 | if flag.NArg() - 1 != 0 { 284 | fmt.Fprintln(os.Stderr, "GetAlertOrders requires 0 args") 285 | flag.Usage() 286 | } 287 | fmt.Print(client.GetAlertOrders()) 288 | fmt.Print("\n") 289 | break 290 | case "": 291 | Usage() 292 | break 293 | default: 294 | fmt.Fprintln(os.Stderr, "Invalid function ", cmd) 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/broker/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package main 6 | 7 | import ( 8 | "config" 9 | "logger" 10 | "math/rand" 11 | "runtime" 12 | "strategy" 13 | "time" 14 | "trade_server" 15 | ) 16 | 17 | func init() { 18 | runtime.GOMAXPROCS(runtime.NumCPU()) 19 | // 设置随机数种子 20 | rand.Seed(time.Now().Unix()) 21 | } 22 | 23 | func main() { 24 | config.LoadConfig() 25 | 26 | printBanner() 27 | 28 | strategy.TradeCenter() 29 | 30 | thriftserver := config.Config["thriftserver"] 31 | server := trade_server.NewTraderServer(thriftserver) 32 | server.Run() 33 | } 34 | 35 | func printBanner() { 36 | version := "V1.1 postgres" 37 | logger.Infoln("[ ---------------------------------------------------------->>> ") 38 | logger.Infoln(" trading broker Engine", version) 39 | logger.Infoln(" <<<----------------------------------------------------------] ") 40 | } 41 | -------------------------------------------------------------------------------- /src/chbtc/api.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package chbtc 6 | 7 | import ( 8 | "common" 9 | "config" 10 | "logger" 11 | "util" 12 | ) 13 | 14 | type Chbtc struct { 15 | tradeAPI *ChbtcTrade 16 | name string 17 | } 18 | 19 | func NewExchange(name, access_key, secret_key string) *Chbtc { 20 | w := new(Chbtc) 21 | w.tradeAPI = NewChbtcTrade(access_key, secret_key) 22 | return w 23 | } 24 | 25 | func (w Chbtc) GetTicker() (ticker common.Ticker, err error) { 26 | symbol := config.Config["symbol"] 27 | if symbol == "btc_cny" { 28 | symbol = "btc" 29 | } else { 30 | symbol = "ltc" 31 | panic(-1) 32 | } 33 | 34 | chTicker, err := w.getTicker(symbol) 35 | if err != nil { 36 | return 37 | } 38 | 39 | ticker.Date = "" 40 | ticker.Ticker.Buy = util.ToFloat(chTicker.Ticker.Buy) 41 | ticker.Ticker.High = util.ToFloat(chTicker.Ticker.High) 42 | ticker.Ticker.Last = util.ToFloat(chTicker.Ticker.Last) 43 | ticker.Ticker.Low = util.ToFloat(chTicker.Ticker.Low) 44 | ticker.Ticker.Sell = util.ToFloat(chTicker.Ticker.Sell) 45 | ticker.Ticker.Vol = util.ToFloat(chTicker.Ticker.Vol) 46 | return 47 | } 48 | 49 | func (w Chbtc) GetDepth() (orderBook common.OrderBook, err error) { 50 | symbol := config.Config["symbol"] 51 | if symbol == "btc_cny" { 52 | symbol = "btc" 53 | } else { 54 | symbol = "ltc" 55 | panic(-1) 56 | } 57 | 58 | return w.getDepth(symbol) 59 | } 60 | 61 | func (w Chbtc) Buy(price, btc string) (buyId string, result string, err error) { 62 | tradeAPI := w.tradeAPI 63 | 64 | // symbol := config.Config["symbol"] 65 | return tradeAPI.doTrade("buy", price, btc) 66 | } 67 | 68 | func (w Chbtc) Sell(price, btc string) (sellId string, result string, err error) { 69 | tradeAPI := w.tradeAPI 70 | 71 | // symbol := config.Config["symbol"] 72 | return tradeAPI.doTrade("sell", price, btc) 73 | } 74 | 75 | func (w Chbtc) BuyMarket(amount string) (buyId string, result string, err error) { 76 | panic("exchange does not support") 77 | } 78 | 79 | func (w Chbtc) SellMarket(amount string) (sellId string, result string, err error) { 80 | panic("exchange does not support") 81 | } 82 | 83 | func (w Chbtc) GetOrder(order_id string) (order common.Order, result string, err error) { 84 | tradeAPI := w.tradeAPI 85 | 86 | symbol := config.Config["symbol"] 87 | if symbol == "btc_cny" { 88 | symbol = "1" 89 | } else if symbol == "ltc_cny" { 90 | symbol = "0" 91 | } else { 92 | panic(-1) 93 | } 94 | 95 | return tradeAPI.Get_order(symbol, order_id) 96 | } 97 | 98 | func (w Chbtc) CancelOrder(order_id string) (err error) { 99 | tradeAPI := w.tradeAPI 100 | 101 | symbol := config.Config["symbol"] 102 | if symbol == "btc_cny" { 103 | symbol = "1" 104 | } else if symbol == "ltc_cny" { 105 | symbol = "0" 106 | } else { 107 | panic(-1) 108 | } 109 | return tradeAPI.Cancel_order(symbol, order_id) 110 | } 111 | 112 | func (w Chbtc) GetAccount() (account common.Account, err error) { 113 | tradeAPI := w.tradeAPI 114 | 115 | account, err = tradeAPI.GetAccount() 116 | if err != nil { 117 | logger.Debugln("Chbtc GetAccount failed") 118 | return 119 | } 120 | 121 | return 122 | } 123 | 124 | func (w *Chbtc) Withdraw(amount, fees, receiveAddr, safePwd string) (id string, err error) { 125 | tradeAPI := w.tradeAPI 126 | return tradeAPI.withdraw(amount, fees, receiveAddr, safePwd) 127 | } 128 | 129 | func (w *Chbtc) CancelWithdraw(downloadId, safePwd string) (err error) { 130 | tradeAPI := w.tradeAPI 131 | return tradeAPI.cancelWithdraw(downloadId, safePwd) 132 | } 133 | -------------------------------------------------------------------------------- /src/chbtc/api_test.go: -------------------------------------------------------------------------------- 1 | package chbtc 2 | 3 | import ( 4 | "config" 5 | "logger" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | const ( 11 | Chbtc_access_key string = "" 12 | Chbtc_secret_key string = "" 13 | ) 14 | 15 | func setup() { 16 | config.Env = config.Test 17 | config.Root = "/Users/phil/dev/work/haobtc/trader" 18 | config.LoadConfig() 19 | 20 | //strategy.Task() 21 | } 22 | 23 | func Test_API(t *testing.T) { 24 | setup() 25 | 26 | api := NewExchange(Chbtc_access_key, Chbtc_secret_key) 27 | buyId, result, err := api.Buy("1100", "0.011") 28 | logger.Infoln(buyId, result, err) 29 | 30 | order, result, err := api.GetOrder(buyId) 31 | logger.Infoln(order, result, err) 32 | 33 | err = api.CancelOrder(order.Id) 34 | logger.Infoln(err) 35 | 36 | order, result, err = api.GetOrder(buyId) 37 | logger.Infoln(order, result, err) 38 | 39 | // sellId, result, err := api.Sell("2100", "0.012") 40 | // logger.Infoln(sellId, result, err) 41 | 42 | // order, result, err = api.GetOrder(sellId) 43 | // logger.Infoln(order, result, err) 44 | } 45 | 46 | func TaskTemplate(seconds time.Duration, f func()) { 47 | ticker := time.NewTicker(seconds * time.Second) // one second 48 | defer ticker.Stop() 49 | 50 | for _ = range ticker.C { 51 | f() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/chbtc/marketAPI.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package chbtc 6 | 7 | import ( 8 | . "common" 9 | . "config" 10 | "encoding/json" 11 | "errors" 12 | "logger" 13 | "util" 14 | ) 15 | 16 | type ChTicker struct { 17 | Ticker ChTickerPrice 18 | } 19 | 20 | type ChTickerPrice struct { 21 | Buy string 22 | High string 23 | Last string 24 | Low string 25 | Sell string 26 | Vol string 27 | } 28 | 29 | func (w *Chbtc) getTicker(symbol string) (ticker ChTicker, err error) { 30 | ticker_url := Config["ch_ticker_url"] 31 | body, err := util.HttpGet(ticker_url) 32 | if err != nil { 33 | logger.Errorln(err) 34 | return 35 | } 36 | 37 | if err = json.Unmarshal([]byte(body), &ticker); err != nil { 38 | logger.Infoln(err) 39 | return 40 | } 41 | 42 | return 43 | } 44 | 45 | func (w *Chbtc) getDepth(symbol string) (orderBook OrderBook, err error) { 46 | depth_url := Config["ch_depth_url"] 47 | body, err := util.HttpGet(depth_url) 48 | if err != nil { 49 | logger.Errorln(err) 50 | return 51 | } 52 | 53 | defaultstruct := make(map[string]interface{}) 54 | err = json.Unmarshal([]byte(body), &defaultstruct) 55 | if err != nil { 56 | logger.Infoln("defaultstruct", defaultstruct) 57 | return 58 | } 59 | 60 | asks := defaultstruct["asks"].([]interface{}) 61 | bids := defaultstruct["bids"].([]interface{}) 62 | 63 | for i, ask := range asks { 64 | _ask := ask.([]interface{}) 65 | price, ret := _ask[0].(float64) 66 | if !ret { 67 | err = errors.New("data wrong") 68 | return orderBook, err 69 | } 70 | amount, ret := _ask[1].(float64) 71 | if !ret { 72 | err = errors.New("data wrong") 73 | return orderBook, err 74 | } 75 | order := MarketOrder{ 76 | Price: price, 77 | Amount: amount, 78 | } 79 | if i < DEPTH { 80 | orderBook.Asks[i] = order 81 | } else { 82 | break 83 | } 84 | } 85 | 86 | for i, bid := range bids { 87 | _bid := bid.([]interface{}) 88 | price, ret := _bid[0].(float64) 89 | if !ret { 90 | err = errors.New("data wrong") 91 | return orderBook, err 92 | } 93 | amount, ret := _bid[1].(float64) 94 | if !ret { 95 | err = errors.New("data wrong") 96 | return orderBook, err 97 | } 98 | order := MarketOrder{ 99 | Price: price, 100 | Amount: amount, 101 | } 102 | 103 | if i < DEPTH { 104 | orderBook.Bids[i] = order 105 | } else { 106 | break 107 | } 108 | } 109 | 110 | return 111 | } 112 | -------------------------------------------------------------------------------- /src/chbtc/tradeAPI.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package chbtc 6 | 7 | import ( 8 | "common" 9 | . "config" 10 | "crypto/hmac" 11 | "crypto/md5" 12 | "crypto/sha1" 13 | "encoding/hex" 14 | "errors" 15 | "fmt" 16 | "logger" 17 | "strconv" 18 | "time" 19 | "util" 20 | 21 | . "github.com/bitly/go-simplejson" 22 | ) 23 | 24 | /* 25 | https://vip.chbtc.com/u/api 26 | */ 27 | type ChbtcTrade struct { 28 | access_key string 29 | secret_key string 30 | } 31 | 32 | func NewChbtcTrade(access_key, secret_key string) *ChbtcTrade { 33 | w := new(ChbtcTrade) 34 | w.access_key = access_key 35 | w.secret_key = secret_key 36 | return w 37 | } 38 | 39 | func (w *ChbtcTrade) createSign(method, otherParams string) string { 40 | to_sign_str := "method=" + method + "&accesskey=" + w.access_key + otherParams 41 | 42 | secret_key := sha1.Sum([]byte(w.secret_key)) 43 | secret_key_hexstr := hex.EncodeToString(secret_key[:]) 44 | 45 | hmacMD5 := hmac.New(md5.New, []byte(secret_key_hexstr)) 46 | hmacMD5.Write([]byte(to_sign_str)) 47 | expectedMAC := hmacMD5.Sum(nil) 48 | 49 | sign := hex.EncodeToString(expectedMAC) 50 | 51 | now := time.Now().Unix() * 1000 52 | req_para := to_sign_str + "&sign=" + sign + "&reqTime=" + strconv.FormatInt(now, 10) 53 | 54 | return req_para 55 | } 56 | 57 | type ErrorMsg struct { 58 | Code int 59 | Message string 60 | } 61 | 62 | func (w *ChbtcTrade) check_json_result(body string) (errorMsg ErrorMsg, ret bool) { 63 | //dummy function 64 | ret = true 65 | return 66 | } 67 | 68 | func (w *ChbtcTrade) GetAccount() (account common.Account, err error) { 69 | method := "getAccountInfo" 70 | 71 | req_para := w.createSign(method, "") 72 | 73 | body, err := util.HttpPost(Config["ch_api_url"]+method, req_para) 74 | if err != nil { 75 | return 76 | } 77 | 78 | js, err := NewJson([]byte(body)) 79 | if err != nil { 80 | return 81 | } 82 | 83 | if js, ret := js.CheckGet("result"); ret { 84 | account.Available_cny = js.Get("balance").Get("CNY").Get("amount").MustFloat64() 85 | account.Available_btc = js.Get("balance").Get("BTC").Get("amount").MustFloat64() 86 | account.Available_ltc = js.Get("balance").Get("LTC").Get("amount").MustFloat64() 87 | account.Frozen_cny = js.Get("frozen").Get("CNY").Get("amount").MustFloat64() 88 | account.Frozen_btc = js.Get("frozen").Get("BTC").Get("amount").MustFloat64() 89 | account.Frozen_ltc = js.Get("frozen").Get("LTC").Get("amount").MustFloat64() 90 | } 91 | 92 | return 93 | } 94 | 95 | func (w *ChbtcTrade) _doTrade(tradeType, price, amount string) (string, string, error) { 96 | method := "order" 97 | otherParams := "&price=" + price + "&amount=" + amount + "&tradeType=" + tradeType + "¤cy=btc" 98 | req_para := w.createSign(method, otherParams) 99 | body, err := util.HttpPost(Config["ch_api_url"]+method, req_para) 100 | if err != nil { 101 | return "", "", err 102 | } 103 | 104 | js, err := NewJson([]byte(body)) 105 | if err != nil { 106 | return "", "", err 107 | } 108 | 109 | if jscode, ret := js.CheckGet("code"); ret { 110 | code := jscode.MustInt() 111 | 112 | if code == 1000 { 113 | return js.Get("id").MustString(), "", nil 114 | } else { 115 | result := string(body) 116 | return "", result, nil 117 | } 118 | } 119 | 120 | err = errors.New(string(body)) 121 | return "", "", err 122 | } 123 | 124 | func (w *ChbtcTrade) doTrade(tradeType, price, amount string) (string, string, error) { 125 | var _tradeType string 126 | if tradeType == "buy" { 127 | _tradeType = "1" 128 | } else if tradeType == "sell" { 129 | _tradeType = "0" 130 | } else { 131 | panic(-1) 132 | } 133 | 134 | return w._doTrade(_tradeType, price, amount) 135 | } 136 | 137 | func (w *ChbtcTrade) Get_order(cointype string, id string) (order common.Order, result string, err error) { 138 | method := "getOrder" 139 | otherParams := "&id=" + id + "¤cy=btc" 140 | 141 | req_para := w.createSign(method, otherParams) 142 | body, err := util.HttpPost(Config["ch_api_url"]+method, req_para) 143 | if err != nil { 144 | logger.Errorln(err) 145 | return 146 | } 147 | 148 | result = string(body) 149 | js, err := NewJson([]byte(body)) 150 | if err != nil { 151 | logger.Errorln(err) 152 | return 153 | } 154 | 155 | if jsid, ret := js.CheckGet("id"); ret { 156 | order.Id = jsid.MustString() 157 | // order.Price = js.Get("price").MustFloat64() 158 | order.Amount = js.Get("total_amount").MustFloat64() 159 | order.Deal_amount = js.Get("trade_amount").MustFloat64() 160 | if order.Deal_amount < 0.000001 { 161 | order.Price = js.Get("price").MustFloat64() 162 | } else { 163 | trade_money := js.Get("trade_money").MustFloat64() 164 | order.Price = trade_money / order.Deal_amount 165 | } 166 | 167 | status := js.Get("status").MustInt() 168 | 169 | switch status { 170 | case 0, 3: 171 | order.Status = common.ORDER_STATE_PENDING 172 | case 2: 173 | order.Status = common.ORDER_STATE_SUCCESS 174 | case 1: 175 | order.Status = common.ORDER_STATE_CANCELED 176 | default: 177 | order.Status = common.ORDER_STATE_UNKNOWN 178 | } 179 | return 180 | } else if jscode, ret := js.CheckGet("code"); ret { 181 | jscode := jscode.MustInt() 182 | if jscode == 3001 { 183 | order.Status = common.ORDER_STATE_ERROR 184 | return 185 | } 186 | } else { 187 | err = errors.New("Get_order failed") 188 | return 189 | } 190 | 191 | return 192 | } 193 | 194 | func (w *ChbtcTrade) Cancel_order(cointype string, id string) (err error) { 195 | method := "cancelOrder" 196 | otherParams := "&id=" + id + "¤cy=btc" 197 | 198 | req_para := w.createSign(method, otherParams) 199 | fmt.Println(req_para) 200 | body, err := util.HttpPost(Config["ch_api_url"]+method, req_para) 201 | if err != nil { 202 | return 203 | } 204 | 205 | js, err := NewJson([]byte(body)) 206 | if err != nil { 207 | return 208 | } 209 | 210 | if jscode, ret := js.CheckGet("code"); ret { 211 | code := jscode.MustInt() 212 | 213 | if code == 1000 { 214 | return nil 215 | } else { 216 | logger.Errorln("code=", code) 217 | return errors.New("Cancel_order failed") 218 | } 219 | } else { 220 | return errors.New("Cancel_order failed") 221 | } 222 | } 223 | 224 | func (w *ChbtcTrade) withdraw(amount, fees, receiveAddr, safePwd string) (id string, err error) { 225 | method := "withdraw" 226 | otherParams := "¤cy=btc_cny&fees=" + fees + "&amount=" + amount + "&receiveAddr=" + receiveAddr + "&safePwd=" + safePwd 227 | 228 | req_para := w.createSign(method, otherParams) 229 | fmt.Println(req_para) 230 | body, err := util.HttpPost(Config["ch_api_url"]+method, req_para) 231 | if err != nil { 232 | return 233 | } 234 | 235 | js, err := NewJson([]byte(body)) 236 | if err != nil { 237 | return 238 | } 239 | logger.Infoln(js) 240 | logger.Errorln(err) 241 | 242 | if jscode, ret := js.CheckGet("code"); ret { 243 | code := jscode.MustInt() 244 | 245 | if code == 1000 { 246 | return js.Get("id").MustString(), nil 247 | } else { 248 | logger.Errorln("code=", code) 249 | return "", errors.New("withdraw failed") 250 | } 251 | } else { 252 | return "", errors.New("withdraw failed") 253 | } 254 | } 255 | 256 | func (w *ChbtcTrade) cancelWithdraw(downloadId, safePwd string) (err error) { 257 | method := "cancelWithdraw" 258 | otherParams := "¤cy=btc_cny&downloadId=" + downloadId + "&safePwd=" + safePwd 259 | 260 | req_para := w.createSign(method, otherParams) 261 | fmt.Println(req_para) 262 | body, err := util.HttpPost(Config["ch_api_url"]+method, req_para) 263 | if err != nil { 264 | return 265 | } 266 | 267 | js, err := NewJson([]byte(body)) 268 | if err != nil { 269 | return 270 | } 271 | logger.Infoln(js) 272 | logger.Errorln(err) 273 | if jscode, ret := js.CheckGet("code"); ret { 274 | code := jscode.MustInt() 275 | 276 | if code == 1000 { 277 | return nil 278 | } else { 279 | logger.Errorln("code=", code) 280 | return errors.New("cancelWithdraw failed") 281 | } 282 | } else { 283 | return errors.New("cancelWithdraw failed") 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const DEPTH = 150 4 | 5 | // trade interface type and method 6 | type Account struct { 7 | Available_cny float64 8 | Available_btc float64 9 | Available_ltc float64 10 | Frozen_cny float64 11 | Frozen_btc float64 12 | Frozen_ltc float64 13 | } 14 | 15 | type Ticker struct { 16 | Date string 17 | Ticker TickerPrice 18 | } 19 | 20 | type TickerPrice struct { 21 | Buy float64 22 | High float64 23 | Last float64 24 | Low float64 25 | Sell float64 26 | Vol float64 27 | } 28 | 29 | type MarketOrder struct { 30 | Price float64 // 价格 31 | Amount float64 // 委单量 32 | } 33 | 34 | // price from high to low: asks[0] > .....>asks[DEPTH] > bids[0] > ......> bids[DEPTH] 35 | type OrderBook struct { 36 | Asks [DEPTH]MarketOrder // sell 37 | Bids [DEPTH]MarketOrder // buy 38 | } 39 | 40 | const ( 41 | ORDER_STATE_PENDING string = "PENDING" 42 | ORDER_STATE_SUCCESS string = "SUCCESS" 43 | ORDER_STATE_CANCELED string = "CANCELED" 44 | ORDER_STATE_ERROR string = "ERROR" 45 | ORDER_STATE_UNKNOWN string = "UNKNOWN" 46 | ) 47 | 48 | type Order struct { 49 | Id string 50 | Price float64 51 | Amount float64 //因为火币在buy order中返回的与ok,chbtc不同,我们common统一处理为比特币金额,注意:坑!ok的买单,amount返回为空 52 | Deal_amount float64 //因为火币在buy order中返回的与ok,chbtc不同,我们common统一处理为比特币成交金额 53 | Status string 54 | } 55 | 56 | type TradeAPI interface { 57 | GetTicker() (Ticker, error) 58 | GetAccount() (Account, error) 59 | GetDepth() (OrderBook, error) 60 | Buy(price, amount string) (order_id string, result string, err error) 61 | Sell(price, amount string) (order_id string, result string, err error) 62 | 63 | BuyMarket(amount string) (order_id string, result string, err error) 64 | SellMarket(amount string) (order_id string, result string, err error) 65 | GetOrder(order_id string) (order Order, result string, err error) 66 | CancelOrder(order_id string) error 67 | } 68 | 69 | ////////////////// 70 | // below for test 71 | ////////////////// 72 | var g_MockObjs map[string]TradeAPI = make(map[string]TradeAPI) 73 | 74 | func GetMockTradeAPI(exchange string) TradeAPI { 75 | return g_MockObjs[exchange] 76 | } 77 | 78 | func SetMockTradeAPI(exchange string, mockObj TradeAPI) { 79 | g_MockObjs[exchange] = mockObj 80 | } 81 | -------------------------------------------------------------------------------- /src/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package config 6 | 7 | import ( 8 | "encoding/json" 9 | "io/ioutil" 10 | ) 11 | 12 | var Config map[string]string 13 | 14 | func get_config_path(file string) (filepath string) { 15 | //fmt.Println("config file:", Root + file) 16 | return Root + file 17 | } 18 | 19 | func load_config(file string) (config map[string]string, err error) { 20 | // Load 全局配置文件 21 | configFile := get_config_path(file) 22 | 23 | content, err := ioutil.ReadFile(configFile) 24 | if err != nil { 25 | return nil, err 26 | } 27 | config = make(map[string]string) 28 | err = json.Unmarshal(content, &config) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return config, nil 34 | } 35 | 36 | func LoadConfig() (err error) { 37 | Config, err = load_config("/conf/config.json") 38 | if err != nil { 39 | panic("load config file failed") 40 | return 41 | } 42 | 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /src/config/env.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package config 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | ) 11 | 12 | // Envs 13 | const ( 14 | Dev string = "development" 15 | Prod string = "production" 16 | Test string = "test" 17 | ) 18 | 19 | // Env is the environment that Martini is executing in. The MARTINI_ENV is read on initialization to set this variable. 20 | var Env = Dev 21 | var Root string 22 | 23 | func setENV(e string) { 24 | if len(e) > 0 { 25 | Env = e 26 | } 27 | } 28 | 29 | func init() { 30 | var err error 31 | Root, err = os.Getwd() 32 | fmt.Println("Root:", Root) 33 | if err != nil { 34 | panic(err) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/config/env_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package config 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func Test_SetENV(t *testing.T) { 12 | tests := []struct { 13 | in string 14 | out string 15 | }{ 16 | {"", "development"}, 17 | {"not_development", "not_development"}, 18 | } 19 | 20 | for _, test := range tests { 21 | setENV(test.in) 22 | if Env != test.out { 23 | expect(t, Env, test.out) 24 | } 25 | } 26 | } 27 | 28 | func Test_Root(t *testing.T) { 29 | if len(Root) == 0 { 30 | t.Errorf("Expected root path will be set") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/db/account.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "logger" 6 | "time" 7 | "trade_service" 8 | ) 9 | 10 | type Account struct { 11 | gorm.Model 12 | trade_service.Account 13 | } 14 | 15 | func SetAccount(account *trade_service.Account) (err error) { 16 | db, err := getORMDB() 17 | if err != nil { 18 | logger.Errorln(err) 19 | return 20 | } 21 | 22 | var db_account Account 23 | if err := db.Where("exchange = ?", account.Exchange).First(&db_account).Error; err != nil { 24 | logger.Errorln("not found:", err) 25 | db_account.Exchange = account.Exchange 26 | db_account.AvailableCny = account.AvailableCny 27 | db_account.AvailableBtc = account.AvailableBtc 28 | db_account.FrozenCny = account.FrozenCny 29 | db_account.FrozenBtc = account.FrozenBtc 30 | 31 | if err := db.Create(&db_account).Error; err != nil { 32 | logger.Errorln(err) 33 | return err 34 | } 35 | } else { 36 | db_account.CreatedAt = time.Now() 37 | db_account.AvailableCny = account.AvailableCny 38 | db_account.AvailableBtc = account.AvailableBtc 39 | db_account.FrozenCny = account.FrozenCny 40 | db_account.FrozenBtc = account.FrozenBtc 41 | if err := db.Save(&db_account).Error; err != nil { 42 | logger.Errorln(err) 43 | return err 44 | } 45 | } 46 | 47 | logger.Debugln("set account ok", account) 48 | return 49 | } 50 | 51 | func GetAccount() (accounts []*trade_service.Account, err error) { 52 | db, err := getORMDB() 53 | if err != nil { 54 | logger.Errorln(err) 55 | return 56 | } 57 | 58 | var db_accounts []Account 59 | 60 | if err = db.Order("id").Find(&db_accounts).Error; err != nil { 61 | logger.Errorln("GetAccount err:", err) 62 | return 63 | } 64 | 65 | for _, db_account := range db_accounts { 66 | account := new(trade_service.Account) 67 | account.Exchange = db_account.Exchange 68 | account.AvailableCny = db_account.AvailableCny 69 | account.AvailableBtc = db_account.AvailableBtc 70 | account.FrozenCny = db_account.FrozenCny 71 | account.FrozenBtc = db_account.FrozenBtc 72 | account.PauseTrade = db_account.PauseTrade 73 | 74 | accounts = append(accounts, account) 75 | } 76 | 77 | return 78 | } 79 | -------------------------------------------------------------------------------- /src/db/amount_config.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "logger" 6 | "time" 7 | "trade_service" 8 | ) 9 | 10 | type AmountConfig struct { 11 | gorm.Model 12 | trade_service.AmountConfig 13 | } 14 | 15 | var g_amount_config *trade_service.AmountConfig 16 | 17 | func SetAmountConfig(amount_config *trade_service.AmountConfig) (err error) { 18 | g_amount_config = amount_config 19 | return setAmountConfig(amount_config) 20 | } 21 | 22 | func GetAmountConfig() (amount_config *trade_service.AmountConfig, err error) { 23 | if g_amount_config != nil { 24 | return g_amount_config, nil 25 | } 26 | 27 | amount_config, err = getAmountConfig() 28 | if err == nil { 29 | g_amount_config = amount_config 30 | } 31 | 32 | return 33 | } 34 | 35 | func setAmountConfig(amount_config *trade_service.AmountConfig) (err error) { 36 | db, err := getORMDB() 37 | if err != nil { 38 | logger.Errorln(err) 39 | return 40 | } 41 | 42 | tx := db.Begin() 43 | 44 | var amountConfig AmountConfig 45 | 46 | if err := tx.First(&amountConfig).Error; err != nil { 47 | logger.Errorln("setAmountConfig amount record does not exist:", err) 48 | // return err 49 | } 50 | 51 | amountConfig.CreatedAt = time.Now() 52 | amountConfig.MaxCny = amount_config.MaxCny 53 | amountConfig.MaxBtc = amount_config.MaxBtc 54 | 55 | if err := tx.Save(&amountConfig).Error; err != nil { 56 | tx.Rollback() 57 | logger.Errorln(err) 58 | return err 59 | } 60 | 61 | tx.Commit() 62 | 63 | logger.Infoln("set amountConfig ok", amountConfig) 64 | 65 | return 66 | } 67 | 68 | func getAmountConfig() (amount_config *trade_service.AmountConfig, err error) { 69 | db, err := getORMDB() 70 | if err != nil { 71 | logger.Errorln(err) 72 | return 73 | } 74 | 75 | var amountConfig AmountConfig 76 | 77 | if err = db.First(&amountConfig).Error; err != nil { 78 | logger.Errorln("getAmountConfig amount record does not exist:", err) 79 | return 80 | } 81 | 82 | amount_config = trade_service.NewAmountConfig() 83 | amount_config.MaxCny = amountConfig.MaxCny 84 | amount_config.MaxBtc = amountConfig.MaxBtc 85 | 86 | logger.Infoln("get amountConfig ok", amount_config) 87 | 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /src/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | _ "github.com/jinzhu/gorm/dialects/postgres" 6 | "log" 7 | "logger" 8 | "os" 9 | "sync" 10 | ) 11 | 12 | var mutex = &sync.Mutex{} 13 | 14 | var sqlconn string 15 | var g_ormDB *gorm.DB 16 | 17 | func Init_sqlstr(_sqlconn string) { 18 | sqlconn = _sqlconn 19 | } 20 | 21 | func GetDB() (db *gorm.DB, err error) { 22 | return getORMDB() 23 | } 24 | 25 | func getORMDB() (*gorm.DB, error) { 26 | if g_ormDB != nil { 27 | return g_ormDB, nil 28 | } 29 | 30 | mutex.Lock() 31 | defer mutex.Unlock() 32 | 33 | //check again 34 | if g_ormDB != nil { 35 | return g_ormDB, nil 36 | } 37 | 38 | db, err := open_db_orm() 39 | if err == nil { 40 | g_ormDB = db 41 | } 42 | 43 | return g_ormDB, err 44 | } 45 | 46 | func open_db_orm() (db *gorm.DB, err error) { 47 | db, err = gorm.Open("postgres", sqlconn) 48 | if err != nil { 49 | logger.Errorln("open:", err) 50 | return 51 | } 52 | // Get database connection handle [*sql.DB](http://golang.org/pkg/database/sql/#DB) 53 | db.DB() 54 | 55 | // Then you could invoke `*sql.DB`'s functions with it 56 | err = db.DB().Ping() 57 | if err != nil { 58 | logger.Errorln("ping:", err) 59 | return 60 | } 61 | 62 | db.DB().SetMaxIdleConns(10) 63 | db.DB().SetMaxOpenConns(100) 64 | 65 | // Disable table name's pluralization 66 | db.SingularTable(true) 67 | db.LogMode(false) 68 | db.SetLogger(log.New(os.Stdout, "\r\n", 0)) 69 | 70 | // migrate(db) 71 | 72 | return 73 | } 74 | 75 | func migrate(db *gorm.DB) { 76 | // Automating Migration 77 | db.AutoMigrate(&AmountConfig{}, &Ticker{}, &Depth{}) 78 | 79 | db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&AmountConfig{}) 80 | } 81 | -------------------------------------------------------------------------------- /src/db/depth.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "common" 5 | "encoding/json" 6 | "errors" 7 | "github.com/jinzhu/gorm" 8 | "logger" 9 | "time" 10 | ) 11 | 12 | type Depth struct { 13 | gorm.Model 14 | 15 | Exchange string 16 | Orderbook string `gorm:"type:longtext;"` 17 | } 18 | 19 | func SetDepth(exchange string, orderbook *common.OrderBook) (err error) { 20 | jsonOrderbook, err := json.Marshal(orderbook) 21 | if err != nil { 22 | logger.Errorln("Marshal fail", err) 23 | return 24 | } 25 | 26 | db, err := getORMDB() 27 | if err != nil { 28 | logger.Errorln(err) 29 | return 30 | } 31 | 32 | var depth Depth 33 | depth.Exchange = exchange 34 | depth.Orderbook = string(jsonOrderbook) 35 | 36 | if err := db.Create(&depth).Error; err != nil { 37 | logger.Errorln("SetDepth err:", err) 38 | return err 39 | } 40 | 41 | return 42 | } 43 | 44 | func GetDepth(exchange string) (orderbook common.OrderBook, err error) { 45 | db, err := getORMDB() 46 | if err != nil { 47 | logger.Errorln(err) 48 | return 49 | } 50 | 51 | var depth Depth 52 | 53 | if err = db.Where("exchange = ?", exchange).Last(&depth).Error; err != nil { 54 | logger.Errorln("GetDepth Last err:", err, exchange) 55 | return 56 | } 57 | 58 | now_time := time.Now() 59 | diff := now_time.Sub(depth.CreatedAt).Seconds() 60 | 61 | logger.Debugln("time:", diff, depth.CreatedAt.Format(time.RFC3339), now_time.Format(time.RFC3339)) 62 | if diff > 15 { 63 | err = errors.New("last depth falls behind 15 seconds") 64 | logger.Errorln(exchange, err, diff, depth.CreatedAt.Format(time.RFC3339), now_time.Format(time.RFC3339)) 65 | return 66 | } 67 | 68 | if err = json.Unmarshal([]byte(depth.Orderbook), &orderbook); err != nil { 69 | logger.Errorln("Unmarshal fail", err) 70 | return 71 | } 72 | 73 | logger.Debugln("GetDepth:", depth.ID, depth.CreatedAt) 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /src/db/exchange_config.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "logger" 5 | "trade_service" 6 | ) 7 | 8 | func SetExchangeConfig(exchange_configs []*trade_service.ExchangeConfig) (err error) { 9 | db, err := getORMDB() 10 | if err != nil { 11 | logger.Errorln(err) 12 | return 13 | } 14 | 15 | tx := db.Begin() 16 | 17 | var db_exchange_config trade_service.ExchangeConfig 18 | if err := tx.Delete(&db_exchange_config).Error; err != nil { 19 | logger.Errorln("_setExchangeConfig delete all err:", err) 20 | tx.Rollback() 21 | return err 22 | } 23 | 24 | for i := 0; i < len(exchange_configs); i++ { 25 | exchange_config := exchange_configs[i] 26 | if err := tx.Create(&exchange_config).Error; err != nil { 27 | logger.Errorln("_setExchangeConfig save err:", err) 28 | tx.Rollback() 29 | return err 30 | } 31 | } 32 | 33 | tx.Commit() 34 | return nil 35 | } 36 | 37 | func GetExchangeConfig(exchange string) (exchange_config trade_service.ExchangeConfig, err error) { 38 | db, err := getORMDB() 39 | if err != nil { 40 | logger.Errorln(err) 41 | return 42 | } 43 | 44 | if err = db.Where("exchange = ?", exchange).First(&exchange_config).Error; err != nil { 45 | logger.Errorln("GetExchangeConfig Find err:", err, exchange) 46 | return 47 | } 48 | 49 | return 50 | } 51 | 52 | func GetExchangeConfigs() (exchange_configs []trade_service.ExchangeConfig, err error) { 53 | db, err := getORMDB() 54 | if err != nil { 55 | logger.Errorln(err) 56 | return 57 | } 58 | 59 | if err = db.Find(&exchange_configs).Error; err != nil { 60 | logger.Errorln("GetExchangeConfigs err:", err) 61 | return 62 | } 63 | 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /src/db/site_order.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "logger" 6 | "time" 7 | "trade_service" 8 | ) 9 | 10 | type SiteOrder struct { 11 | ID int64 `json:"id"` 12 | ClientID string `json:"client_id"` 13 | TradeType trade_service.TradeType `json:"trade_type"` 14 | OrderStatus trade_service.OrderStatus `json:"order_status"` 15 | Amount float64 `json:"amount"` 16 | Price float64 `json:"price"` 17 | EstimatePrice float64 `json:"estimate_price"` 18 | EstimateCny float64 `json:"estimate_cny"` 19 | EstimateBtc float64 `json:"estimate_btc"` 20 | Created time.Time `json:"created"` 21 | } 22 | 23 | type DBSiteOrder struct { 24 | ID int64 `json:"id"` 25 | ClientID string `json:"client_id"` 26 | TradeType string `json:"trade_type"` 27 | OrderStatus string `json:"order_status"` 28 | Amount float64 `json:"amount"` 29 | Price float64 `json:"price"` 30 | EstimatePrice float64 `json:"estimate_price"` 31 | EstimateCny float64 `json:"estimate_cny"` 32 | EstimateBtc float64 `json:"estimate_btc"` 33 | Created time.Time `json:"created"` 34 | } 35 | 36 | func (DBSiteOrder) TableName() string { 37 | return "site_order" 38 | } 39 | 40 | func InsertOrder(tx *gorm.DB, order *SiteOrder) (order_id int64, err error) { 41 | dbSiteOrder := _convert_to_db_site_order(order) 42 | if err = tx.Create(&dbSiteOrder).Error; err != nil { 43 | logger.Errorln("InsertOrder err:", err) 44 | return 45 | } 46 | 47 | logger.Infoln("InsertOrder ok", dbSiteOrder) 48 | 49 | order.ID = dbSiteOrder.ID 50 | 51 | return order.ID, nil 52 | } 53 | 54 | func GetOrderByClientID(tx *gorm.DB, client_id string) (orders []SiteOrder, err error) { 55 | var dbSiteOrders []DBSiteOrder 56 | if err = tx.Where("client_id = ?", client_id).Find(&dbSiteOrders).Error; err != nil { 57 | logger.Errorln("GetOrderByClientID err:", err, client_id) 58 | return 59 | } 60 | 61 | return convert_to_site_orders(dbSiteOrders) 62 | } 63 | 64 | func GetOrdersByStatus(tx *gorm.DB, orderStatus trade_service.OrderStatus) (orders []SiteOrder, err error) { 65 | var dbSiteOrders []DBSiteOrder 66 | if err = tx.Where("order_status = ?", orderStatus.String()).Find(&orders).Error; err != nil { 67 | logger.Errorln("GetOrdersByStatus err:", err, orderStatus.String()) 68 | return 69 | } 70 | 71 | return convert_to_site_orders(dbSiteOrders) 72 | } 73 | 74 | //remove ID and Created 75 | func _convert_to_db_site_order(order *SiteOrder) (dbSiteOrder DBSiteOrder) { 76 | dbSiteOrder.ClientID = order.ClientID 77 | dbSiteOrder.TradeType = order.TradeType.String() 78 | dbSiteOrder.OrderStatus = order.OrderStatus.String() 79 | dbSiteOrder.Amount = order.Amount 80 | dbSiteOrder.Price = order.Price 81 | dbSiteOrder.EstimatePrice = order.EstimatePrice 82 | dbSiteOrder.EstimateCny = order.EstimateCny 83 | dbSiteOrder.EstimateBtc = order.EstimateBtc 84 | dbSiteOrder.Created = time.Now() 85 | 86 | return 87 | } 88 | 89 | func convert_to_site_order(dbSiteOrder DBSiteOrder) (order SiteOrder, err error) { 90 | order.ID = dbSiteOrder.ID 91 | order.ClientID = dbSiteOrder.ClientID 92 | order.TradeType, err = trade_service.TradeTypeFromString(dbSiteOrder.TradeType) 93 | if err != nil { 94 | logger.Errorln("TradeType panic", err) 95 | return 96 | } 97 | order.OrderStatus, err = trade_service.OrderStatusFromString(dbSiteOrder.OrderStatus) 98 | if err != nil { 99 | logger.Errorln("OrderStatus panic", err) 100 | return 101 | } 102 | order.Amount = dbSiteOrder.Amount 103 | order.Price = dbSiteOrder.Price 104 | order.EstimatePrice = dbSiteOrder.EstimatePrice 105 | order.EstimateCny = dbSiteOrder.EstimateCny 106 | order.EstimateBtc = dbSiteOrder.EstimateBtc 107 | order.Created = dbSiteOrder.Created 108 | 109 | return 110 | } 111 | 112 | func convert_to_site_orders(dbSiteOrders []DBSiteOrder) (orders []SiteOrder, err error) { 113 | for _, dbSiteOrder := range dbSiteOrders { 114 | var order SiteOrder 115 | order, err = convert_to_site_order(dbSiteOrder) 116 | if err != nil { 117 | logger.Errorln("convert_to_site_order err", err) 118 | return 119 | } 120 | 121 | orders = append(orders, order) 122 | } 123 | 124 | return 125 | } 126 | -------------------------------------------------------------------------------- /src/db/ticker.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "logger" 6 | "trade_service" 7 | ) 8 | 9 | type Ticker struct { 10 | gorm.Model 11 | Ask float64 `gorm:"type:decimal(65,2);"` 12 | Bid float64 `gorm:"type:decimal(65,2);"` 13 | } 14 | 15 | var g_cacheTicker *trade_service.Ticker 16 | 17 | func SetTicker(ticker *trade_service.Ticker) (err error) { 18 | g_cacheTicker = ticker 19 | 20 | db, err := getORMDB() 21 | if err != nil { 22 | logger.Errorln(err) 23 | return 24 | } 25 | 26 | var dbTicker Ticker 27 | dbTicker.Bid = ticker.GetBid() 28 | dbTicker.Ask = ticker.GetAsk() 29 | 30 | if err := db.Save(&dbTicker).Error; err != nil { 31 | logger.Errorln("SetTicker err:", err) 32 | return err 33 | } 34 | 35 | logger.Infoln("SetTicker ok", ticker) 36 | return 37 | } 38 | 39 | func GetTicker() (ticker *trade_service.Ticker, err error) { 40 | if g_cacheTicker != nil { 41 | return g_cacheTicker, nil 42 | } 43 | 44 | db, err := getORMDB() 45 | if err != nil { 46 | logger.Errorln(err) 47 | return 48 | } 49 | 50 | var dbTicker Ticker 51 | 52 | if err = db.Last(&dbTicker).Error; err != nil { 53 | logger.Errorln("GetTicker Last err:", err) 54 | return 55 | } 56 | 57 | ticker = trade_service.NewTicker() 58 | ticker.Ask = dbTicker.Ask 59 | ticker.Bid = dbTicker.Bid 60 | 61 | logger.Infoln("GetTicker ok", ticker) 62 | g_cacheTicker = ticker 63 | 64 | return 65 | } 66 | 67 | func GetNTickers(ticker_compare_count int) (tickers []Ticker, err error) { 68 | db, err := getORMDB() 69 | if err != nil { 70 | logger.Errorln(err) 71 | return 72 | } 73 | 74 | if err = db.Order("id desc").Limit(ticker_compare_count).Find(&tickers).Error; err != nil { 75 | logger.Errorln("GetNTicker err:", err) 76 | return 77 | } 78 | 79 | logger.Infoln("GetNTickers ok", tickers) 80 | 81 | return 82 | } 83 | -------------------------------------------------------------------------------- /src/db/trade_order.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "logger" 6 | "time" 7 | "trade_service" 8 | ) 9 | 10 | type DBTradeOrder struct { 11 | ID int64 `thrift:"id,1" json:"id"` 12 | SiteOrderID int64 `thrift:"site_order_id,2" json:"site_order_id"` 13 | Exchange string `thrift:"exchange,3" json:"exchange"` 14 | Price float64 `thrift:"price,4" json:"price"` 15 | TradeType string `thrift:"trade_type,5" json:"trade_type"` 16 | OrderStatus string `thrift:"order_status,6" json:"order_status"` 17 | EstimateCny float64 `thrift:"estimate_cny,7" json:"estimate_cny"` 18 | EstimateBtc float64 `thrift:"estimate_btc,8" json:"estimate_btc"` 19 | EstimatePrice float64 `thrift:"estimate_price,9" json:"estimate_price"` 20 | DealCny float64 `thrift:"deal_cny,10" json:"deal_cny"` 21 | DealBtc float64 `thrift:"deal_btc,11" json:"deal_btc"` 22 | DealPrice float64 `thrift:"deal_price,12" json:"deal_price"` 23 | PriceMargin float64 `thrift:"price_margin,13" json:"price_margin"` 24 | OrderID string `thrift:"order_id,14" json:"order_id"` 25 | Created time.Time `thrift:"created,15" json:"created"` 26 | UpdateAt time.Time `thrift:"update_at,16" json:"update_at"` 27 | TryTimes int64 `thrift:"try_times,17" json:"try_times"` 28 | Info string `thrift:"info,18" json:"info"` 29 | Memo string `thrift:"memo,19" json:"memo"` 30 | MatchID int64 `thrift:"match_id,20" json:"match_id"` 31 | } 32 | 33 | func (DBTradeOrder) TableName() string { 34 | return "trade_order" 35 | } 36 | 37 | func InsertTradeOrder(tx *gorm.DB, tradeOrder *trade_service.TradeOrder) (id int64, err error) { 38 | dbTradeOrder := convert_to_db_trade_order(true, tradeOrder) 39 | dbTradeOrder.ID = 0 40 | 41 | if err = tx.Create(&dbTradeOrder).Error; err != nil { 42 | logger.Errorln("InsertOrder err:", err) 43 | return 44 | } 45 | 46 | *tradeOrder, err = convert_to_trade_order(dbTradeOrder) 47 | if err != nil { 48 | return 49 | } 50 | 51 | return tradeOrder.ID, nil 52 | } 53 | 54 | func UpdateTradeOrder(tx *gorm.DB, tradeOrder trade_service.TradeOrder) (err error) { 55 | 56 | var dbTradeOrder DBTradeOrder 57 | if err := tx.Set("gorm:query_option", "FOR UPDATE").Where("id = ?", tradeOrder.ID).First(&dbTradeOrder).Error; err != nil { 58 | logger.Errorln("UpdateTradeOrder not found:", err) 59 | return err 60 | } 61 | 62 | newDbTradeOrder := convert_to_db_trade_order(false, &tradeOrder) 63 | 64 | if err = tx.Save(&newDbTradeOrder).Error; err != nil { 65 | logger.Errorln("UpdateTradeOrder Save err:", err, newDbTradeOrder) 66 | return 67 | } 68 | 69 | return 70 | } 71 | 72 | func GetAlertOrders() (tradeOrders []trade_service.TradeOrder, err error) { 73 | db, err := getORMDB() 74 | if err != nil { 75 | logger.Errorln(err) 76 | return 77 | } 78 | 79 | return GetAllTradeOrders(db, trade_service.OrderStatus_ERROR) 80 | } 81 | 82 | func GetTotalReadyNow() (buy_total, sell_total float64) { 83 | buy_total = 0 84 | sell_total = 0 85 | 86 | db, err := getORMDB() 87 | if err != nil { 88 | logger.Errorln(err) 89 | return 90 | } 91 | 92 | type Result struct { 93 | TradeType string 94 | SellTotal float64 95 | BuyTotal float64 96 | } 97 | 98 | var results []Result 99 | db.Table("trade_order").Select("trade_type, sum(estimate_btc) as sell_total, sum(estimate_cny) as buy_total").Group("trade_type").Where("order_status in (?)", []string{"READY", "TIME_WEIGHTED"}).Scan(&results) 100 | for _, result := range results { 101 | if result.TradeType == "BUY" { 102 | buy_total = result.BuyTotal 103 | } else if result.TradeType == "SELL" { 104 | sell_total = result.SellTotal 105 | } 106 | } 107 | 108 | return 109 | } 110 | 111 | func GetAllTradeOrders(tx *gorm.DB, order_status trade_service.OrderStatus) (tradeOrders []trade_service.TradeOrder, err error) { 112 | var dbTradeOrders []DBTradeOrder 113 | if err = tx.Set("gorm:query_option", "FOR UPDATE").Where("order_status = ?", order_status.String()).Find(&dbTradeOrders).Error; err != nil { 114 | logger.Errorln("GetAllTradeOrders err:", order_status.String(), err) 115 | return 116 | } 117 | 118 | return convert_to_trade_orders(dbTradeOrders) 119 | } 120 | 121 | func GetTradeOrders(tx *gorm.DB, exchange string, order_status trade_service.OrderStatus) (tradeOrders []trade_service.TradeOrder, err error) { 122 | var dbTradeOrders []DBTradeOrder 123 | if err = tx.Set("gorm:query_option", "FOR UPDATE").Where("order_status = ? AND exchange = ?", order_status.String(), exchange).Find(&dbTradeOrders).Error; err != nil { 124 | logger.Errorln("GetTradeOrders err:", order_status.String(), exchange, err) 125 | return 126 | } 127 | 128 | return convert_to_trade_orders(dbTradeOrders) 129 | } 130 | 131 | func GetFirstTradeOrder(tx *gorm.DB, exchange string, order_status trade_service.OrderStatus) (tradeOrders []trade_service.TradeOrder, err error) { 132 | var dbTradeOrders []DBTradeOrder 133 | if err = tx.Set("gorm:query_option", "FOR UPDATE").Where("order_status = ? AND exchange = ?", order_status.String(), exchange).Order("try_times desc").Limit(1).Find(&dbTradeOrders).Error; err != nil { 134 | logger.Errorln("GetFirstTradeOrder err:", order_status.String(), exchange, err) 135 | return 136 | } 137 | 138 | return convert_to_trade_orders(dbTradeOrders) 139 | } 140 | 141 | func GetTimeoutOrder(tx *gorm.DB, exchange string) (tradeOrders []trade_service.TradeOrder, err error) { 142 | var dbTradeOrders []DBTradeOrder 143 | if err = tx.Set("gorm:query_option", "FOR UPDATE").Where("order_status = 'ORDERED' AND exchange = ?", exchange).Order("update_at desc").Limit(10).Find(&dbTradeOrders).Error; err != nil { 144 | logger.Errorln("GetTimeoutOrder err:", exchange, err) 145 | return 146 | } 147 | 148 | var final_dbTradeOrders []DBTradeOrder 149 | for _, dbTradeOrder := range dbTradeOrders { 150 | now_time := time.Now() 151 | diff := now_time.Sub(dbTradeOrder.UpdateAt).Seconds() 152 | 153 | logger.Debugln("time:diff", diff, dbTradeOrder.UpdateAt.Format(time.RFC3339), now_time.Format(time.RFC3339)) 154 | if diff < 15 { 155 | break 156 | } 157 | 158 | final_dbTradeOrders = append(final_dbTradeOrders, dbTradeOrder) 159 | } 160 | 161 | logger.Debugln("final_dbTradeOrders:", final_dbTradeOrders) 162 | 163 | return convert_to_trade_orders(final_dbTradeOrders) 164 | } 165 | 166 | //remove ID and Created 167 | func convert_to_db_trade_order(isInsert bool, tradeOrder *trade_service.TradeOrder) (dbTradeOrder DBTradeOrder) { 168 | dbTradeOrder.ID = tradeOrder.ID 169 | dbTradeOrder.SiteOrderID = tradeOrder.SiteOrderID 170 | dbTradeOrder.Exchange = tradeOrder.Exchange 171 | dbTradeOrder.Price = tradeOrder.Price 172 | dbTradeOrder.TradeType = tradeOrder.TradeType.String() 173 | dbTradeOrder.OrderStatus = tradeOrder.OrderStatus.String() 174 | dbTradeOrder.EstimateCny = tradeOrder.EstimateCny 175 | dbTradeOrder.EstimateBtc = tradeOrder.EstimateBtc 176 | dbTradeOrder.EstimatePrice = tradeOrder.EstimatePrice 177 | dbTradeOrder.DealCny = tradeOrder.DealCny 178 | dbTradeOrder.DealBtc = tradeOrder.DealBtc 179 | dbTradeOrder.DealPrice = tradeOrder.DealPrice 180 | dbTradeOrder.PriceMargin = tradeOrder.PriceMargin 181 | dbTradeOrder.OrderID = tradeOrder.OrderID 182 | 183 | if isInsert { 184 | dbTradeOrder.Created = time.Now() 185 | dbTradeOrder.UpdateAt = time.Now() 186 | } else { 187 | created_at, err := time.Parse( 188 | time.RFC3339, 189 | tradeOrder.Created) 190 | if err == nil { 191 | dbTradeOrder.Created = created_at 192 | } else { 193 | logger.Errorln("Parse Created time.RFC3339 error:", tradeOrder.Created) 194 | } 195 | 196 | update_at, err := time.Parse( 197 | time.RFC3339, 198 | tradeOrder.UpdateAt) 199 | if err == nil { 200 | dbTradeOrder.UpdateAt = update_at 201 | } else { 202 | logger.Errorln("Parse UpdateAt time.RFC3339 error:", tradeOrder.UpdateAt) 203 | } 204 | } 205 | 206 | dbTradeOrder.TryTimes = tradeOrder.TryTimes 207 | dbTradeOrder.Info = tradeOrder.Info 208 | dbTradeOrder.Memo = tradeOrder.Memo 209 | dbTradeOrder.MatchID = tradeOrder.MatchID 210 | 211 | return 212 | } 213 | 214 | func convert_to_trade_order(dbTradeOrder DBTradeOrder) (tradeOrder trade_service.TradeOrder, err error) { 215 | tradeOrder.ID = dbTradeOrder.ID 216 | tradeOrder.SiteOrderID = dbTradeOrder.SiteOrderID 217 | tradeOrder.Exchange = dbTradeOrder.Exchange 218 | tradeOrder.Price = dbTradeOrder.Price 219 | tradeOrder.TradeType, err = trade_service.TradeTypeFromString(dbTradeOrder.TradeType) 220 | if err != nil { 221 | logger.Errorln("TradeType panic", err) 222 | return 223 | } 224 | tradeOrder.OrderStatus, err = trade_service.OrderStatusFromString(dbTradeOrder.OrderStatus) 225 | if err != nil { 226 | logger.Errorln("OrderStatus panic", err) 227 | return 228 | } 229 | tradeOrder.EstimateCny = dbTradeOrder.EstimateCny 230 | tradeOrder.EstimateBtc = dbTradeOrder.EstimateBtc 231 | tradeOrder.EstimatePrice = dbTradeOrder.EstimatePrice 232 | tradeOrder.DealCny = dbTradeOrder.DealCny 233 | tradeOrder.DealBtc = dbTradeOrder.DealBtc 234 | tradeOrder.DealPrice = dbTradeOrder.DealPrice 235 | tradeOrder.PriceMargin = dbTradeOrder.PriceMargin 236 | tradeOrder.OrderID = dbTradeOrder.OrderID 237 | tradeOrder.Created = dbTradeOrder.Created.Format(time.RFC3339) 238 | tradeOrder.UpdateAt = dbTradeOrder.UpdateAt.Format(time.RFC3339) 239 | tradeOrder.TryTimes = dbTradeOrder.TryTimes 240 | tradeOrder.Info = dbTradeOrder.Info 241 | tradeOrder.Memo = dbTradeOrder.Memo 242 | tradeOrder.MatchID = dbTradeOrder.MatchID 243 | return 244 | } 245 | 246 | func convert_to_trade_orders(dbTradeOrders []DBTradeOrder) (tradeOrders []trade_service.TradeOrder, err error) { 247 | for _, dbTradeOrder := range dbTradeOrders { 248 | var tradeOrder trade_service.TradeOrder 249 | tradeOrder, err = convert_to_trade_order(dbTradeOrder) 250 | if err != nil { 251 | logger.Errorln("convert_to_trade_order err", err) 252 | return 253 | } 254 | 255 | tradeOrders = append(tradeOrders, tradeOrder) 256 | } 257 | 258 | return 259 | } 260 | -------------------------------------------------------------------------------- /src/db/tx.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "logger" 6 | ) 7 | 8 | func TxBegin() (tx *gorm.DB, err error) { 9 | db_handle, err := GetDB() 10 | if err != nil { 11 | logger.Errorln(err) 12 | return 13 | } 14 | 15 | tx = db_handle.Begin() 16 | 17 | return tx, nil 18 | } 19 | 20 | func TxEnd(tx *gorm.DB, exception error) (err error) { 21 | if exception != nil { 22 | logger.Errorln(exception) 23 | tx.Rollback() 24 | return nil 25 | } else { 26 | tx.Commit() 27 | return nil 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func TXWrapper(f func(tx *gorm.DB) error) { 34 | tx, err := TxBegin() 35 | if err != nil { 36 | logger.Errorln("TxBegin failed", err) 37 | return 38 | } 39 | 40 | err = f(tx) 41 | 42 | err = TxEnd(tx, err) 43 | if err != nil { 44 | logger.Errorln("TxEnd failed", err) 45 | return 46 | } 47 | 48 | return 49 | } 50 | 51 | func TXWrapperEx(f func(tx *gorm.DB, exchange string) error, exchange string) { 52 | tx, err := TxBegin() 53 | if err != nil { 54 | logger.Errorln("TxBegin failed", err) 55 | return 56 | } 57 | 58 | err = f(tx, exchange) 59 | 60 | err = TxEnd(tx, err) 61 | if err != nil { 62 | logger.Errorln("TxEnd failed", err) 63 | return 64 | } 65 | 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /src/haobtc/api.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package haobtc 6 | 7 | import ( 8 | "common" 9 | "config" 10 | "fmt" 11 | "logger" 12 | "util" 13 | ) 14 | 15 | type Haobtc struct { 16 | tradeAPI *HaobtcTrade 17 | name string 18 | } 19 | 20 | func NewExchange(name, partner, secret_key string) *Haobtc { 21 | w := new(Haobtc) 22 | w.name = name 23 | w.tradeAPI = NewHaobtcTrade(name, partner, secret_key) 24 | return w 25 | } 26 | 27 | func (w Haobtc) GetTicker() (ticker common.Ticker, err error) { 28 | symbol := config.Config["symbol"] 29 | 30 | okTicker, err := w.getTicker(symbol) 31 | if err != nil { 32 | return 33 | } 34 | 35 | ticker.Date = okTicker.Date 36 | ticker.Ticker.Buy = util.ToFloat(okTicker.Ticker.Buy) 37 | ticker.Ticker.High = util.ToFloat(okTicker.Ticker.High) 38 | ticker.Ticker.Last = util.ToFloat(okTicker.Ticker.Last) 39 | ticker.Ticker.Low = util.ToFloat(okTicker.Ticker.Low) 40 | ticker.Ticker.Sell = util.ToFloat(okTicker.Ticker.Sell) 41 | ticker.Ticker.Vol = util.ToFloat(okTicker.Ticker.Vol) 42 | return 43 | } 44 | 45 | //get depth 46 | func (w Haobtc) GetDepth() (orderBook common.OrderBook, err error) { 47 | symbol := config.Config["symbol"] 48 | return w.getDepth(symbol) 49 | } 50 | 51 | func (w Haobtc) Buy(price, btc string) (buyId string, result string, err error) { 52 | tradeAPI := w.tradeAPI 53 | 54 | symbol := config.Config["symbol"] 55 | if symbol == "btc_cny" { 56 | return tradeAPI.BuyBTC(price, btc) 57 | } else { 58 | panic(-1) 59 | } 60 | 61 | return 62 | } 63 | 64 | func (w Haobtc) Sell(price, btc string) (sellId string, result string, err error) { 65 | tradeAPI := w.tradeAPI 66 | symbol := config.Config["symbol"] 67 | if symbol == "btc_cny" { 68 | return tradeAPI.SellBTC(price, btc) 69 | } else { 70 | panic(-1) 71 | } 72 | 73 | return 74 | } 75 | 76 | func (w Haobtc) BuyMarket(cny string) (buyId string, result string, err error) { 77 | tradeAPI := w.tradeAPI 78 | 79 | symbol := config.Config["symbol"] 80 | if symbol == "btc_cny" { 81 | return tradeAPI.BuyMarketBTC(cny) 82 | } else { 83 | panic(-1) 84 | } 85 | 86 | return 87 | } 88 | 89 | func (w Haobtc) SellMarket(btc string) (sellId string, result string, err error) { 90 | tradeAPI := w.tradeAPI 91 | symbol := config.Config["symbol"] 92 | if symbol == "btc_cny" { 93 | return tradeAPI.SellMarketBTC(btc) 94 | } else { 95 | panic(-1) 96 | } 97 | 98 | return 99 | } 100 | 101 | func (w Haobtc) GetOrder(order_id string) (order common.Order, result string, err error) { 102 | symbol := config.Config["symbol"] 103 | tradeAPI := w.tradeAPI 104 | 105 | haobtc_order, result, err := tradeAPI.Get_order(symbol, order_id) 106 | if err != nil { 107 | return 108 | } 109 | 110 | order.Id = fmt.Sprintf("%d", haobtc_order.Order_id) 111 | order.Price = haobtc_order.Avg_price 112 | order.Amount = haobtc_order.Amount 113 | order.Deal_amount = haobtc_order.Deal_size 114 | 115 | if haobtc_order.Type == "MARKET" && haobtc_order.Side == "BUY" { //haobtc的市价买单,代表买入金额,比较特殊 116 | order.Amount = 0 117 | } 118 | 119 | switch haobtc_order.Status { 120 | case "PENDING", "SUBMIT", "OPEN": 121 | order.Status = common.ORDER_STATE_PENDING 122 | case "CLOSE": 123 | order.Status = common.ORDER_STATE_SUCCESS 124 | case "CANCELED": 125 | order.Status = common.ORDER_STATE_CANCELED // treat canceled status as a error since there is not enough fund 126 | default: 127 | order.Status = common.ORDER_STATE_UNKNOWN 128 | } 129 | 130 | return 131 | } 132 | 133 | func (w Haobtc) CancelOrder(order_id string) (err error) { 134 | tradeAPI := w.tradeAPI 135 | symbol := config.Config["symbol"] 136 | return tradeAPI.Cancel_order(symbol, order_id) 137 | } 138 | 139 | func (w Haobtc) GetAccount() (account common.Account, err error) { 140 | tradeAPI := w.tradeAPI 141 | 142 | userInfo, err := tradeAPI.GetAccount() 143 | if err != nil { 144 | logger.Debugln("haobtc GetAccount failed") 145 | return 146 | } 147 | 148 | account.Available_cny = userInfo.Exchange_cny 149 | account.Available_btc = userInfo.Exchange_btc 150 | 151 | account.Frozen_cny = userInfo.Exchange_frozen_cny 152 | account.Frozen_btc = userInfo.Exchange_frozen_btc 153 | 154 | return 155 | } 156 | -------------------------------------------------------------------------------- /src/haobtc/api_test.go: -------------------------------------------------------------------------------- 1 | package haobtc 2 | 3 | import ( 4 | "config" 5 | "logger" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | const ( 11 | OKCoin_api_key string = "" 12 | OKCoin_secret_key string = "" 13 | ) 14 | 15 | func setup() { 16 | config.Env = config.Test 17 | config.Root = "/Users/phil/dev/work/haobtc/trader" 18 | config.LoadConfig() 19 | 20 | //strategy.Task() 21 | } 22 | 23 | func Test_API(t *testing.T) { 24 | setup() 25 | 26 | api := NewExchange(OKCoin_api_key, OKCoin_secret_key) 27 | 28 | if false { 29 | 30 | buyId, result, err := api.Buy("1100", "0.011") 31 | logger.Infoln(buyId, result, err) 32 | 33 | order, result, err := api.GetOrder(buyId) 34 | logger.Infoln(order, result, err) 35 | 36 | sellId, result, err := api.Sell("2100", "0.012") 37 | logger.Infoln(sellId, result, err) 38 | 39 | order, result, err = api.GetOrder(sellId) 40 | logger.Infoln(order, result, err) 41 | } 42 | 43 | if true { 44 | buyId, result, err := api.BuyMarket("30") 45 | logger.Infoln(buyId, result, err) 46 | 47 | order, result, err := api.GetOrder(buyId) 48 | logger.Infoln(order, result, err) 49 | 50 | sellId, result, err := api.SellMarket("0.013") 51 | logger.Infoln(sellId, result, err) 52 | 53 | order, result, err = api.GetOrder(sellId) 54 | logger.Infoln(order, result, err) 55 | } 56 | 57 | } 58 | 59 | func TaskTemplate(seconds time.Duration, f func()) { 60 | ticker := time.NewTicker(seconds * time.Second) // one second 61 | defer ticker.Stop() 62 | 63 | for _ = range ticker.C { 64 | f() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/haobtc/marketAPI.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package haobtc 6 | 7 | import ( 8 | . "common" 9 | . "config" 10 | "encoding/json" 11 | "fmt" 12 | "logger" 13 | "util" 14 | ) 15 | 16 | func (w *Haobtc) getTicker(symbol string) (ticker Ticker, err error) { 17 | ticker_url := fmt.Sprintf(Config[w.name+"_ticker_url"], symbol) 18 | body, err := util.HttpGet(ticker_url) 19 | if err != nil { 20 | logger.Errorln(err) 21 | return 22 | } 23 | 24 | logger.Infoln(body, err) 25 | 26 | if err = json.Unmarshal([]byte(body), &ticker); err != nil { 27 | logger.Infoln(err) 28 | return 29 | } 30 | 31 | return 32 | } 33 | 34 | func (w *Haobtc) getDepth(symbol string) (orderBook OrderBook, err error) { 35 | depth_url := fmt.Sprintf(Config[w.name+"_depth_url"], DEPTH) 36 | 37 | logger.Debugln("Haobtc", depth_url) 38 | body, err := util.HttpGet(depth_url) 39 | if err != nil { 40 | logger.Errorln(err, depth_url) 41 | return 42 | } 43 | 44 | logger.Debugln("Haobtc", depth_url, body) 45 | return w.analyzeOrderBook(body) 46 | } 47 | 48 | type _OKOrderBook struct { 49 | Asks [DEPTH]interface{} 50 | Bids [DEPTH]interface{} 51 | } 52 | 53 | func convert2struct(_okOrderBook _OKOrderBook) (okOrderBook OrderBook) { 54 | for k, v := range _okOrderBook.Asks { 55 | switch vt := v.(type) { 56 | case []interface{}: 57 | for ik, iv := range vt { 58 | switch ik { 59 | case 0: 60 | okOrderBook.Asks[k].Price = util.InterfaceToFloat64(iv) 61 | case 1: 62 | okOrderBook.Asks[k].Amount = util.InterfaceToFloat64(iv) 63 | } 64 | } 65 | } 66 | } 67 | 68 | for k, v := range _okOrderBook.Bids { 69 | switch vt := v.(type) { 70 | case []interface{}: 71 | for ik, iv := range vt { 72 | switch ik { 73 | case 0: 74 | okOrderBook.Bids[k].Price = util.InterfaceToFloat64(iv) 75 | case 1: 76 | okOrderBook.Bids[k].Amount = util.InterfaceToFloat64(iv) 77 | } 78 | } 79 | } 80 | } 81 | return 82 | } 83 | 84 | func (w *Haobtc) analyzeOrderBook(content string) (orderBook OrderBook, err error) { 85 | // init to false 86 | var _okOrderBook _OKOrderBook 87 | if err = json.Unmarshal([]byte(content), &_okOrderBook); err != nil { 88 | logger.Errorln(err) 89 | return 90 | } 91 | 92 | okOrderBook := convert2struct(_okOrderBook) 93 | 94 | for i := 0; i < DEPTH; i++ { 95 | orderBook.Asks[i].Price = okOrderBook.Asks[len(_okOrderBook.Asks)-i-1].Price 96 | orderBook.Asks[i].Amount = okOrderBook.Asks[len(_okOrderBook.Asks)-i-1].Amount 97 | orderBook.Bids[i].Price = okOrderBook.Bids[i].Price 98 | orderBook.Bids[i].Amount = okOrderBook.Bids[i].Amount 99 | } 100 | 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /src/haobtc/tradeAPI.go: -------------------------------------------------------------------------------- 1 | package haobtc 2 | 3 | import ( 4 | . "config" 5 | "crypto/md5" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "logger" 11 | "net/url" 12 | "sort" 13 | "strings" 14 | "util" 15 | ) 16 | 17 | type HaobtcTrade struct { 18 | name string 19 | api_key string 20 | secret_key string 21 | errno int64 22 | } 23 | 24 | func NewHaobtcTrade(name, api_key, secret_key string) *HaobtcTrade { 25 | w := new(HaobtcTrade) 26 | w.name = name 27 | w.api_key = api_key 28 | w.secret_key = secret_key 29 | return w 30 | } 31 | 32 | func (w *HaobtcTrade) createSign(pParams map[string]string) string { 33 | ms := util.NewMapSorter(pParams) 34 | sort.Sort(ms) 35 | 36 | v := url.Values{} 37 | for _, item := range ms { 38 | v.Add(item.Key, item.Val) 39 | } 40 | 41 | to_sign_str := v.Encode() 42 | 43 | logger.Debugln(to_sign_str) 44 | 45 | h := md5.New() 46 | 47 | raw_str := v.Encode() 48 | 49 | raw_str += "&secret_key=" + w.secret_key 50 | 51 | logger.Debugln("raw_str", raw_str) 52 | io.WriteString(h, raw_str) 53 | sign := fmt.Sprintf("%X", h.Sum(nil)) 54 | 55 | req_para := to_sign_str + "&sign=" + sign 56 | 57 | return req_para 58 | } 59 | 60 | type ErrorMsg struct { 61 | Code string 62 | Message string 63 | } 64 | 65 | func (w *HaobtcTrade) check_json_result(body string) (errorMsg ErrorMsg, ret bool) { 66 | if !strings.Contains(body, "code") { 67 | ret = true 68 | return 69 | } 70 | 71 | doc := json.NewDecoder(strings.NewReader(body)) 72 | if err := doc.Decode(&errorMsg); err == io.EOF { 73 | logger.Errorln("HaobtcTrade errorMsg:", err, body) 74 | ret = false 75 | return 76 | } else if err != nil { 77 | logger.Errorln("HaobtcTrade errorMsg:", err, body) 78 | ret = false 79 | return 80 | } 81 | 82 | ret = false 83 | return 84 | } 85 | 86 | ////// 87 | type UserInfo struct { 88 | Exchange_cny float64 89 | Exchange_btc float64 90 | Wallet_cny float64 91 | Wallet_btc float64 92 | Exchange_frozen_cny float64 93 | Exchange_frozen_btc float64 94 | } 95 | 96 | func (w *HaobtcTrade) GetAccount() (userInfo UserInfo, err error) { 97 | pParams := make(map[string]string) 98 | pParams["api_key"] = w.api_key 99 | 100 | req_para := w.createSign(pParams) 101 | 102 | body, err := util.HttpPost(Config[w.name+"_api_userinfo"], req_para) 103 | if err != nil { 104 | return 105 | } 106 | 107 | errorMsg, ret := w.check_json_result(body) 108 | if ret == false { 109 | err = errors.New(string(body)) 110 | logger.Errorln(ret, body, errorMsg) 111 | return 112 | } 113 | 114 | doc := json.NewDecoder(strings.NewReader(body)) 115 | if err = doc.Decode(&userInfo); err == io.EOF { 116 | logger.Errorln(err) 117 | } else if err != nil { 118 | logger.Errorln(err) 119 | } 120 | 121 | return 122 | } 123 | 124 | ///// 125 | 126 | func (w *HaobtcTrade) doTrade(symbol, method, price, amount string) (string, string, error) { 127 | pParams := make(map[string]string) 128 | pParams["api_key"] = w.api_key 129 | pParams["type"] = method 130 | 131 | if method != "sell_market" && method != "buy_market" { 132 | pParams["price"] = price 133 | } 134 | 135 | pParams["amount"] = amount 136 | 137 | req_para := w.createSign(pParams) 138 | 139 | body, err := util.HttpPost(Config[w.name+"_api_trade"], req_para) 140 | if err != nil { 141 | return "", "", err 142 | } 143 | _, ret := w.check_json_result(body) 144 | if ret == false { 145 | result := string(body) 146 | return "", result, nil 147 | } 148 | 149 | doc := json.NewDecoder(strings.NewReader(body)) 150 | 151 | type Msg struct { 152 | Order_id int64 153 | } 154 | 155 | var m Msg 156 | if err = doc.Decode(&m); err == io.EOF { 157 | logger.Errorln("HaobtcTrade errorMsg:", err, body) 158 | } else if err != nil { 159 | logger.Errorln("HaobtcTrade errorMsg:", err, body) 160 | } 161 | 162 | logger.Debugln(m) 163 | if m.Order_id == -1 { 164 | return "", "balance is not enough", err 165 | } 166 | 167 | return fmt.Sprintf("%d", m.Order_id), "", err 168 | } 169 | 170 | func (w *HaobtcTrade) BuyBTC(price, amount string) (string, string, error) { 171 | return w.doTrade("btc_cny", "buy", price, amount) 172 | } 173 | 174 | func (w *HaobtcTrade) SellBTC(price, amount string) (string, string, error) { 175 | return w.doTrade("btc_cny", "sell", price, amount) 176 | } 177 | 178 | func (w *HaobtcTrade) BuyMarketBTC(cny string) (string, string, error) { 179 | return w.doTrade("btc_cny", "buy_market", "", cny) 180 | } 181 | 182 | func (w *HaobtcTrade) SellMarketBTC(btc string) (string, string, error) { 183 | return w.doTrade("btc_cny", "sell_market", "", btc) 184 | } 185 | 186 | ///// 187 | type HaobtcOrder struct { 188 | Amount float64 189 | Avg_price float64 190 | Create_date string 191 | Deal_size float64 192 | Order_id int64 193 | Price float64 194 | Status string 195 | Side string 196 | Type string 197 | } 198 | 199 | func (w *HaobtcTrade) Get_order(symbol string, order_id string) (m HaobtcOrder, result string, err error) { 200 | pParams := make(map[string]string) 201 | pParams["api_key"] = w.api_key 202 | pParams["order_id"] = order_id 203 | 204 | req_para := w.createSign(pParams) 205 | 206 | body, err := util.HttpPost(Config[w.name+"_api_order_info"], req_para) 207 | if err != nil { 208 | return 209 | } 210 | 211 | result = string(body) 212 | _, ret := w.check_json_result(body) 213 | if ret == false { 214 | err = errors.New(string(body)) 215 | logger.Errorln("Get_order check_json_result:", order_id, body) 216 | 217 | return 218 | } 219 | 220 | doc := json.NewDecoder(strings.NewReader(body)) 221 | 222 | if err = doc.Decode(&m); err == io.EOF { 223 | logger.Errorln(err) 224 | } else if err != nil { 225 | logger.Errorln(err) 226 | logger.Errorln(body) 227 | logger.Errorln(m) 228 | } 229 | 230 | return 231 | } 232 | 233 | func (w *HaobtcTrade) Get_BTCorder(order_id string) (m HaobtcOrder, result string, err error) { 234 | return w.Get_order("btc_cny", order_id) 235 | } 236 | 237 | func (w *HaobtcTrade) Cancel_order(symbol string, order_id string) (err error) { 238 | pParams := make(map[string]string) 239 | pParams["api_key"] = w.api_key 240 | pParams["order_id"] = order_id 241 | 242 | req_para := w.createSign(pParams) 243 | 244 | body, err := util.HttpPost(Config[w.name+"_api_cancelorder"], req_para) 245 | if err != nil { 246 | return 247 | } 248 | _, ret := w.check_json_result(body) 249 | if ret == false { 250 | err = errors.New(string(body)) 251 | logger.Errorln("cancel check_json_result:", order_id, body) 252 | return 253 | } 254 | 255 | doc := json.NewDecoder(strings.NewReader(body)) 256 | 257 | type Msg struct { 258 | Order_id int64 259 | } 260 | 261 | var m Msg 262 | if err = doc.Decode(&m); err == io.EOF { 263 | logger.Errorln("cancel decode eof:", order_id, body) 264 | } else if err != nil { 265 | logger.Errorln("cancel decode err:", order_id, body) 266 | } 267 | 268 | logger.Debugln(m) 269 | 270 | return 271 | } 272 | 273 | func (w *HaobtcTrade) Cancel_BTCorder(order_id string) (err error) { 274 | return w.Cancel_order("btc_cny", order_id) 275 | } 276 | -------------------------------------------------------------------------------- /src/huobi/api.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package huobi 6 | 7 | import ( 8 | "common" 9 | "config" 10 | "fmt" 11 | "logger" 12 | "os" 13 | "strconv" 14 | "util" 15 | ) 16 | 17 | type Huobi struct { 18 | tradeAPI *HuobiTrade 19 | name string 20 | } 21 | 22 | func NewExchange(name, access_key, secret_key string) *Huobi { 23 | w := new(Huobi) 24 | w.tradeAPI = NewHuobiTrade(access_key, secret_key) 25 | return w 26 | } 27 | 28 | func (w Huobi) GetTicker() (ticker common.Ticker, err error) { 29 | symbol := config.Config["symbol"] 30 | if symbol == "btc_cny" { 31 | symbol = "btc" 32 | } else { 33 | symbol = "ltc" 34 | } 35 | 36 | return w.getTicker(symbol) 37 | } 38 | 39 | func (w Huobi) GetDepth() (orderBook common.OrderBook, err error) { 40 | symbol := config.Config["symbol"] 41 | if symbol == "btc_cny" { 42 | symbol = "btc" 43 | } else { 44 | symbol = "ltc" 45 | } 46 | 47 | return w.getDepth(symbol) 48 | } 49 | 50 | func (w Huobi) Buy(price, btc string) (buyId string, result string, err error) { 51 | tradeAPI := w.tradeAPI 52 | 53 | symbol := config.Config["symbol"] 54 | return tradeAPI.doTrade("buy", symbol, price, btc) 55 | } 56 | 57 | func (w Huobi) Sell(price, btc string) (sellId string, result string, err error) { 58 | tradeAPI := w.tradeAPI 59 | 60 | symbol := config.Config["symbol"] 61 | return tradeAPI.doTrade("sell", symbol, price, btc) 62 | } 63 | 64 | func (w Huobi) BuyMarket(cny string) (buyId string, result string, err error) { 65 | tradeAPI := w.tradeAPI 66 | symbol := config.Config["symbol"] 67 | return tradeAPI.doTrade("buy_market", symbol, "", cny) 68 | } 69 | 70 | func (w Huobi) SellMarket(btc string) (sellId string, result string, err error) { 71 | tradeAPI := w.tradeAPI 72 | symbol := config.Config["symbol"] 73 | return tradeAPI.doTrade("sell_market", symbol, "", btc) 74 | } 75 | 76 | func (w Huobi) GetOrder(order_id string) (order common.Order, result string, err error) { 77 | tradeAPI := w.tradeAPI 78 | 79 | symbol := config.Config["symbol"] 80 | if symbol == "btc_cny" { 81 | symbol = "1" 82 | } else if symbol == "ltc_cny" { 83 | symbol = "0" 84 | } else { 85 | panic(-1) 86 | } 87 | hbOrder, result, err := tradeAPI.Get_order(symbol, order_id) 88 | if err != nil { 89 | return 90 | } 91 | 92 | order.Id = fmt.Sprintf("%d", hbOrder.Id) 93 | 94 | price, err := strconv.ParseFloat(hbOrder.Processed_price, 64) 95 | if err != nil { 96 | logger.Errorln("config item order_price is not float") 97 | return 98 | } 99 | 100 | amount, err := strconv.ParseFloat(hbOrder.Order_amount, 64) 101 | if err != nil { 102 | logger.Errorln("config item order_amount is not float") 103 | return 104 | } 105 | 106 | deal_amount, err := strconv.ParseFloat(hbOrder.Processed_amount, 64) 107 | if err != nil { 108 | logger.Errorln("config item processed_amount is not float") 109 | return 110 | } 111 | 112 | order.Price = price 113 | 114 | //1限价买 2限价卖 3市价买 4市价卖 115 | if hbOrder.Type == 3 { //火币的市价买单,代表买入金额和成交金额,比较特殊 116 | if price > 1 { 117 | order.Amount = amount / price 118 | order.Deal_amount = deal_amount / price 119 | } 120 | } else if hbOrder.Type == 4 || hbOrder.Type == 2 || hbOrder.Type == 1 { 121 | order.Amount = amount 122 | order.Deal_amount = deal_amount 123 | } else { 124 | logger.Panicln("unsupport trade type!") 125 | os.Exit(-1) 126 | } 127 | 128 | switch hbOrder.Status { 129 | case 0, 1: 130 | order.Status = common.ORDER_STATE_PENDING 131 | case 2: 132 | order.Status = common.ORDER_STATE_SUCCESS 133 | case 3, 6: 134 | order.Status = common.ORDER_STATE_CANCELED 135 | default: 136 | order.Status = common.ORDER_STATE_UNKNOWN 137 | } 138 | 139 | return 140 | } 141 | 142 | func (w Huobi) CancelOrder(order_id string) (err error) { 143 | tradeAPI := w.tradeAPI 144 | 145 | symbol := config.Config["symbol"] 146 | if symbol == "btc_cny" { 147 | symbol = "1" 148 | } else if symbol == "ltc_cny" { 149 | symbol = "0" 150 | } else { 151 | panic(-1) 152 | } 153 | return tradeAPI.Cancel_order(symbol, order_id) 154 | } 155 | 156 | func (w Huobi) GetAccount() (account common.Account, err error) { 157 | tradeAPI := w.tradeAPI 158 | 159 | userInfo, err := tradeAPI.GetAccount() 160 | 161 | if err != nil { 162 | logger.Debugln("Huobi GetAccount failed", err) 163 | return 164 | } 165 | account.Available_cny = util.ToFloat(userInfo.Available_cny_display) 166 | account.Available_btc = util.ToFloat(userInfo.Available_btc_display) 167 | account.Available_ltc = util.ToFloat(userInfo.Available_ltc_display) 168 | 169 | account.Frozen_cny = util.ToFloat(userInfo.Frozen_cny_display) 170 | account.Frozen_btc = util.ToFloat(userInfo.Frozen_btc_display) 171 | account.Frozen_ltc = util.ToFloat(userInfo.Frozen_ltc_display) 172 | 173 | return 174 | } 175 | -------------------------------------------------------------------------------- /src/huobi/api_test.go: -------------------------------------------------------------------------------- 1 | package huobi 2 | 3 | import ( 4 | "config" 5 | "logger" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | const ( 11 | Huobi_access_key string = "" 12 | Huobi_secret_key string = "" 13 | ) 14 | 15 | func setup() { 16 | config.Env = config.Test 17 | config.Root = "/Users/phil/dev/work/haobtc/trader" 18 | config.LoadConfig() 19 | 20 | //strategy.Task() 21 | } 22 | 23 | func Test_API(t *testing.T) { 24 | setup() 25 | 26 | api := NewExchange(Huobi_access_key, Huobi_secret_key) 27 | 28 | if false { 29 | 30 | buyId, result, err := api.Buy("1100", "0.001") 31 | logger.Infoln(buyId, result, err) 32 | 33 | order, result, err := api.GetOrder(buyId) 34 | logger.Infoln(order, result, err) 35 | 36 | sellId, result, err := api.Sell("2100", "0.002") 37 | logger.Infoln(sellId, result, err) 38 | 39 | order, result, err = api.GetOrder(sellId) 40 | logger.Infoln(order, result, err) 41 | } 42 | 43 | if true { 44 | 45 | buyId, result, err := api.BuyMarket("1") 46 | logger.Infoln(buyId, result, err) 47 | 48 | order, result, err := api.GetOrder(buyId) 49 | logger.Infoln(order, result, err) 50 | 51 | sellId, result, err := api.SellMarket("0.0022") 52 | logger.Infoln(sellId, result, err) 53 | 54 | order, result, err = api.GetOrder(sellId) 55 | logger.Infoln(order, result, err) 56 | } 57 | 58 | } 59 | 60 | func TaskTemplate(seconds time.Duration, f func()) { 61 | ticker := time.NewTicker(seconds * time.Second) // one second 62 | defer ticker.Stop() 63 | 64 | for _ = range ticker.C { 65 | f() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/huobi/marketAPI.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package huobi 6 | 7 | import ( 8 | . "common" 9 | . "config" 10 | "encoding/json" 11 | "errors" 12 | "fmt" 13 | "logger" 14 | "util" 15 | ) 16 | 17 | func (w *Huobi) getTicker(symbol string) (ticker Ticker, err error) { 18 | ticker_url := fmt.Sprintf(Config["hb_ticker_url"], symbol) 19 | body, err := util.HttpGet(ticker_url) 20 | if err != nil { 21 | logger.Errorln(err) 22 | return 23 | } 24 | 25 | if err = json.Unmarshal([]byte(body), &ticker); err != nil { 26 | logger.Infoln(err) 27 | return 28 | } 29 | 30 | return 31 | } 32 | 33 | func (w *Huobi) getDepth(symbol string) (orderBook OrderBook, err error) { 34 | depth_url := fmt.Sprintf(Config["hb_depth_url"], symbol, DEPTH) 35 | body, err := util.HttpGet(depth_url) 36 | if err != nil { 37 | logger.Errorln(err) 38 | return 39 | } 40 | 41 | defaultstruct := make(map[string]interface{}) 42 | err = json.Unmarshal([]byte(body), &defaultstruct) 43 | if err != nil { 44 | logger.Infoln("defaultstruct", defaultstruct) 45 | return 46 | } 47 | 48 | asks := defaultstruct["asks"].([]interface{}) 49 | bids := defaultstruct["bids"].([]interface{}) 50 | 51 | for i, ask := range asks { 52 | _ask := ask.([]interface{}) 53 | price, ret := _ask[0].(float64) 54 | if !ret { 55 | err = errors.New("data wrong") 56 | return orderBook, err 57 | } 58 | amount, ret := _ask[1].(float64) 59 | if !ret { 60 | err = errors.New("data wrong") 61 | return orderBook, err 62 | } 63 | order := MarketOrder{ 64 | Price: price, 65 | Amount: amount, 66 | } 67 | orderBook.Asks[len(asks)-i-1] = order 68 | } 69 | 70 | for i, bid := range bids { 71 | _bid := bid.([]interface{}) 72 | price, ret := _bid[0].(float64) 73 | if !ret { 74 | err = errors.New("data wrong") 75 | return orderBook, err 76 | } 77 | amount, ret := _bid[1].(float64) 78 | if !ret { 79 | err = errors.New("data wrong") 80 | return orderBook, err 81 | } 82 | order := MarketOrder{ 83 | Price: price, 84 | Amount: amount, 85 | } 86 | orderBook.Bids[i] = order 87 | } 88 | 89 | return 90 | } 91 | -------------------------------------------------------------------------------- /src/huobi/tradeapi.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package huobi 6 | 7 | import ( 8 | . "config" 9 | "crypto/md5" 10 | "encoding/json" 11 | "errors" 12 | "fmt" 13 | "io" 14 | "logger" 15 | "net/url" 16 | "sort" 17 | "strconv" 18 | "strings" 19 | "time" 20 | "util" 21 | ) 22 | 23 | /* 24 | https://www.huobi.com/help/index.php?a=api_help_v3 25 | */ 26 | type HuobiTrade struct { 27 | access_key string 28 | secret_key string 29 | } 30 | 31 | func NewHuobiTrade(access_key, secret_key string) *HuobiTrade { 32 | w := new(HuobiTrade) 33 | w.access_key = access_key 34 | w.secret_key = secret_key 35 | return w 36 | } 37 | 38 | func (w *HuobiTrade) createSign(pParams map[string]string) string { 39 | pParams["secret_key"] = w.secret_key 40 | 41 | ms := util.NewMapSorter(pParams) 42 | sort.Sort(ms) 43 | 44 | v := url.Values{} 45 | for _, item := range ms { 46 | v.Add(item.Key, item.Val) 47 | } 48 | 49 | to_sign_str := v.Encode() 50 | 51 | h := md5.New() 52 | 53 | io.WriteString(h, v.Encode()) 54 | 55 | sign := fmt.Sprintf("%x", h.Sum(nil)) 56 | 57 | req_para := to_sign_str + "&sign=" + sign 58 | 59 | return req_para 60 | } 61 | 62 | type ErrorMsg struct { 63 | Code string 64 | Message string 65 | } 66 | 67 | func (w *HuobiTrade) check_json_result(body string) (errorMsg ErrorMsg, ret bool) { 68 | if !strings.Contains(body, "code") { 69 | ret = true 70 | return 71 | } 72 | 73 | doc := json.NewDecoder(strings.NewReader(body)) 74 | if err := doc.Decode(&errorMsg); err == io.EOF { 75 | logger.Errorln("HuobiTrade errorMsg:", err, body) 76 | ret = false 77 | return 78 | } else if err != nil { 79 | logger.Errorln("HuobiTrade errorMsg:", err, body) 80 | ret = false 81 | return 82 | } 83 | 84 | if errorMsg.Code != "0" { 85 | logger.Errorln("HuobiTrade errorMsg:", errorMsg) 86 | ret = false 87 | return 88 | } 89 | 90 | ret = true 91 | return 92 | } 93 | 94 | type Account_info struct { 95 | Total string 96 | Net_asset string 97 | Available_cny_display string 98 | Available_btc_display string 99 | Available_ltc_display string 100 | Frozen_cny_display string 101 | Frozen_btc_display string 102 | Frozen_ltc_display string 103 | Loan_cny_display string 104 | Loan_btc_display string 105 | Loan_ltc_display string 106 | } 107 | 108 | func (w *HuobiTrade) GetAccount() (account_info Account_info, err error) { 109 | pParams := make(map[string]string) 110 | pParams["method"] = "get_account_info" 111 | pParams["access_key"] = w.access_key 112 | now := time.Now().Unix() 113 | pParams["created"] = strconv.FormatInt(now, 10) 114 | 115 | req_para := w.createSign(pParams) 116 | body, err := util.HttpPost(Config["hb_api_url"], req_para) 117 | if err != nil { 118 | return 119 | } 120 | 121 | _, ret := w.check_json_result(body) 122 | if ret == false { 123 | err = errors.New(string(body)) 124 | return 125 | } 126 | 127 | doc := json.NewDecoder(strings.NewReader(body)) 128 | 129 | if err := doc.Decode(&account_info); err == io.EOF { 130 | logger.Errorln(err) 131 | } else if err != nil { 132 | logger.Errorln(err) 133 | } 134 | 135 | return 136 | } 137 | 138 | func (w *HuobiTrade) _doTrade(method, cointype, price, amount string) (string, string, error) { 139 | pParams := make(map[string]string) 140 | pParams["method"] = method 141 | pParams["access_key"] = w.access_key 142 | pParams["coin_type"] = cointype 143 | if method == "buy" || method == "sell" { 144 | pParams["price"] = price 145 | } 146 | 147 | pParams["amount"] = amount 148 | now := time.Now().Unix() 149 | pParams["created"] = strconv.FormatInt(now, 10) 150 | req_para := w.createSign(pParams) 151 | body, err := util.HttpPost(Config["hb_api_url"], req_para) 152 | if err != nil { 153 | return "", "", err 154 | } 155 | _, ret := w.check_json_result(body) 156 | if ret == false { 157 | result := string(body) 158 | return "", result, nil 159 | } 160 | 161 | doc := json.NewDecoder(strings.NewReader(body)) 162 | 163 | type Msg struct { 164 | Result string 165 | Id int64 166 | } 167 | 168 | var m Msg 169 | if err = doc.Decode(&m); err == io.EOF { 170 | logger.Errorln("HuobiTrade errorMsg:", err, body) 171 | } else if err != nil { 172 | logger.Errorln("HuobiTrade errorMsg:", err, body) 173 | } 174 | 175 | if m.Result == "success" { 176 | return fmt.Sprintf("%d", m.Id), "", nil 177 | } else { 178 | err = errors.New(string(body)) 179 | return "", "", err 180 | } 181 | } 182 | 183 | func (w *HuobiTrade) doTrade(method, symbol, price, amount string) (string, string, error) { 184 | var cointype string 185 | if symbol == "btc_cny" { 186 | cointype = "1" 187 | } else if symbol == "ltc_cny" { 188 | cointype = "0" 189 | } else { 190 | panic(-1) 191 | } 192 | 193 | return w._doTrade(method, cointype, price, amount) 194 | } 195 | 196 | type HBOrder struct { 197 | Id int64 198 | Type int 199 | Order_price string 200 | Order_amount string 201 | Processed_price string 202 | Processed_amount string 203 | Vot string 204 | Fee string 205 | Total string 206 | Status int 207 | } 208 | 209 | func (w *HuobiTrade) Get_order(cointype string, order_id string) (m HBOrder, result string, err error) { 210 | pParams := make(map[string]string) 211 | pParams["method"] = "order_info" 212 | pParams["access_key"] = w.access_key 213 | pParams["coin_type"] = cointype 214 | pParams["id"] = order_id 215 | now := time.Now().Unix() 216 | pParams["created"] = strconv.FormatInt(now, 10) 217 | 218 | req_para := w.createSign(pParams) 219 | body, err := util.HttpPost(Config["hb_api_url"], req_para) 220 | if err != nil { 221 | return 222 | } 223 | 224 | result = string(body) 225 | _, ret := w.check_json_result(body) 226 | if ret == false { 227 | logger.Infoln(body) 228 | err = errors.New(string(body)) 229 | return 230 | } 231 | 232 | doc := json.NewDecoder(strings.NewReader(body)) 233 | if err = doc.Decode(&m); err == io.EOF { 234 | logger.Infoln(err) 235 | } else if err != nil { 236 | logger.Infoln(err) 237 | } 238 | 239 | return 240 | } 241 | 242 | func (w *HuobiTrade) Cancel_order(cointype string, order_id string) (err error) { 243 | pParams := make(map[string]string) 244 | pParams["method"] = "cancel_order" 245 | pParams["access_key"] = w.access_key 246 | pParams["coin_type"] = cointype 247 | pParams["id"] = order_id 248 | now := time.Now().Unix() 249 | pParams["created"] = strconv.FormatInt(now, 10) 250 | 251 | req_para := w.createSign(pParams) 252 | body, err := util.HttpPost(Config["hb_api_url"], req_para) 253 | if err != nil { 254 | return 255 | } 256 | _, ret := w.check_json_result(body) 257 | if ret == false { 258 | err = errors.New(string(body)) 259 | return 260 | } 261 | 262 | doc := json.NewDecoder(strings.NewReader(body)) 263 | 264 | type Msg struct { 265 | Result string 266 | } 267 | 268 | var m Msg 269 | if err = doc.Decode(&m); err == io.EOF { 270 | logger.Errorln(err) 271 | } else if err != nil { 272 | logger.Errorln(err) 273 | } 274 | 275 | logger.Debugln(m) 276 | 277 | if m.Result == "success" { 278 | return nil 279 | } else { 280 | err = errors.New(string(body)) 281 | return 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/logger/logger.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package logger 6 | 7 | import ( 8 | . "config" 9 | "github.com/rifflock/lfshook" 10 | "github.com/sirupsen/logrus" 11 | // "github.com/zbindenren/logrus_mail" 12 | "os" 13 | "path/filepath" 14 | "runtime" 15 | ) 16 | 17 | var ( 18 | // 日志文件 19 | debug_file = Root + "/log/debug.log" 20 | info_file = Root + "/log/info.log" 21 | error_file = Root + "/log/error.log" 22 | panic_file = Root + "/log/panic.log" 23 | ) 24 | 25 | func init() { 26 | os.Mkdir(Root+"/log/", 0777) 27 | } 28 | 29 | var Log *logrus.Logger 30 | 31 | func NewLogger() *logrus.Logger { 32 | if Log != nil { 33 | return Log 34 | } 35 | 36 | Log = logrus.New() 37 | textFormatter := new(logrus.TextFormatter) 38 | // textFormatter.DisableColors = true 39 | textFormatter.FullTimestamp = true 40 | textFormatter.TimestampFormat = "20060102 15:04:05" 41 | Log.Formatter = textFormatter 42 | if Config["loglevel"] == "debug" { 43 | Log.Level = logrus.DebugLevel 44 | } else { 45 | Log.Level = logrus.InfoLevel 46 | } 47 | 48 | Log.Hooks.Add(lfshook.NewHook(lfshook.PathMap{ 49 | logrus.DebugLevel: debug_file, 50 | logrus.InfoLevel: info_file, 51 | logrus.ErrorLevel: error_file, 52 | logrus.PanicLevel: panic_file, 53 | })) 54 | 55 | // if you do not need authentication for your smtp host 56 | // hook, err := logrus_mail.NewMailAuthHook("trader", "smtp.ym.163.com", 994, "haobtc@blocktip.com", "78623269@qq.com", "haobtc@blocktip.com", "pfE8pmQUUK00") 57 | // if err == nil { 58 | // Log.Hooks.Add(hook) 59 | // } 60 | 61 | return Log 62 | } 63 | 64 | func Debugln(args ...interface{}) { 65 | // 加上文件调用和行号 66 | _, callerFile, line, ok := runtime.Caller(1) 67 | if ok { 68 | args = append([]interface{}{"[", filepath.Base(callerFile), "]", line}, args...) 69 | } 70 | 71 | NewLogger().Debugln(args...) 72 | } 73 | 74 | func Infoln(args ...interface{}) { 75 | // 加上文件调用和行号 76 | _, callerFile, line, ok := runtime.Caller(1) 77 | if ok { 78 | args = append([]interface{}{"[", filepath.Base(callerFile), "]", line}, args...) 79 | } 80 | NewLogger().Infoln(args...) 81 | } 82 | 83 | func Errorln(args ...interface{}) { 84 | // 加上文件调用和行号 85 | _, callerFile, line, ok := runtime.Caller(1) 86 | if ok { 87 | args = append([]interface{}{"[", filepath.Base(callerFile), "]", line}, args...) 88 | } 89 | NewLogger().Errorln(args...) 90 | } 91 | 92 | func Panicln(args ...interface{}) { 93 | // 加上文件调用和行号 94 | _, callerFile, line, ok := runtime.Caller(1) 95 | if ok { 96 | args = append([]interface{}{"[", filepath.Base(callerFile), "]", line}, args...) 97 | } 98 | NewLogger().Panicln(args...) 99 | } 100 | -------------------------------------------------------------------------------- /src/mocks/TradeAPI.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "common" 4 | import "github.com/stretchr/testify/mock" 5 | 6 | type TradeAPI struct { 7 | mock.Mock 8 | } 9 | 10 | func (m *TradeAPI) GetTicker() (common.Ticker, error) { 11 | ret := m.Called() 12 | 13 | r0 := ret.Get(0).(common.Ticker) 14 | r1 := ret.Error(1) 15 | 16 | return r0, r1 17 | } 18 | func (m *TradeAPI) GetAccount() (common.Account, error) { 19 | ret := m.Called() 20 | 21 | r0 := ret.Get(0).(common.Account) 22 | r1 := ret.Error(1) 23 | 24 | return r0, r1 25 | } 26 | func (m *TradeAPI) GetDepth() (common.OrderBook, error) { 27 | ret := m.Called() 28 | 29 | r0 := ret.Get(0).(common.OrderBook) 30 | r1 := ret.Error(1) 31 | 32 | return r0, r1 33 | } 34 | func (m *TradeAPI) Buy(price string, amount string) (string, string, error) { 35 | ret := m.Called(price, amount) 36 | 37 | r0 := ret.Get(0).(string) 38 | r1 := ret.Get(1).(string) 39 | r2 := ret.Error(2) 40 | 41 | return r0, r1, r2 42 | } 43 | func (m *TradeAPI) Sell(price string, amount string) (string, string, error) { 44 | ret := m.Called(price, amount) 45 | 46 | r0 := ret.Get(0).(string) 47 | r1 := ret.Get(1).(string) 48 | r2 := ret.Error(2) 49 | 50 | return r0, r1, r2 51 | } 52 | func (m *TradeAPI) BuyMarket(amount string) (string, string, error) { 53 | ret := m.Called(amount) 54 | 55 | r0 := ret.Get(0).(string) 56 | r1 := ret.Get(1).(string) 57 | r2 := ret.Error(2) 58 | 59 | return r0, r1, r2 60 | } 61 | func (m *TradeAPI) SellMarket(amount string) (string, string, error) { 62 | ret := m.Called(amount) 63 | 64 | r0 := ret.Get(0).(string) 65 | r1 := ret.Get(1).(string) 66 | r2 := ret.Error(2) 67 | 68 | return r0, r1, r2 69 | } 70 | func (m *TradeAPI) GetOrder(order_id string) (common.Order, string, error) { 71 | ret := m.Called(order_id) 72 | 73 | r0 := ret.Get(0).(common.Order) 74 | r1 := ret.Get(1).(string) 75 | r2 := ret.Error(2) 76 | 77 | return r0, r1, r2 78 | } 79 | func (m *TradeAPI) CancelOrder(order_id string) error { 80 | ret := m.Called(order_id) 81 | 82 | r0 := ret.Error(0) 83 | 84 | return r0 85 | } 86 | -------------------------------------------------------------------------------- /src/mocks/depth_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "strategy" 6 | "testing" 7 | // "trade_service" 8 | "logger" 9 | ) 10 | 11 | func Test_GetDepth(t *testing.T) { 12 | setup() 13 | 14 | chbtc_mockObj := new(TradeAPI) 15 | okcoin_mockObj := new(TradeAPI) 16 | huobi_mockObj := new(TradeAPI) 17 | 18 | setupDepth(chbtc_mockObj, okcoin_mockObj, huobi_mockObj) 19 | 20 | exchanges := []string{"huobi", "okcoin", "chbtc"} 21 | asks, bids, markets, err := strategy.GetMergeDepth(exchanges) 22 | assert.NoError(t, err, "should be no err") 23 | assert.NotNil(t, asks, "should be not nil") 24 | assert.NotNil(t, bids, "should be not nil") 25 | 26 | // strategy.PrintDepthList(asks) 27 | // strategy.PrintDepthList(bids) 28 | 29 | depthCount := 0 30 | for e := asks.Front(); e != nil; e = e.Next() { 31 | sumExchangeOrder := e.Value.(*strategy.SumExchangeOrder) 32 | depthCount++ 33 | switch depthCount { 34 | case 1: 35 | assert.EqualValues(t, sumExchangeOrder.Amount, 0, "Amount should be 0") 36 | assert.EqualValues(t, sumExchangeOrder.Price, 0, "Price should be 0") 37 | case 2: 38 | assert.EqualValues(t, sumExchangeOrder.Amount, 0.4, "Amount should be 0.4") 39 | assert.EqualValues(t, sumExchangeOrder.Price, 1400.02, "Price should be 1400.02") 40 | case 3: 41 | assert.EqualValues(t, sumExchangeOrder.Amount, 0.2, "Amount should be 0.2") 42 | assert.EqualValues(t, sumExchangeOrder.Price, 1401.01, "Price should be 1401.01") 43 | case 4: 44 | assert.EqualValues(t, sumExchangeOrder.Amount, 0.2, "Amount should be 0.2") 45 | assert.EqualValues(t, sumExchangeOrder.Price, 1402.01, "Price should be 1402.01") 46 | } 47 | 48 | logger.Debugln(depthCount, sumExchangeOrder.Amount, sumExchangeOrder.Price) 49 | for i := 0; i < len(markets); i++ { 50 | exchange := markets[i] 51 | if sumExchangeOrder.ExchangeOrder[exchange] != nil { 52 | logger.Infoln(depthCount, sumExchangeOrder.ExchangeOrder[exchange]) 53 | switch depthCount { 54 | case 1: 55 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0, "Amount should be 0") 56 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 0, "Price should be 0") 57 | case 2: 58 | switch exchange { 59 | case "chbtc": 60 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.1, "Amount should be 0.1") 61 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1400.02, "Price should be 1400.02") 62 | case "okcoin": 63 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.2, "Amount should be 0.2") 64 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1400.02, "Price should be 1400.02") 65 | case "huobi": 66 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.1, "Amount should be 0.1") 67 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1400.02, "Price should be 1400.02") 68 | } 69 | case 3: 70 | assert.NotEqual(t, sumExchangeOrder.ExchangeOrder[exchange].Exchange, "chbtc", "Exchange should not be chbtc") 71 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.1, "Amount should be 0.2") 72 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1401.01, "Price should be 1401.01") 73 | case 4: 74 | assert.Equal(t, sumExchangeOrder.ExchangeOrder[exchange].Exchange, "chbtc", "Exchange should be chbtc") 75 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.2, "Amount should be 0.2") 76 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1402.01, "Price should be 1402.01") 77 | } 78 | } 79 | } 80 | } 81 | 82 | assert.EqualValues(t, depthCount, 4, "depthCount should be 4") 83 | 84 | depthCount = 0 85 | for e := bids.Front(); e != nil; e = e.Next() { 86 | sumExchangeOrder := e.Value.(*strategy.SumExchangeOrder) 87 | depthCount++ 88 | 89 | logger.Debugln(depthCount, sumExchangeOrder.Amount, sumExchangeOrder.Price) 90 | 91 | switch depthCount { 92 | case 1: 93 | assert.EqualValues(t, sumExchangeOrder.Amount, 0.1, "Amount should be 0.1") 94 | assert.EqualValues(t, sumExchangeOrder.Price, 1399.02, "Price should be 1399.02") 95 | case 2: 96 | assert.EqualValues(t, sumExchangeOrder.Amount, 0.2, "Amount should be 0.2") 97 | assert.EqualValues(t, sumExchangeOrder.Price, 1398.02, "Price should be 1398.02") 98 | case 3: 99 | assert.EqualValues(t, sumExchangeOrder.Amount, 0.6, "Amount should be 0.6") 100 | assert.EqualValues(t, sumExchangeOrder.Price, 1397.01, "Price should be 1397.01") 101 | case 4: 102 | assert.EqualValues(t, sumExchangeOrder.Amount, 0.3, "Amount should be 0.3") 103 | assert.EqualValues(t, sumExchangeOrder.Price, 1396.01, "Price should be 1396.01") 104 | case 5: 105 | assert.EqualValues(t, sumExchangeOrder.Amount, 0, "Amount should be 0") 106 | assert.EqualValues(t, sumExchangeOrder.Price, 0, "Price should be 0") 107 | } 108 | 109 | for i := 0; i < len(markets); i++ { 110 | exchange := markets[i] 111 | if sumExchangeOrder.ExchangeOrder[exchange] != nil { 112 | logger.Infoln(depthCount, sumExchangeOrder.ExchangeOrder[exchange]) 113 | switch depthCount { 114 | case 1: 115 | assert.Equal(t, sumExchangeOrder.ExchangeOrder[exchange].Exchange, "okcoin", "Exchange should be okcoin") 116 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.1, "Amount should be 0.1") 117 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1399.02, "Price should be 1399.02") 118 | case 2: 119 | assert.NotEqual(t, sumExchangeOrder.ExchangeOrder[exchange].Exchange, "okcoin", "Exchange should not be okcoin") 120 | switch exchange { 121 | case "chbtc": 122 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.1, "Amount should be 0.1") 123 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1398.02, "Price should be 1398.02") 124 | case "huobi": 125 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.1, "Amount should be 0.1") 126 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1398.02, "Price should be 1398.02") 127 | } 128 | case 3: 129 | assert.NotEqual(t, sumExchangeOrder.ExchangeOrder[exchange].Exchange, "chbtc", "Exchange should be chbtc") 130 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.3, "Amount should be 0.3") 131 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1397.01, "Price should be 1397.01") 132 | case 4: 133 | assert.Equal(t, sumExchangeOrder.ExchangeOrder[exchange].Exchange, "chbtc", "Exchange should be chbtc") 134 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0.3, "Amount should be 0.3") 135 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 1396.01, "Price should be 1396.01") 136 | case 5: 137 | assert.Contains(t, markets, exchange, "should contains!") 138 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Amount, 0, "Amount should be 0") 139 | assert.EqualValues(t, sumExchangeOrder.ExchangeOrder[exchange].Price, 0, "Price should be 0") 140 | } 141 | } 142 | } 143 | } 144 | 145 | assert.EqualValues(t, depthCount, 5, "depthCount should be 5") 146 | 147 | // assert that the expectations were met 148 | chbtc_mockObj.AssertExpectations(t) 149 | okcoin_mockObj.AssertExpectations(t) 150 | huobi_mockObj.AssertExpectations(t) 151 | } 152 | -------------------------------------------------------------------------------- /src/mocks/fund_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | "trade_server" 7 | ) 8 | 9 | func Test_GetAccount(t *testing.T) { 10 | setup() 11 | 12 | // create an instance of our test object 13 | chbtc_mockObj := new(TradeAPI) 14 | okcoin_mockObj := new(TradeAPI) 15 | huobi_mockObj := new(TradeAPI) 16 | 17 | chbtc_account, okcoin_account, huobi_account := setupAccount(chbtc_mockObj, okcoin_mockObj, huobi_mockObj) 18 | 19 | serverhandler := new(trade_server.TradeServiceHandler) 20 | exchange_accounts, err := serverhandler.GetAccount() 21 | assert.NoError(t, err, "should be no err") 22 | 23 | for i := 0; i < len(exchange_accounts); i++ { 24 | exchange_account := exchange_accounts[i] 25 | switch exchange_account.Exchange { 26 | case "chbtc": 27 | assert.Equal(t, exchange_account.AvailableCny, chbtc_account.Available_cny, "should same cny amount") 28 | assert.Equal(t, exchange_account.AvailableBtc, chbtc_account.Available_btc, "should same btc amount") 29 | case "okcoin": 30 | assert.Equal(t, exchange_account.AvailableCny, okcoin_account.Available_cny, "should same cny amount") 31 | assert.Equal(t, exchange_account.AvailableBtc, okcoin_account.Available_btc, "should same btc amount") 32 | case "huobi": 33 | assert.Equal(t, exchange_account.AvailableCny, huobi_account.Available_cny, "should same cny amount") 34 | assert.Equal(t, exchange_account.AvailableBtc, huobi_account.Available_btc, "should same btc amount") 35 | } 36 | } 37 | 38 | // assert that the expectations were met 39 | chbtc_mockObj.AssertExpectations(t) 40 | okcoin_mockObj.AssertExpectations(t) 41 | huobi_mockObj.AssertExpectations(t) 42 | } 43 | -------------------------------------------------------------------------------- /src/mocks/setup.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "common" 5 | "config" 6 | "db" 7 | "strategy" 8 | ) 9 | 10 | func setup() { 11 | config.Env = config.Test 12 | config.Root = "/Users/phil/dev/work/trader" 13 | config.LoadConfig() 14 | db.Init_sqlstr("root:root@tcp(127.0.0.1:3306)/trader_test") 15 | //strategy.Task() 16 | } 17 | 18 | func setupDepth(chbtc_mockObj, okcoin_mockObj, huobi_mockObj *TradeAPI) { 19 | // setup expectations 20 | var chbtc_orderBook common.OrderBook 21 | chbtc_orderBook.Asks[common.DEPTH-1] = common.MarketOrder{Price: 1402.01, Amount: 0.2} 22 | chbtc_orderBook.Asks[common.DEPTH-2] = common.MarketOrder{Price: 1400.02, Amount: 0.1} 23 | chbtc_orderBook.Bids[0] = common.MarketOrder{Price: 1398.02, Amount: 0.1} 24 | chbtc_orderBook.Bids[1] = common.MarketOrder{Price: 1396.01, Amount: 0.3} 25 | chbtc_mockObj.On("GetDepth").Return(chbtc_orderBook, nil) 26 | 27 | var okcoin_orderBook common.OrderBook 28 | okcoin_orderBook.Asks[common.DEPTH-1] = common.MarketOrder{Price: 1401.01, Amount: 0.1} 29 | okcoin_orderBook.Asks[common.DEPTH-2] = common.MarketOrder{Price: 1400.02, Amount: 0.2} 30 | okcoin_orderBook.Bids[0] = common.MarketOrder{Price: 1399.02, Amount: 0.1} 31 | okcoin_orderBook.Bids[1] = common.MarketOrder{Price: 1397.01, Amount: 0.3} 32 | okcoin_mockObj.On("GetDepth").Return(okcoin_orderBook, nil) 33 | 34 | var huobi_orderBook common.OrderBook 35 | huobi_orderBook.Asks[common.DEPTH-1] = common.MarketOrder{Price: 1401.01, Amount: 0.1} 36 | huobi_orderBook.Asks[common.DEPTH-2] = common.MarketOrder{Price: 1400.02, Amount: 0.1} 37 | huobi_orderBook.Bids[0] = common.MarketOrder{Price: 1398.02, Amount: 0.1} 38 | huobi_orderBook.Bids[1] = common.MarketOrder{Price: 1397.01, Amount: 0.3} 39 | huobi_mockObj.On("GetDepth").Return(huobi_orderBook, nil) 40 | 41 | common.SetMockTradeAPI("chbtc", chbtc_mockObj) 42 | common.SetMockTradeAPI("okcoin", okcoin_mockObj) 43 | common.SetMockTradeAPI("huobi", huobi_mockObj) 44 | 45 | strategy.QueryDepth() 46 | 47 | return 48 | } 49 | 50 | func setupAccount(chbtc_mockObj, okcoin_mockObj, huobi_mockObj *TradeAPI) (chbtc_account, okcoin_account, huobi_account common.Account) { 51 | // setup expectations 52 | 53 | chbtc_account.Available_cny = 10000 54 | chbtc_account.Available_btc = 10 55 | chbtc_mockObj.On("GetAccount").Return(chbtc_account, nil) 56 | 57 | okcoin_account.Available_cny = 20000 58 | okcoin_account.Available_btc = 20 59 | okcoin_mockObj.On("GetAccount").Return(okcoin_account, nil) 60 | 61 | huobi_account.Available_cny = 30000 62 | huobi_account.Available_btc = 30 63 | huobi_mockObj.On("GetAccount").Return(huobi_account, nil) 64 | 65 | common.SetMockTradeAPI("chbtc", chbtc_mockObj) 66 | common.SetMockTradeAPI("okcoin", okcoin_mockObj) 67 | common.SetMockTradeAPI("huobi", huobi_mockObj) 68 | 69 | // call the code we are testing 70 | strategy.QueryFund() 71 | 72 | return 73 | } 74 | -------------------------------------------------------------------------------- /src/mocks/tradebuy_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "common" 5 | "logger" 6 | "strategy" 7 | "testing" 8 | "trade_server" 9 | "trade_service" 10 | ) 11 | 12 | func Test_Buy(t *testing.T) { 13 | setup() 14 | 15 | chbtc_mockObj := new(TradeAPI) 16 | okcoin_mockObj := new(TradeAPI) 17 | huobi_mockObj := new(TradeAPI) 18 | 19 | setupDepth(chbtc_mockObj, okcoin_mockObj, huobi_mockObj) 20 | setupAccount(chbtc_mockObj, okcoin_mockObj, huobi_mockObj) 21 | 22 | // create an instance of our test object 23 | // setup expectations 24 | { 25 | buyOrderID := "19" 26 | chbtc_mockObj.On("Buy", "1402.01", "0.1426").Return(buyOrderID, "", nil) 27 | okcoin_mockObj.On("BuyMarket", "420.11").Return(buyOrderID, "", nil) 28 | huobi_mockObj.On("BuyMarket", "280.10").Return(buyOrderID, "", nil) 29 | 30 | var order common.Order 31 | order.Id = buyOrderID 32 | order.Price = 1402.01 33 | order.Amount = 0.1425 34 | order.Deal_amount = 0.1425 35 | order.Status = common.ORDER_STATE_SUCCESS 36 | chbtc_mockObj.On("GetOrder", buyOrderID).Return(order, "", nil) 37 | order.Status = common.ORDER_STATE_SUCCESS 38 | okcoin_mockObj.On("GetOrder", buyOrderID).Return(order, "", nil) 39 | order.Status = common.ORDER_STATE_SUCCESS 40 | huobi_mockObj.On("GetOrder", buyOrderID).Return(order, "", nil) 41 | 42 | // chbtc_mockObj.On("CancelOrder", buyOrderID).Return(nil) 43 | // okcoin_mockObj.On("CancelOrder", buyOrderID).Return(nil) 44 | // huobi_mockObj.On("CancelOrder", buyOrderID).Return(nil) 45 | } 46 | 47 | common.SetMockTradeAPI("chbtc", chbtc_mockObj) 48 | common.SetMockTradeAPI("okcoin", okcoin_mockObj) 49 | common.SetMockTradeAPI("huobi", huobi_mockObj) 50 | 51 | // call the code we are testing 52 | var buyOrder trade_service.Trade 53 | buyOrder.UID = "10" 54 | buyOrder.Amount = 900 55 | serverhandler := new(trade_server.TradeServiceHandler) 56 | siteOrder, err := serverhandler.Buy(&buyOrder) 57 | logger.Infoln(siteOrder, err) 58 | 59 | strategy.ProgressReady() 60 | strategy.ProgressOrdered() 61 | 62 | // assert that the expectations were met 63 | chbtc_mockObj.AssertExpectations(t) 64 | okcoin_mockObj.AssertExpectations(t) 65 | huobi_mockObj.AssertExpectations(t) 66 | } 67 | -------------------------------------------------------------------------------- /src/mocks/tradesell_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "common" 5 | "logger" 6 | "strategy" 7 | "testing" 8 | "trade_server" 9 | "trade_service" 10 | ) 11 | 12 | func Test_Sell(t *testing.T) { 13 | setup() 14 | 15 | chbtc_mockObj := new(TradeAPI) 16 | okcoin_mockObj := new(TradeAPI) 17 | huobi_mockObj := new(TradeAPI) 18 | 19 | setupDepth(chbtc_mockObj, okcoin_mockObj, huobi_mockObj) 20 | setupAccount(chbtc_mockObj, okcoin_mockObj, huobi_mockObj) 21 | 22 | // create an instance of our test object 23 | // setup expectations 24 | { 25 | orderID := "19" 26 | chbtc_mockObj.On("Sell", "1396.01", "0.2000").Return(orderID, "", nil) 27 | okcoin_mockObj.On("SellMarket", "0.4000").Return(orderID, "", nil) 28 | huobi_mockObj.On("SellMarket", "0.4000").Return(orderID, "", nil) 29 | 30 | var order common.Order 31 | order.Id = orderID 32 | order.Price = 1396.01 33 | order.Amount = 0.2000 34 | order.Deal_amount = 0.2000 35 | order.Status = common.ORDER_STATE_SUCCESS 36 | chbtc_mockObj.On("GetOrder", orderID).Return(order, "", nil) 37 | order.Status = common.ORDER_STATE_SUCCESS 38 | okcoin_mockObj.On("GetOrder", orderID).Return(order, "", nil) 39 | order.Status = common.ORDER_STATE_SUCCESS 40 | huobi_mockObj.On("GetOrder", orderID).Return(order, "", nil) 41 | 42 | // chbtc_mockObj.On("CancelOrder", orderID).Return(nil) 43 | // okcoin_mockObj.On("CancelOrder", buyOrderID).Return(nil) 44 | // huobi_mockObj.On("CancelOrder", buyOrderID).Return(nil) 45 | } 46 | 47 | common.SetMockTradeAPI("chbtc", chbtc_mockObj) 48 | common.SetMockTradeAPI("okcoin", okcoin_mockObj) 49 | common.SetMockTradeAPI("huobi", huobi_mockObj) 50 | 51 | // call the code we are testing 52 | var sellOrder trade_service.Trade 53 | sellOrder.UID = "10" 54 | sellOrder.Amount = 1 55 | serverhandler := new(trade_server.TradeServiceHandler) 56 | siteOrder, err := serverhandler.Sell(&sellOrder) 57 | logger.Infoln(siteOrder, err) 58 | 59 | strategy.ProgressReady() 60 | strategy.ProgressOrdered() 61 | 62 | // assert that the expectations were met 63 | chbtc_mockObj.AssertExpectations(t) 64 | okcoin_mockObj.AssertExpectations(t) 65 | huobi_mockObj.AssertExpectations(t) 66 | } 67 | -------------------------------------------------------------------------------- /src/okcoin/api.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package okcoin 6 | 7 | import ( 8 | "common" 9 | "config" 10 | "fmt" 11 | "logger" 12 | "util" 13 | ) 14 | 15 | import s "strings" 16 | 17 | type Okcoin struct { 18 | tradeAPI *OkcoinTrade 19 | name string 20 | } 21 | 22 | func NewExchange(name, partner, secret_key string) *Okcoin { 23 | w := new(Okcoin) 24 | w.tradeAPI = NewOkcoinTrade(partner, secret_key) 25 | return w 26 | } 27 | 28 | func (w Okcoin) GetTicker() (ticker common.Ticker, err error) { 29 | symbol := config.Config["symbol"] 30 | 31 | okTicker, err := w.getTicker(symbol) 32 | if err != nil { 33 | return 34 | } 35 | 36 | ticker.Date = okTicker.Date 37 | ticker.Ticker.Buy = util.ToFloat(okTicker.Ticker.Buy) 38 | ticker.Ticker.High = util.ToFloat(okTicker.Ticker.High) 39 | ticker.Ticker.Last = util.ToFloat(okTicker.Ticker.Last) 40 | ticker.Ticker.Low = util.ToFloat(okTicker.Ticker.Low) 41 | ticker.Ticker.Sell = util.ToFloat(okTicker.Ticker.Sell) 42 | ticker.Ticker.Vol = util.ToFloat(okTicker.Ticker.Vol) 43 | return 44 | } 45 | 46 | //get depth 47 | func (w Okcoin) GetDepth() (orderBook common.OrderBook, err error) { 48 | symbol := config.Config["symbol"] 49 | return w.getDepth(symbol) 50 | } 51 | 52 | func (w Okcoin) Buy(price, btc string) (buyId string, result string, err error) { 53 | tradeAPI := w.tradeAPI 54 | 55 | symbol := config.Config["symbol"] 56 | if symbol == "btc_cny" { 57 | return tradeAPI.BuyBTC(price, btc) 58 | } else { 59 | panic(-1) 60 | } 61 | 62 | return 63 | } 64 | 65 | func (w Okcoin) Sell(price, btc string) (sellId string, result string, err error) { 66 | tradeAPI := w.tradeAPI 67 | symbol := config.Config["symbol"] 68 | if symbol == "btc_cny" { 69 | return tradeAPI.SellBTC(price, btc) 70 | } else { 71 | panic(-1) 72 | } 73 | 74 | return 75 | } 76 | 77 | func (w Okcoin) BuyMarket(cny string) (buyId string, result string, err error) { 78 | tradeAPI := w.tradeAPI 79 | 80 | symbol := config.Config["symbol"] 81 | if symbol == "btc_cny" { 82 | return tradeAPI.BuyMarketBTC(cny) 83 | } else { 84 | panic(-1) 85 | } 86 | 87 | return 88 | } 89 | 90 | func (w Okcoin) SellMarket(btc string) (sellId string, result string, err error) { 91 | tradeAPI := w.tradeAPI 92 | symbol := config.Config["symbol"] 93 | if symbol == "btc_cny" { 94 | return tradeAPI.SellMarketBTC(btc) 95 | } else { 96 | panic(-1) 97 | } 98 | 99 | return 100 | } 101 | 102 | func (w Okcoin) GetOrder(order_id string) (order common.Order, result string, err error) { 103 | symbol := config.Config["symbol"] 104 | tradeAPI := w.tradeAPI 105 | 106 | ok_orderTable, result, err := tradeAPI.Get_order(symbol, order_id) 107 | if err != nil { 108 | return 109 | } 110 | 111 | order.Id = fmt.Sprintf("%d", ok_orderTable.Orders[0].Order_id) 112 | order.Price = ok_orderTable.Orders[0].Avg_price 113 | order.Amount = ok_orderTable.Orders[0].Amount //注意:坑!ok的买单,amount返回为空 114 | order.Deal_amount = ok_orderTable.Orders[0].Deal_amount 115 | 116 | switch ok_orderTable.Orders[0].Status { 117 | case 0, 1: 118 | order.Status = common.ORDER_STATE_PENDING 119 | case 2: 120 | order.Status = common.ORDER_STATE_SUCCESS 121 | case -1: 122 | order.Status = common.ORDER_STATE_CANCELED // treat canceled status as a error since there is not enough fund 123 | default: 124 | order.Status = common.ORDER_STATE_UNKNOWN 125 | } 126 | 127 | return 128 | } 129 | 130 | func (w Okcoin) CancelOrder(order_id string) (err error) { 131 | tradeAPI := w.tradeAPI 132 | symbol := config.Config["symbol"] 133 | return tradeAPI.Cancel_order(symbol, order_id) 134 | } 135 | 136 | func (w Okcoin) GetAccount() (account common.Account, err error) { 137 | tradeAPI := w.tradeAPI 138 | 139 | userInfo, err := tradeAPI.GetAccount() 140 | if err != nil { 141 | logger.Debugln("okcoin GetAccount failed") 142 | return 143 | } 144 | 145 | cnystr := userInfo.Info.Funds.Free.CNY 146 | cnystr = s.Replace(cnystr, ",", "", -1) 147 | 148 | account.Available_cny = util.ToFloat(cnystr) 149 | 150 | account.Available_btc = util.ToFloat(userInfo.Info.Funds.Free.BTC) 151 | account.Available_ltc = util.ToFloat(userInfo.Info.Funds.Free.LTC) 152 | 153 | account.Frozen_cny = util.ToFloat(userInfo.Info.Funds.Freezed.CNY) 154 | account.Frozen_btc = util.ToFloat(userInfo.Info.Funds.Freezed.BTC) 155 | account.Frozen_ltc = util.ToFloat(userInfo.Info.Funds.Freezed.LTC) 156 | 157 | return 158 | } 159 | -------------------------------------------------------------------------------- /src/okcoin/api_test.go: -------------------------------------------------------------------------------- 1 | package okcoin 2 | 3 | import ( 4 | "config" 5 | "logger" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | const ( 11 | OKCoin_api_key string = "" 12 | OKCoin_secret_key string = "" 13 | ) 14 | 15 | func setup() { 16 | config.Env = config.Test 17 | config.Root = "/Users/phil/dev/work/haobtc/trader" 18 | config.LoadConfig() 19 | 20 | //strategy.Task() 21 | } 22 | 23 | func Test_API(t *testing.T) { 24 | setup() 25 | 26 | api := NewExchange(OKCoin_api_key, OKCoin_secret_key) 27 | 28 | if false { 29 | 30 | buyId, result, err := api.Buy("1100", "0.011") 31 | logger.Infoln(buyId, result, err) 32 | 33 | order, result, err := api.GetOrder(buyId) 34 | logger.Infoln(order, result, err) 35 | 36 | sellId, result, err := api.Sell("2100", "0.012") 37 | logger.Infoln(sellId, result, err) 38 | 39 | order, result, err = api.GetOrder(sellId) 40 | logger.Infoln(order, result, err) 41 | } 42 | 43 | if true { 44 | buyId, result, err := api.BuyMarket("30") 45 | logger.Infoln(buyId, result, err) 46 | 47 | order, result, err := api.GetOrder(buyId) 48 | logger.Infoln(order, result, err) 49 | 50 | sellId, result, err := api.SellMarket("0.013") 51 | logger.Infoln(sellId, result, err) 52 | 53 | order, result, err = api.GetOrder(sellId) 54 | logger.Infoln(order, result, err) 55 | } 56 | 57 | } 58 | 59 | func TaskTemplate(seconds time.Duration, f func()) { 60 | ticker := time.NewTicker(seconds * time.Second) // one second 61 | defer ticker.Stop() 62 | 63 | for _ = range ticker.C { 64 | f() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/okcoin/marketAPI.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package okcoin 6 | 7 | import ( 8 | . "common" 9 | . "config" 10 | "encoding/json" 11 | "fmt" 12 | "logger" 13 | "util" 14 | ) 15 | 16 | type OKTicker struct { 17 | Date string 18 | Ticker OKTickerPrice 19 | } 20 | 21 | type OKTickerPrice struct { 22 | Buy string 23 | High string 24 | Last string 25 | Low string 26 | Sell string 27 | Vol string 28 | } 29 | 30 | func (w *Okcoin) getTicker(symbol string) (ticker OKTicker, err error) { 31 | ticker_url := fmt.Sprintf(Config["ok_ticker_url"], symbol) 32 | body, err := util.HttpGet(ticker_url) 33 | if err != nil { 34 | logger.Errorln(err) 35 | return 36 | } 37 | 38 | logger.Infoln(body, err) 39 | 40 | if err = json.Unmarshal([]byte(body), &ticker); err != nil { 41 | logger.Infoln(err) 42 | return 43 | } 44 | 45 | return 46 | } 47 | 48 | func (w *Okcoin) getDepth(symbol string) (orderBook OrderBook, err error) { 49 | depth_url := fmt.Sprintf(Config["ok_depth_url"], symbol, DEPTH) 50 | 51 | logger.Debugln("okcoin", depth_url) 52 | body, err := util.HttpGet(depth_url) 53 | if err != nil { 54 | logger.Errorln(err, depth_url) 55 | return 56 | } 57 | 58 | logger.Debugln("okcoin", depth_url, body) 59 | return w.analyzeOrderBook(body) 60 | } 61 | 62 | type OKMarketOrder struct { 63 | Price float64 // 价格 64 | Amount float64 // 委单量 65 | } 66 | 67 | type _OKOrderBook struct { 68 | Asks [DEPTH]interface{} 69 | Bids [DEPTH]interface{} 70 | } 71 | 72 | type OKOrderBook struct { 73 | Asks [DEPTH]OKMarketOrder 74 | Bids [DEPTH]OKMarketOrder 75 | } 76 | 77 | func convert2struct(_okOrderBook _OKOrderBook) (okOrderBook OKOrderBook) { 78 | for k, v := range _okOrderBook.Asks { 79 | switch vt := v.(type) { 80 | case []interface{}: 81 | for ik, iv := range vt { 82 | switch ik { 83 | case 0: 84 | okOrderBook.Asks[k].Price = util.InterfaceToFloat64(iv) 85 | case 1: 86 | okOrderBook.Asks[k].Amount = util.InterfaceToFloat64(iv) 87 | } 88 | } 89 | } 90 | } 91 | 92 | for k, v := range _okOrderBook.Bids { 93 | switch vt := v.(type) { 94 | case []interface{}: 95 | for ik, iv := range vt { 96 | switch ik { 97 | case 0: 98 | okOrderBook.Bids[k].Price = util.InterfaceToFloat64(iv) 99 | case 1: 100 | okOrderBook.Bids[k].Amount = util.InterfaceToFloat64(iv) 101 | } 102 | } 103 | } 104 | } 105 | return 106 | } 107 | 108 | func (w *Okcoin) analyzeOrderBook(content string) (orderBook OrderBook, err error) { 109 | // init to false 110 | var _okOrderBook _OKOrderBook 111 | if err = json.Unmarshal([]byte(content), &_okOrderBook); err != nil { 112 | logger.Infoln(err) 113 | return 114 | } 115 | 116 | okOrderBook := convert2struct(_okOrderBook) 117 | 118 | for i := 0; i < DEPTH; i++ { 119 | orderBook.Asks[i].Price = okOrderBook.Asks[len(_okOrderBook.Asks)-DEPTH+i].Price 120 | orderBook.Asks[i].Amount = okOrderBook.Asks[len(_okOrderBook.Asks)-DEPTH+i].Amount 121 | orderBook.Bids[i].Price = okOrderBook.Bids[i].Price 122 | orderBook.Bids[i].Amount = okOrderBook.Bids[i].Amount 123 | } 124 | 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /src/okcoin/tradeAPI.go: -------------------------------------------------------------------------------- 1 | /* 2 | SEE DOC: 3 | TRADE API 4 | https://www.okcoin.cn/about/rest_api.do 5 | */ 6 | 7 | package okcoin 8 | 9 | import ( 10 | . "config" 11 | "crypto/md5" 12 | "encoding/json" 13 | "errors" 14 | "fmt" 15 | "io" 16 | "logger" 17 | "net/url" 18 | "sort" 19 | "strings" 20 | "util" 21 | ) 22 | 23 | type OkcoinTrade struct { 24 | api_key string 25 | secret_key string 26 | errno int64 27 | } 28 | 29 | func NewOkcoinTrade(api_key, secret_key string) *OkcoinTrade { 30 | w := new(OkcoinTrade) 31 | w.api_key = api_key 32 | w.secret_key = secret_key 33 | return w 34 | } 35 | 36 | func (w *OkcoinTrade) createSign(pParams map[string]string) string { 37 | ms := util.NewMapSorter(pParams) 38 | sort.Sort(ms) 39 | 40 | v := url.Values{} 41 | for _, item := range ms { 42 | v.Add(item.Key, item.Val) 43 | } 44 | 45 | to_sign_str := v.Encode() 46 | 47 | //v.Add("secret_key", w.secret_key) 48 | 49 | h := md5.New() 50 | 51 | raw_str := v.Encode() 52 | 53 | raw_str += "&secret_key=" + w.secret_key 54 | 55 | // logger.Infoln("raw_str",raw_str) 56 | io.WriteString(h, raw_str) 57 | sign := fmt.Sprintf("%X", h.Sum(nil)) 58 | 59 | req_para := to_sign_str + "&sign=" + sign 60 | 61 | return req_para 62 | } 63 | 64 | type ErrorMsg struct { 65 | Result bool 66 | Error_code int 67 | } 68 | 69 | func (w *OkcoinTrade) check_json_result(body string) (errorMsg ErrorMsg, ret bool) { 70 | if !strings.Contains(body, "result") { 71 | ret = false 72 | return 73 | } 74 | 75 | doc := json.NewDecoder(strings.NewReader(body)) 76 | if err := doc.Decode(&errorMsg); err == io.EOF { 77 | logger.Errorln("OkcoinTrade errorMsg:", err, body) 78 | ret = false 79 | return 80 | } else if err != nil { 81 | logger.Errorln("OkcoinTrade errorMsg:", err, body) 82 | ret = false 83 | return 84 | } 85 | 86 | if errorMsg.Result != true { 87 | logger.Errorln("OkcoinTrade errorMsg:", errorMsg) 88 | ret = false 89 | return 90 | } 91 | ret = true 92 | return 93 | } 94 | 95 | ////// 96 | type Asset struct { 97 | Net string 98 | Total string 99 | } 100 | type UnionFund struct { 101 | BTC string 102 | LTC string 103 | } 104 | 105 | type Money struct { 106 | BTC string 107 | CNY string 108 | LTC string 109 | } 110 | 111 | type Funds struct { 112 | Asset Asset 113 | Borrow Money 114 | Free Money 115 | Freezed Money 116 | UnionFund UnionFund 117 | } 118 | 119 | type Info struct { 120 | Funds Funds 121 | } 122 | 123 | type UserInfo struct { 124 | Result bool 125 | Info Info 126 | } 127 | 128 | func (w *OkcoinTrade) GetAccount() (userInfo UserInfo, err error) { 129 | pParams := make(map[string]string) 130 | pParams["api_key"] = w.api_key 131 | 132 | req_para := w.createSign(pParams) 133 | 134 | body, err := util.HttpPost(Config["ok_api_userinfo"], req_para) 135 | if err != nil { 136 | return 137 | } 138 | 139 | errorMsg, ret := w.check_json_result(body) 140 | if ret == false { 141 | err = errors.New(string(body)) 142 | logger.Infoln(ret, errorMsg) 143 | return 144 | } 145 | 146 | doc := json.NewDecoder(strings.NewReader(body)) 147 | if err = doc.Decode(&userInfo); err == io.EOF { 148 | logger.Debugln(err) 149 | } else if err != nil { 150 | logger.Errorln(err) 151 | } 152 | 153 | return 154 | } 155 | 156 | ///// 157 | 158 | func (w *OkcoinTrade) doTrade(symbol, method, price, amount string) (string, string, error) { 159 | pParams := make(map[string]string) 160 | pParams["api_key"] = w.api_key 161 | pParams["symbol"] = symbol 162 | pParams["type"] = method 163 | 164 | if method != "sell_market" { 165 | pParams["price"] = price 166 | } 167 | 168 | if method != "buy_market" { 169 | pParams["amount"] = amount 170 | } 171 | 172 | req_para := w.createSign(pParams) 173 | 174 | body, err := util.HttpPost(Config["ok_api_trade"], req_para) 175 | if err != nil { 176 | return "", "", err 177 | } 178 | _, ret := w.check_json_result(body) 179 | if ret == false { 180 | result := string(body) 181 | return "", result, nil 182 | } 183 | 184 | doc := json.NewDecoder(strings.NewReader(body)) 185 | 186 | type Msg struct { 187 | Result bool 188 | Order_id int64 189 | } 190 | 191 | var m Msg 192 | if err := doc.Decode(&m); err == io.EOF { 193 | logger.Errorln("OkcoinTrade errorMsg:", err, body) 194 | } else if err != nil { 195 | logger.Errorln("OkcoinTrade errorMsg:", err, body) 196 | } 197 | 198 | if m.Result == true { 199 | return fmt.Sprintf("%d", m.Order_id), "", nil 200 | } else { 201 | err = errors.New(string(body)) 202 | return "", "", err 203 | } 204 | } 205 | 206 | func (w *OkcoinTrade) BuyBTC(price, amount string) (string, string, error) { 207 | return w.doTrade("btc_cny", "buy", price, amount) 208 | } 209 | 210 | func (w *OkcoinTrade) SellBTC(price, amount string) (string, string, error) { 211 | return w.doTrade("btc_cny", "sell", price, amount) 212 | } 213 | 214 | func (w *OkcoinTrade) BuyLTC(price, amount string) (string, string, error) { 215 | return w.doTrade("ltc_cny", "buy", price, amount) 216 | } 217 | 218 | func (w *OkcoinTrade) SellLTC(price, amount string) (string, string, error) { 219 | return w.doTrade("ltc_cny", "sell", price, amount) 220 | } 221 | 222 | func (w *OkcoinTrade) BuyMarketBTC(cny string) (string, string, error) { 223 | return w.doTrade("btc_cny", "buy_market", cny, "") 224 | } 225 | 226 | func (w *OkcoinTrade) SellMarketBTC(btc string) (string, string, error) { 227 | return w.doTrade("btc_cny", "sell_market", "", btc) 228 | } 229 | 230 | func (w *OkcoinTrade) BuyMarketLTC(cny string) (string, string, error) { 231 | return w.doTrade("ltc_cny", "buy_market", cny, "") 232 | } 233 | 234 | func (w *OkcoinTrade) SellMarketLTC(ltc string) (string, string, error) { 235 | return w.doTrade("ltc_cny", "sell_market", "", ltc) 236 | } 237 | 238 | ///// 239 | type OKOrder struct { 240 | Amount float64 241 | Avg_price float64 242 | Create_date int 243 | Deal_amount float64 244 | Order_id int64 245 | Orders_id int64 246 | Price float64 247 | Status int 248 | Symbol string 249 | Type string 250 | } 251 | 252 | type OKOrderTable struct { 253 | Result bool 254 | Orders []OKOrder 255 | } 256 | 257 | func (w *OkcoinTrade) Get_order(symbol string, order_id string) (m OKOrderTable, result string, err error) { 258 | pParams := make(map[string]string) 259 | pParams["api_key"] = w.api_key 260 | pParams["symbol"] = symbol 261 | pParams["order_id"] = order_id 262 | 263 | req_para := w.createSign(pParams) 264 | 265 | body, err := util.HttpPost(Config["ok_api_order_info"], req_para) 266 | if err != nil { 267 | return 268 | } 269 | 270 | result = string(body) 271 | _, ret := w.check_json_result(body) 272 | if ret == false { 273 | err = errors.New(string(body)) 274 | logger.Errorln("Get_order check_json_result:", order_id, body) 275 | 276 | return 277 | } 278 | 279 | doc := json.NewDecoder(strings.NewReader(body)) 280 | 281 | if err = doc.Decode(&m); err == io.EOF { 282 | logger.Errorln(err) 283 | } else if err != nil { 284 | logger.Errorln(err) 285 | logger.Errorln(body) 286 | logger.Errorln(m) 287 | } 288 | 289 | return 290 | } 291 | 292 | func (w *OkcoinTrade) Get_BTCorder(order_id string) (m OKOrderTable, result string, err error) { 293 | return w.Get_order("btc_cny", order_id) 294 | } 295 | 296 | func (w *OkcoinTrade) Get_LTCorder(order_id string) (m OKOrderTable, result string, err error) { 297 | return w.Get_order("ltc_cny", order_id) 298 | } 299 | 300 | func (w *OkcoinTrade) Cancel_order(symbol string, order_id string) (err error) { 301 | pParams := make(map[string]string) 302 | pParams["api_key"] = w.api_key 303 | pParams["symbol"] = symbol 304 | pParams["order_id"] = order_id 305 | 306 | req_para := w.createSign(pParams) 307 | 308 | body, err := util.HttpPost(Config["ok_api_cancelorder"], req_para) 309 | if err != nil { 310 | return 311 | } 312 | _, ret := w.check_json_result(body) 313 | if ret == false { 314 | err = errors.New(string(body)) 315 | logger.Errorln("cancel check_json_result:", order_id, body) 316 | return 317 | } 318 | 319 | doc := json.NewDecoder(strings.NewReader(body)) 320 | 321 | type Msg struct { 322 | Result bool 323 | Order_id int64 324 | } 325 | 326 | var m Msg 327 | if err := doc.Decode(&m); err == io.EOF { 328 | logger.Errorln("cancel decode eof:", order_id, body) 329 | } else if err != nil { 330 | logger.Errorln("cancel decode err:", order_id, body) 331 | } 332 | 333 | logger.Debugln(m) 334 | 335 | if m.Result == true { 336 | logger.Infoln(m) 337 | return nil 338 | } else { 339 | logger.Infoln(m) 340 | err = errors.New(string(body)) 341 | 342 | return 343 | } 344 | } 345 | 346 | func (w *OkcoinTrade) Cancel_BTCorder(order_id string) (err error) { 347 | return w.Cancel_order("btc_cny", order_id) 348 | } 349 | 350 | func (w *OkcoinTrade) Cancel_LTCorder(order_id string) (err error) { 351 | return w.Cancel_order("ltc_cny", order_id) 352 | } 353 | -------------------------------------------------------------------------------- /src/strategy/amount_limit.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "config" 9 | "db" 10 | "logger" 11 | "sync" 12 | "trade_service" 13 | "util" 14 | ) 15 | 16 | var buy_queue_mutex = &sync.Mutex{} 17 | var buy_total_amount float64 18 | var sell_queue_mutex = &sync.Mutex{} 19 | var sell_total_amount float64 20 | 21 | var buy_limit float64 22 | var sell_limit float64 23 | 24 | func initTotalReadyAmount() { 25 | _buy_queue_amount, prs := config.Config["buy_queue_amount"] 26 | if !prs { 27 | _buy_queue_amount = "200" 28 | } 29 | 30 | _sell_queue_amount, prs := config.Config["sell_queue_amount"] 31 | if !prs { 32 | _sell_queue_amount = "200" 33 | } 34 | 35 | buy_limit = util.ToFloat(_buy_queue_amount) 36 | sell_limit = util.ToFloat(_sell_queue_amount) 37 | 38 | buy_total, sell_total := db.GetTotalReadyNow() 39 | 40 | logger.Infoln("init limit:", buy_limit, sell_limit, buy_total, sell_total) 41 | 42 | incr_buy(buy_total) 43 | incr_sell(sell_total) 44 | } 45 | 46 | func get_current_buy_total() float64 { 47 | buy_queue_mutex.Lock() 48 | defer buy_queue_mutex.Unlock() 49 | 50 | if buy_total_amount < 0 { 51 | logger.Errorln("buy_total_amount:", buy_total_amount) 52 | buy_total_amount = 0 53 | } 54 | return buy_total_amount 55 | } 56 | 57 | func get_current_sell_total() float64 { 58 | sell_queue_mutex.Lock() 59 | defer sell_queue_mutex.Unlock() 60 | 61 | if sell_total_amount < 0 { 62 | logger.Errorln("sell_total_amount:", sell_total_amount) 63 | sell_total_amount = 0 64 | } 65 | 66 | return sell_total_amount 67 | } 68 | 69 | func get_factor(amount float64, tradeType trade_service.TradeType) float64 { 70 | if tradeType == trade_service.TradeType_BUY { 71 | return get_buy_factor(amount) 72 | } else { 73 | return get_sell_factor(amount) 74 | } 75 | } 76 | func get_buy_factor(amount float64) float64 { 77 | factor := buy_total_amount + amount/buy_limit 78 | return factor 79 | } 80 | 81 | func get_sell_factor(amount float64) float64 { 82 | factor := sell_total_amount + amount/sell_limit 83 | return factor 84 | } 85 | 86 | func is_limit_buy(amount float64) bool { 87 | buy_queue_mutex.Lock() 88 | defer buy_queue_mutex.Unlock() 89 | 90 | if buy_total_amount+amount > buy_limit { 91 | logger.Infoln("buy_total_amount:", buy_total_amount) 92 | return true 93 | } 94 | 95 | buy_total_amount += amount 96 | 97 | logger.Infoln("buy_total_amount:", buy_total_amount) 98 | 99 | return false 100 | } 101 | 102 | func is_limit_sell(amount float64) bool { 103 | sell_queue_mutex.Lock() 104 | defer sell_queue_mutex.Unlock() 105 | 106 | if sell_total_amount+amount > sell_limit { 107 | logger.Infoln("sell_total_amount:", sell_total_amount) 108 | return true 109 | } 110 | 111 | sell_total_amount += amount 112 | 113 | logger.Infoln("sell_total_amount:", sell_total_amount) 114 | return false 115 | } 116 | 117 | func incr_buy(amount float64) { 118 | buy_queue_mutex.Lock() 119 | defer buy_queue_mutex.Unlock() 120 | 121 | buy_total_amount += amount 122 | 123 | logger.Infoln("buy_total_amount:", buy_total_amount) 124 | } 125 | 126 | func incr_sell(amount float64) { 127 | sell_queue_mutex.Lock() 128 | defer sell_queue_mutex.Unlock() 129 | 130 | sell_total_amount += amount 131 | 132 | logger.Infoln("sell_total_amount:", sell_total_amount) 133 | } 134 | 135 | func decr_buy(amount float64) { 136 | buy_queue_mutex.Lock() 137 | defer buy_queue_mutex.Unlock() 138 | 139 | buy_total_amount -= amount 140 | 141 | logger.Infoln("buy_total_amount:", buy_total_amount) 142 | } 143 | 144 | func decr_sell(amount float64) { 145 | sell_queue_mutex.Lock() 146 | defer sell_queue_mutex.Unlock() 147 | 148 | sell_total_amount -= amount 149 | 150 | logger.Infoln("sell_total_amount:", sell_total_amount) 151 | } 152 | -------------------------------------------------------------------------------- /src/strategy/dbtask.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "db" 9 | "fmt" 10 | "logger" 11 | "trade_service" 12 | ) 13 | 14 | func _queryFund(exchange string) (err error) { 15 | logger.Debugln("_______queryFund begin:", exchange) 16 | tradeAPI, err := GetExchange(exchange) 17 | if err != nil { 18 | logger.Errorln("queryFund err:", err, exchange) 19 | return 20 | } 21 | 22 | account, err := tradeAPI.GetAccount() 23 | if err != nil { 24 | logger.Errorln("queryFund err:", err, exchange) 25 | return 26 | } 27 | 28 | var dbFund trade_service.Account 29 | dbFund.Exchange = exchange 30 | dbFund.AvailableCny = account.Available_cny 31 | dbFund.AvailableBtc = account.Available_btc 32 | dbFund.FrozenCny = account.Frozen_cny 33 | dbFund.FrozenBtc = account.Frozen_btc 34 | 35 | fundExchages[exchange] = dbFund 36 | funds_log := fmt.Sprintf("_queryFund: exchange=%s cny=%f btc=%f Frozen_cny=%f Frozen_btc=%f\n", 37 | exchange, 38 | dbFund.AvailableCny, 39 | dbFund.AvailableBtc, 40 | dbFund.FrozenCny, 41 | dbFund.FrozenBtc) 42 | logger.Debugln(funds_log) 43 | 44 | err = db.SetAccount(&dbFund) 45 | if err != nil { 46 | logger.Errorln("queryFund:", err, exchange) 47 | return 48 | } 49 | 50 | logger.Debugln("_______queryFund end:", exchange) 51 | return 52 | } 53 | 54 | func _queryDepth(exchange string) (err error) { 55 | logger.Debugln("_______queryDepth begin:", exchange) 56 | tradeAPI, err := GetExchange(exchange) 57 | if err != nil { 58 | logger.Errorln("_queryDepth:", err, exchange) 59 | return 60 | } 61 | 62 | orderbook, err := tradeAPI.GetDepth() 63 | if err != nil { 64 | logger.Errorln("_queryDepth:", err, exchange) 65 | return 66 | } 67 | 68 | err = db.SetDepth(exchange, &orderbook) 69 | if err != nil { 70 | logger.Errorln("_queryDepth:", err, exchange) 71 | return 72 | } 73 | 74 | logger.Debugln("_______queryDepth end:", exchange) 75 | return 76 | } 77 | 78 | func QueryTicker() { 79 | logger.Debugln("QueryTicker begin") 80 | btc_threshold := 30.0 81 | amount_config, err := db.GetAmountConfig() 82 | if err != nil { 83 | logger.Errorln(err) 84 | // return 85 | } else { 86 | btc_threshold = amount_config.MaxBtc 87 | } 88 | 89 | ticker := trade_service.NewTicker() 90 | var order db.SiteOrder 91 | order.ID = -1 92 | { 93 | order.Amount = btc_threshold + get_current_buy_total() 94 | order.TradeType = trade_service.TradeType_BUY 95 | 96 | markets := GetUsableExchange(order.TradeType.String(), true) 97 | if len(markets) == 0 { 98 | logger.Errorln("QueryTicker: no used market, use all:", order.TradeType.String()) 99 | // ticker.Ask = 100000 100 | 101 | markets = GetUsableExchange(order.TradeType.String(), false) 102 | } 103 | 104 | _, err := estimateOrder(&order, markets) 105 | if err != nil { 106 | logger.Errorln("QueryTicker: estimateOrder:", err) 107 | return 108 | } 109 | 110 | ticker.Ask = order.EstimatePrice 111 | } 112 | 113 | { 114 | order.Amount = btc_threshold + get_current_sell_total() 115 | order.TradeType = trade_service.TradeType_SELL 116 | 117 | markets := GetUsableExchange(order.TradeType.String(), true) 118 | if len(markets) == 0 { 119 | logger.Errorln("QueryTicker: no used market, use all", order.TradeType.String()) 120 | markets = GetUsableExchange(order.TradeType.String(), false) 121 | // ticker.Bid = 10 122 | } 123 | _, err := estimateOrder(&order, markets) 124 | if err != nil { 125 | logger.Errorln(err) 126 | return 127 | } 128 | 129 | ticker.Bid = order.EstimatePrice 130 | } 131 | 132 | logger.Debugln(ticker.Ask, ticker.Bid) 133 | if ticker.Ask < ticker.Bid { 134 | logger.Infoln("QueryTicker adjust begin:", ticker.Ask, ticker.Bid) 135 | mid_price := (ticker.Ask + ticker.Bid) * 0.5 136 | ticker.Ask = mid_price + 0.01 137 | ticker.Bid = mid_price - 0.01 138 | logger.Infoln("QueryTicker adjust end:", ticker.Ask, ticker.Bid, mid_price) 139 | } 140 | 141 | db.SetTicker(ticker) 142 | 143 | logger.Debugln("QueryTicker end") 144 | 145 | return 146 | } 147 | 148 | func _queryExchangeData(exchange string) (err error) { 149 | _queryFund(exchange) 150 | _queryDepth(exchange) 151 | 152 | return 153 | } 154 | -------------------------------------------------------------------------------- /src/strategy/dispatch.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "db" 9 | "github.com/jinzhu/gorm" 10 | "logger" 11 | "strings" 12 | "trade_service" 13 | ) 14 | 15 | func pushOrder(tx *gorm.DB, siteOrder *db.SiteOrder, tradeOrders map[string]*trade_service.TradeOrder) (err error) { 16 | logger.Infoln("InsertOrder begin", siteOrder) 17 | _, err = db.InsertOrder(tx, siteOrder) 18 | logger.Infoln("InsertOrder end", siteOrder) 19 | //Error 1062: Duplicate entry 20 | if err != nil && strings.Contains(err.Error(), "1062") { 21 | logger.Errorln("InsertOrder Duplicate:", err, siteOrder) 22 | //here do'not need reponse NewTradeException tradeResult_ 23 | return nil 24 | } 25 | 26 | if err != nil { 27 | logger.Errorln("InsertOrder failed:", err, siteOrder) 28 | return 29 | } 30 | 31 | logger.Infoln("InsertTradeOrders begin", len(tradeOrders), tradeOrders) 32 | for exchange, _ := range tradeOrders { 33 | tradeOrder := tradeOrders[exchange] 34 | tradeOrder.Price = siteOrder.Price 35 | tradeOrder.SiteOrderID = siteOrder.ID 36 | logger.Infoln("InsertTradeOrder begin", tradeOrder) 37 | _, err = db.InsertTradeOrder(tx, tradeOrder) 38 | logger.Infoln("InsertTradeOrder end", err, tradeOrder) 39 | if err != nil { 40 | logger.Errorln("InsertTradeOrder failed", err, tradeOrder) 41 | return 42 | } 43 | } 44 | logger.Infoln("InsertTradeOrders end", len(tradeOrders), tradeOrders) 45 | 46 | return 47 | } 48 | 49 | func PushOrder(siteOrder *db.SiteOrder, tradeOrders map[string]*trade_service.TradeOrder) (err error) { 50 | tx, err := db.TxBegin() 51 | if err != nil { 52 | logger.Errorln("TxBegin failed", err) 53 | return 54 | } 55 | 56 | err = pushOrder(tx, siteOrder, tradeOrders) 57 | 58 | err = db.TxEnd(tx, err) 59 | if err != nil { 60 | logger.Errorln("TxEnd failed", err) 61 | return 62 | } 63 | 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /src/strategy/exchange.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "common" 9 | "config" 10 | "db" 11 | "errors" 12 | "fmt" 13 | "haobtc" 14 | "huobi" 15 | "logger" 16 | "okcoin" 17 | "sync" 18 | "trade_service" 19 | ) 20 | 21 | var fundExchages map[string]trade_service.Account 22 | var orderExchages map[string][]string 23 | var mutex = &sync.RWMutex{} 24 | 25 | func init() { 26 | orderExchages = make(map[string][]string) 27 | fundExchages = make(map[string]trade_service.Account) 28 | } 29 | 30 | func getConfExchanges() (markets []string) { 31 | if config.Env == config.Test { 32 | markets = []string{"huobi", "okcoin", "haobtc"} 33 | return 34 | } 35 | 36 | exchange_configs, err := db.GetExchangeConfigs() 37 | if err != nil { 38 | return 39 | } 40 | 41 | for _, value := range exchange_configs { 42 | markets = append(markets, value.Exchange) 43 | } 44 | 45 | return 46 | } 47 | 48 | func GetUsableExchange(method string, check_fund bool) (markets []string) { 49 | all_accounts, err := db.GetAccount() 50 | if err != nil { 51 | logger.Errorln(err) 52 | return 53 | } 54 | 55 | var accounts []*trade_service.Account 56 | for i := 0; i < len(all_accounts); i++ { 57 | if !(all_accounts[i].GetPauseTrade()) && (all_accounts[i].GetExchange()) != "haobtc" { 58 | accounts = append(accounts, all_accounts[i]) 59 | } 60 | } 61 | 62 | if len(accounts) == 0 { 63 | logger.Errorln("No Usable Exchange in DB:", accounts) 64 | return 65 | } 66 | 67 | if !check_fund { 68 | for i := 0; i < len(accounts); i++ { 69 | markets = append(markets, accounts[i].Exchange) 70 | } 71 | return 72 | } 73 | 74 | cny_threshold := 200000.0 75 | btc_threshold := 30.0 76 | amount_config, err := db.GetAmountConfig() 77 | if err != nil { 78 | logger.Errorln(err) 79 | // return 80 | } else { 81 | cny_threshold = amount_config.MaxCny 82 | btc_threshold = amount_config.MaxBtc 83 | } 84 | 85 | // logger.Errorln(cny_threshold, btc_threshold) 86 | 87 | for i := 0; i < len(accounts); i++ { 88 | if (method == "BUY" && accounts[i].GetAvailableCny() > cny_threshold) || 89 | (method == "SELL" && accounts[i].GetAvailableBtc() > btc_threshold) { 90 | markets = append(markets, accounts[i].Exchange) 91 | } 92 | } 93 | 94 | if len(markets) == 0 { 95 | logger.Errorln("GetUsableExchange: No UsableExchange:", method, cny_threshold, btc_threshold, accounts) 96 | } 97 | 98 | return 99 | } 100 | 101 | func _performDel(markets []string, exchange string) []string { 102 | for i := 0; i < len(markets); i++ { 103 | if markets[i] == exchange { 104 | l := len(markets) - 1 105 | markets[i] = markets[l] 106 | markets = markets[:l] 107 | } 108 | } 109 | 110 | return markets 111 | } 112 | 113 | func nextExchange(id int64, method, exchange string) []string { 114 | key := fmt.Sprintf("%d%s", id, method) 115 | mutex.Lock() 116 | defer mutex.Unlock() 117 | 118 | _, exists := orderExchages[key] 119 | // logger.Infoln(exists, exchange, key, orderExchages[key]) 120 | if !exists { 121 | exchanges := GetUsableExchange(method, false) 122 | orderExchages[key] = make([]string, len(exchanges)) 123 | copy(orderExchages[key], exchanges) 124 | } 125 | 126 | newExchanges := _performDel(orderExchages[key], exchange) 127 | 128 | orderExchages[key] = make([]string, len(newExchanges)) 129 | copy(orderExchages[key], newExchanges) 130 | 131 | // logger.Infoln(exchange, key, orderExchages[key]) 132 | return orderExchages[key] 133 | } 134 | 135 | func GetExchange(exchange string) (tradeAPI common.TradeAPI, err error) { 136 | if config.Env == config.Test { 137 | tradeAPI = common.GetMockTradeAPI(exchange) 138 | return 139 | } 140 | 141 | exchange_config, err := db.GetExchangeConfig(exchange) 142 | if err != nil { 143 | logger.Errorln(err) 144 | return 145 | } 146 | 147 | switch exchange { 148 | case "huobi": 149 | return huobi.NewExchange(exchange, exchange_config.AccessKey, exchange_config.SecretKey), nil 150 | case "okcoin": 151 | return okcoin.NewExchange(exchange, exchange_config.AccessKey, exchange_config.SecretKey), nil 152 | case "haobtc": 153 | return haobtc.NewExchange(exchange, exchange_config.AccessKey, exchange_config.SecretKey), nil 154 | default: 155 | err = errors.New("unknow exchange name") 156 | logger.Errorln("unknow exchange name?") 157 | return 158 | } 159 | 160 | return 161 | } 162 | -------------------------------------------------------------------------------- /src/strategy/handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "db" 9 | "logger" 10 | "trade_service" 11 | ) 12 | 13 | func GetOrderByClientID(client_id string) (orders []db.SiteOrder, err error) { 14 | tx, err := db.TxBegin() 15 | if err != nil { 16 | logger.Errorln("TxBegin failed", err) 17 | return 18 | } 19 | 20 | orders, err = db.GetOrderByClientID(tx, client_id) 21 | 22 | err = db.TxEnd(tx, err) 23 | if err != nil { 24 | logger.Errorln("TxEnd failed", err) 25 | return 26 | } 27 | 28 | return 29 | } 30 | 31 | func check_client_order(order *trade_service.Trade) (tradeResult_ *trade_service.TradeException, exist bool) { 32 | orders, err := GetOrderByClientID(order.GetClientID()) 33 | if err != nil { 34 | tradeResult_ := trade_service.NewTradeException() 35 | tradeResult_.Reason = trade_service.EX_INTERNAL_ERROR 36 | logger.Infoln(order, tradeResult_) 37 | return tradeResult_, false 38 | } 39 | 40 | if len(orders) > 0 { 41 | exist = true 42 | logger.Infoln("order already exist:", order, len(orders), orders) 43 | return nil, true 44 | } 45 | 46 | return nil, false 47 | } 48 | 49 | func handleTrade(trade *trade_service.Trade, tradeType trade_service.TradeType) (tradeResult_ *trade_service.TradeException) { 50 | siteOrder := &db.SiteOrder{} 51 | siteOrder.ClientID = trade.GetClientID() 52 | siteOrder.Amount = trade.GetAmount() 53 | siteOrder.Price = trade.GetPrice() 54 | siteOrder.TradeType = tradeType 55 | 56 | logger.Infoln("handleTrade begin", siteOrder) 57 | 58 | markets := GetUsableExchange(tradeType.String(), true) 59 | if len(markets) == 0 { 60 | tradeResult_ = trade_service.NewTradeException() 61 | tradeResult_.Reason = trade_service.EX_NO_USABLE_FUND 62 | 63 | logger.Errorln(tradeType.String(), tradeResult_) 64 | return 65 | } 66 | 67 | tradeOrders, tradeResult_ := estimateOrder(siteOrder, markets) 68 | if tradeResult_ != nil { 69 | logger.Infoln(siteOrder, tradeResult_) 70 | return 71 | } 72 | 73 | // if !check_price_limit(siteOrder) { 74 | // tradeResult_ = trade_service.NewTradeException() 75 | // tradeResult_.Reason = trade_service.EX_PRICE_OUT_OF_SCOPE 76 | // logger.Infoln(siteOrder, tradeResult_) 77 | // return 78 | // } 79 | 80 | err := PushOrder(siteOrder, tradeOrders) 81 | if err != nil { 82 | tradeResult_ = trade_service.NewTradeException() 83 | tradeResult_.Reason = trade_service.EX_INTERNAL_ERROR 84 | } 85 | 86 | logger.Infoln("handleTrade end", tradeResult_, siteOrder) 87 | 88 | return 89 | } 90 | 91 | func Buy(buyOrder *trade_service.Trade) *trade_service.TradeException { 92 | tradeResult_, exist := check_client_order(buyOrder) 93 | if tradeResult_ != nil { 94 | return tradeResult_ 95 | } 96 | 97 | if exist { 98 | return nil 99 | } 100 | 101 | // if !Check_ticker_limit(buyOrder.Price, trade_service.TradeType_BUY) { 102 | // tradeResult_ := trade_service.NewTradeException() 103 | // tradeResult_.Reason = trade_service.EX_PRICE_NOT_SYNC 104 | // logger.Infoln(buyOrder, tradeResult_) 105 | // return tradeResult_ 106 | // } 107 | 108 | ret := is_limit_buy(buyOrder.Amount) 109 | if ret { 110 | tradeResult_ := trade_service.NewTradeException() 111 | tradeResult_.Reason = trade_service.EX_TRADE_QUEUE_FULL 112 | logger.Infoln(tradeResult_) 113 | return tradeResult_ 114 | } 115 | 116 | tradeResult_ = handleTrade(buyOrder, trade_service.TradeType_BUY) 117 | if tradeResult_ != nil { 118 | decr_buy(buyOrder.Amount) 119 | } 120 | 121 | return tradeResult_ 122 | } 123 | 124 | func Sell(sellOrder *trade_service.Trade) *trade_service.TradeException { 125 | tradeResult_, exist := check_client_order(sellOrder) 126 | if tradeResult_ != nil { 127 | return tradeResult_ 128 | } 129 | 130 | if exist { 131 | return nil 132 | } 133 | 134 | // if !Check_ticker_limit(sellOrder.Price, trade_service.TradeType_SELL) { 135 | // tradeResult_ := trade_service.NewTradeException() 136 | // tradeResult_.Reason = trade_service.EX_PRICE_NOT_SYNC 137 | // logger.Infoln(sellOrder, tradeResult_) 138 | // return tradeResult_ 139 | // } 140 | 141 | ret := is_limit_sell(sellOrder.Amount) 142 | if ret { 143 | tradeResult_ := trade_service.NewTradeException() 144 | tradeResult_.Reason = trade_service.EX_TRADE_QUEUE_FULL 145 | logger.Infoln(tradeResult_) 146 | return tradeResult_ 147 | } 148 | 149 | tradeResult_ = handleTrade(sellOrder, trade_service.TradeType_SELL) 150 | if tradeResult_ != nil { 151 | decr_sell(sellOrder.Amount) 152 | } 153 | 154 | return tradeResult_ 155 | } 156 | -------------------------------------------------------------------------------- /src/strategy/match.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "db" 9 | "fmt" 10 | "github.com/jinzhu/gorm" 11 | "logger" 12 | "trade_service" 13 | ) 14 | 15 | func ProcessMatchTx(tx *gorm.DB) (err error) { 16 | tradeOrders, err := db.GetAllTradeOrders(tx, trade_service.OrderStatus_READY) 17 | if err != nil { 18 | logger.Errorln("GetTradeOrders READY failed...") 19 | return 20 | } 21 | 22 | if len(tradeOrders) < 2 { 23 | return 24 | } 25 | 26 | var buy_queue, sell_queue []trade_service.TradeOrder 27 | 28 | for i := 0; i < len(tradeOrders); i++ { 29 | tradeOrder := tradeOrders[i] 30 | tradeType := tradeOrder.TradeType 31 | if tradeType == trade_service.TradeType_BUY { 32 | buy_queue = append(buy_queue, tradeOrder) 33 | } else { 34 | sell_queue = append(sell_queue, tradeOrder) 35 | } 36 | } 37 | 38 | if len(buy_queue) == 0 || len(sell_queue) == 0 { 39 | return 40 | } 41 | 42 | logger.Infoln("ProcessMatch tradeOrders:") 43 | logger.Infoln(len(buy_queue), buy_queue) 44 | logger.Infoln(len(sell_queue), sell_queue) 45 | 46 | for i := 0; i < len(buy_queue); i++ { 47 | buy_tradeOrder := buy_queue[i] 48 | for j := 0; j < len(sell_queue); j++ { 49 | sell_tradeOrder := sell_queue[j] 50 | if buy_tradeOrder.EstimateBtc == sell_tradeOrder.EstimateBtc { 51 | logger.Infoln("ProcessMatch match:", buy_tradeOrder, sell_tradeOrder) 52 | sell_queue = append(sell_queue[:j], sell_queue[j+1:]...) 53 | j-- 54 | 55 | buy_tradeOrder.OrderStatus = trade_service.OrderStatus_MATCH 56 | buy_tradeOrder.MatchID = sell_tradeOrder.ID 57 | buy_tradeOrder.Memo += fmt.Sprintf("match %d:%d;", buy_tradeOrder.ID, sell_tradeOrder.ID) 58 | buy_tradeOrder.DealPrice = sell_tradeOrder.Price //only one side to match the pair price. 59 | buy_tradeOrder.DealBtc = buy_tradeOrder.EstimateBtc 60 | buy_tradeOrder.DealCny = buy_tradeOrder.EstimateCny 61 | buy_tradeOrder.PriceMargin = buy_tradeOrder.Price - buy_tradeOrder.DealPrice 62 | 63 | sell_tradeOrder.OrderStatus = trade_service.OrderStatus_MATCH 64 | sell_tradeOrder.MatchID = buy_tradeOrder.ID 65 | sell_tradeOrder.Memo += fmt.Sprintf("match %d:%d;", sell_tradeOrder.ID, buy_tradeOrder.ID) 66 | sell_tradeOrder.DealPrice = sell_tradeOrder.Price 67 | sell_tradeOrder.DealBtc = sell_tradeOrder.EstimateBtc 68 | sell_tradeOrder.DealCny = sell_tradeOrder.EstimateCny 69 | 70 | decr_buy(buy_tradeOrder.DealCny) 71 | decr_sell(sell_tradeOrder.DealBtc) 72 | 73 | err = db.UpdateTradeOrder(tx, buy_tradeOrder) 74 | if err != nil { 75 | logger.Errorln("ProcessMatch UpdateTradeOrder buy failed", err, buy_tradeOrder) 76 | return 77 | } 78 | 79 | err = db.UpdateTradeOrder(tx, sell_tradeOrder) 80 | if err != nil { 81 | logger.Errorln("ProcessMatch UpdateTradeOrder sell failed", err, sell_tradeOrder) 82 | return 83 | } 84 | 85 | break 86 | } 87 | } 88 | } 89 | 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /src/strategy/orderbook.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "common" 9 | "container/list" 10 | "db" 11 | "fmt" 12 | "logger" 13 | "trade_service" 14 | ) 15 | 16 | type ExchangeOrder struct { 17 | Exchange string 18 | Amount float64 19 | Price float64 20 | } 21 | 22 | type SumExchangeOrder struct { 23 | Amount float64 24 | Price float64 25 | ExchangeOrder map[string]*ExchangeOrder //key is exchange 26 | } 27 | 28 | func mergeDepth(exchange, depth_type string, marketOrders []common.MarketOrder, depthList *list.List) (err error) { 29 | size := len(marketOrders) 30 | 31 | for i := 0; i < size; i++ { 32 | marketOrder := marketOrders[i] 33 | 34 | e := depthList.Front() 35 | for nil != e { 36 | if e.Value.(*SumExchangeOrder).Price == marketOrder.Price { 37 | //combine to the depth list item, just needd update the list item, so just break in the end. 38 | sumExchangeOrder := e.Value.(*SumExchangeOrder) 39 | 40 | if sumExchangeOrder.ExchangeOrder == nil { 41 | //cannot arrive here, just in case 42 | logger.Errorln("mergeDepth exception.") 43 | sumExchangeOrder.ExchangeOrder = make(map[string]*ExchangeOrder) 44 | } 45 | 46 | if sumExchangeOrder.ExchangeOrder[exchange] == nil { 47 | sumExchangeOrder.ExchangeOrder[exchange] = new(ExchangeOrder) 48 | } 49 | 50 | sumExchangeOrder.ExchangeOrder[exchange].Exchange = exchange 51 | sumExchangeOrder.ExchangeOrder[exchange].Price = marketOrder.Price 52 | sumExchangeOrder.ExchangeOrder[exchange].Amount = marketOrder.Amount 53 | sumExchangeOrder.Amount += marketOrder.Amount 54 | sumExchangeOrder.Price = marketOrder.Price 55 | 56 | break 57 | } else if (depth_type == "asks" && marketOrder.Price < e.Value.(*SumExchangeOrder).Price) || 58 | (depth_type == "bids" && marketOrder.Price > e.Value.(*SumExchangeOrder).Price) { 59 | 60 | sumExchangeOrder := new(SumExchangeOrder) 61 | sumExchangeOrder.ExchangeOrder = make(map[string]*ExchangeOrder) 62 | sumExchangeOrder.ExchangeOrder[exchange] = new(ExchangeOrder) 63 | sumExchangeOrder.ExchangeOrder[exchange].Exchange = exchange 64 | sumExchangeOrder.ExchangeOrder[exchange].Price = marketOrder.Price 65 | sumExchangeOrder.ExchangeOrder[exchange].Amount = marketOrder.Amount 66 | sumExchangeOrder.Amount += marketOrder.Amount 67 | sumExchangeOrder.Price = marketOrder.Price 68 | 69 | depthList.InsertBefore(sumExchangeOrder, e) 70 | 71 | break 72 | } 73 | e = e.Next() 74 | } 75 | 76 | //the biggest,put @v on the back of the list 77 | if nil == e { 78 | sumExchangeOrder := new(SumExchangeOrder) 79 | sumExchangeOrder.ExchangeOrder = make(map[string]*ExchangeOrder) 80 | sumExchangeOrder.ExchangeOrder[exchange] = new(ExchangeOrder) 81 | sumExchangeOrder.ExchangeOrder[exchange].Exchange = exchange 82 | sumExchangeOrder.ExchangeOrder[exchange].Price = marketOrder.Price 83 | sumExchangeOrder.ExchangeOrder[exchange].Amount = marketOrder.Amount 84 | sumExchangeOrder.Amount += marketOrder.Amount 85 | sumExchangeOrder.Price = marketOrder.Price 86 | 87 | depthList.PushBack(sumExchangeOrder) 88 | } 89 | } 90 | 91 | return 92 | } 93 | 94 | func GetMergeDepth(markets []string) (asks, bids *list.List, newMarkets []string, err error) { 95 | asks = list.New() 96 | bids = list.New() 97 | 98 | errcount := 0 99 | for i := 0; i < len(markets); i++ { 100 | exchange := markets[i] 101 | orderbook, _err := db.GetDepth(exchange) 102 | if _err != nil { 103 | errcount++ 104 | err = _err 105 | logger.Errorln("GetMergeDepth err:", exchange, err, len(markets), errcount) 106 | continue 107 | } 108 | 109 | // okcoin except special progress 110 | size := len(orderbook.Asks) 111 | if size > 0 && orderbook.Asks[common.DEPTH-1].Price < 0.000001 { 112 | logger.Errorln("GetMergeDepth exception orderbook:", exchange, orderbook) 113 | continue 114 | } 115 | 116 | newMarkets = append(newMarkets, exchange) 117 | mergeDepth(exchange, "asks", orderbook.Asks[:], asks) 118 | mergeDepth(exchange, "bids", orderbook.Bids[:], bids) 119 | } 120 | 121 | if errcount < len(markets) { 122 | err = nil 123 | } else { 124 | logger.Errorln("GetMergeDepth failed, restart", len(markets), errcount) 125 | return 126 | // os.Exit(-1) //triger supervisor restart to fix the broken net connection. 127 | } 128 | 129 | return 130 | } 131 | 132 | func PrintDepthList(depthList *list.List, markets []string) { 133 | return 134 | 135 | logger.Infoln("analyzeDepth depthList", depthList) 136 | depthCount := 0 137 | for e := depthList.Front(); e != nil; e = e.Next() { 138 | sumExchangeOrder := e.Value.(*SumExchangeOrder) 139 | depthCount++ 140 | logger.Infoln(depthCount, sumExchangeOrder.Amount, sumExchangeOrder.Price) 141 | for i := 0; i < len(markets); i++ { 142 | exchange := markets[i] 143 | if sumExchangeOrder.ExchangeOrder[exchange] != nil { 144 | logger.Infoln(depthCount, sumExchangeOrder.ExchangeOrder[exchange]) 145 | } 146 | } 147 | } 148 | 149 | logger.Infoln("depthCount:", depthCount) 150 | } 151 | 152 | func analyzeDepth(nbtc float64, depthList *list.List, markets []string) (tradeOrders map[string]*trade_service.TradeOrder) { 153 | sum_btc := 0.0 154 | 155 | tradeOrders = make(map[string]*trade_service.TradeOrder) 156 | for e := depthList.Front(); e != nil; e = e.Next() { 157 | sumExchangeOrder := e.Value.(*SumExchangeOrder) 158 | price := sumExchangeOrder.Price 159 | // amount := sumExchangeOrder.Amount 160 | 161 | for i := 0; i < len(markets); i++ { 162 | exchange := markets[i] 163 | //logger.Infoln(i, exchange) 164 | 165 | if sumExchangeOrder.ExchangeOrder[exchange] == nil { 166 | continue 167 | } 168 | 169 | if tradeOrders[exchange] == nil { 170 | tradeOrders[exchange] = new(trade_service.TradeOrder) 171 | } 172 | 173 | full_fill := false 174 | left_vol := sumExchangeOrder.ExchangeOrder[exchange].Amount 175 | if sum_btc+left_vol > nbtc { 176 | left_vol = nbtc - sum_btc 177 | full_fill = true 178 | } 179 | 180 | tradeOrders[exchange].EstimateBtc += left_vol 181 | tradeOrders[exchange].EstimatePrice = price 182 | tradeOrders[exchange].EstimateCny += left_vol * price 183 | sum_btc += left_vol 184 | 185 | if full_fill { 186 | return 187 | } 188 | } 189 | } 190 | 191 | return 192 | } 193 | 194 | func analyzeAskDepth(nbtc float64, markets []string) (tradeOrders map[string]*trade_service.TradeOrder, err error) { 195 | asks, _, newMarkets, err := GetMergeDepth(markets) 196 | if err != nil { 197 | logger.Errorln(err) 198 | return 199 | } 200 | 201 | PrintDepthList(asks, newMarkets) 202 | 203 | tradeOrders = analyzeDepth(nbtc, asks, newMarkets) 204 | 205 | return 206 | } 207 | 208 | func analyzeBidDepth(nbtc float64, markets []string) (tradeOrders map[string]*trade_service.TradeOrder, err error) { 209 | _, bids, newMarkets, err := GetMergeDepth(markets) 210 | if err != nil { 211 | logger.Errorln(err) 212 | return 213 | } 214 | 215 | PrintDepthList(bids, newMarkets) 216 | 217 | tradeOrders = analyzeDepth(nbtc, bids, newMarkets) 218 | 219 | return 220 | } 221 | 222 | func estimateOrder(siteOrder *db.SiteOrder, markets []string) (tradeOrders map[string]*trade_service.TradeOrder, tradeResult_ *trade_service.TradeException) { 223 | amount := siteOrder.Amount 224 | tradeType := siteOrder.TradeType 225 | 226 | logger.Infoln("estimateOrder markets:", tradeType, amount, markets) 227 | if len(markets) == 0 { 228 | tradeResult_ = trade_service.NewTradeException() 229 | tradeResult_.Reason = trade_service.EX_NO_USABLE_FUND 230 | 231 | logger.Errorln(tradeType.String(), tradeResult_) 232 | return 233 | } 234 | 235 | var err error 236 | if tradeType == trade_service.TradeType_BUY { 237 | tradeOrders, err = analyzeAskDepth(amount, markets) 238 | } else { 239 | tradeOrders, err = analyzeBidDepth(amount, markets) 240 | } 241 | 242 | if err != nil { 243 | logger.Errorln(err) 244 | tradeResult_ = trade_service.NewTradeException() 245 | tradeResult_.Reason = trade_service.EX_NO_USABLE_DEPTH 246 | return 247 | } 248 | 249 | estimate_btc := 0.0 250 | estimate_cny := 0.0 251 | estimate_price := 0.0 252 | for exchange, _ := range tradeOrders { 253 | tradeOrder := tradeOrders[exchange] 254 | tradeOrder.SiteOrderID = siteOrder.ID 255 | tradeOrder.Exchange = exchange 256 | tradeOrder.TradeType = tradeType 257 | 258 | if use_time_weighted_algorithm { 259 | tradeOrder.OrderStatus = trade_service.OrderStatus_TIME_WEIGHTED 260 | } else { 261 | tradeOrder.OrderStatus = trade_service.OrderStatus_READY 262 | } 263 | estimate_cny += tradeOrder.EstimateCny 264 | estimate_btc += tradeOrder.EstimateBtc 265 | 266 | tradePrice := fmt.Sprintf("%0.2f", tradeOrder.EstimatePrice) 267 | tradeBTC := fmt.Sprintf("%0.4f", tradeOrder.EstimateBtc) 268 | tradeCNY := fmt.Sprintf("%0.2f", tradeOrder.EstimateCny) 269 | 270 | logger.Infoln("estimateOrder trade:", exchange, tradePrice, tradeBTC, tradeCNY) 271 | } 272 | 273 | if len(tradeOrders) > 1 { 274 | toExchange := "" 275 | for exchange, _ := range tradeOrders { 276 | tradeOrder := tradeOrders[exchange] 277 | if tradeOrder.EstimateBtc > unit_min_amount { 278 | toExchange = exchange 279 | break 280 | } 281 | } 282 | 283 | if toExchange != "" { 284 | for exchange, _ := range tradeOrders { 285 | tradeOrder := tradeOrders[exchange] 286 | if tradeOrder.EstimateBtc <= unit_min_amount { 287 | if toExchange != exchange { 288 | logger.Debugln("combine trade begin", tradeType.String(), tradeOrders[toExchange].EstimatePrice, tradeOrders) 289 | logger.Debugln("to add trade", toExchange, tradeOrders[toExchange]) 290 | logger.Debugln("to del trade", exchange, tradeOrder) 291 | tradeOrder.Exchange = toExchange 292 | tradeOrders[toExchange].EstimateBtc += tradeOrders[exchange].EstimateBtc 293 | if tradeType == trade_service.TradeType_BUY { 294 | if tradeOrders[toExchange].EstimatePrice < tradeOrders[exchange].EstimatePrice { 295 | tradeOrders[toExchange].EstimatePrice = tradeOrders[exchange].EstimatePrice 296 | } 297 | } else { 298 | if tradeOrders[toExchange].EstimatePrice > tradeOrders[exchange].EstimatePrice { 299 | tradeOrders[toExchange].EstimatePrice = tradeOrders[exchange].EstimatePrice 300 | } 301 | } 302 | 303 | tradeOrders[toExchange].EstimateCny += tradeOrders[exchange].EstimateCny 304 | 305 | delete(tradeOrders, exchange) 306 | 307 | logger.Debugln("combine trade end", tradeOrders[toExchange].EstimatePrice, tradeOrders) 308 | } 309 | } 310 | } 311 | } 312 | } 313 | 314 | if estimate_btc+0.01 < amount { 315 | tradeResult_ = trade_service.NewTradeException() 316 | tradeResult_.Reason = trade_service.EX_DEPTH_INSUFFICIENT 317 | 318 | logger.Errorln(tradeResult_, estimate_btc, amount) 319 | return 320 | } 321 | 322 | if estimate_btc > 0.01 { 323 | estimate_price = estimate_cny / estimate_btc 324 | } else { 325 | tradeResult_ = trade_service.NewTradeException() 326 | tradeResult_.Reason = trade_service.EX_INTERNAL_ERROR 327 | 328 | logger.Errorln(tradeResult_, estimate_btc, siteOrder) 329 | return 330 | } 331 | 332 | logger.Infoln("estimateOrder result:", amount, siteOrder.TradeType, estimate_cny, estimate_btc, estimate_price) 333 | 334 | siteOrder.EstimatePrice = estimate_price 335 | siteOrder.EstimateCny = estimate_cny 336 | siteOrder.EstimateBtc = estimate_btc 337 | 338 | if use_time_weighted_algorithm { 339 | siteOrder.OrderStatus = trade_service.OrderStatus_TIME_WEIGHTED 340 | } else { 341 | siteOrder.OrderStatus = trade_service.OrderStatus_READY 342 | } 343 | 344 | tradeResult_ = nil 345 | 346 | return 347 | } 348 | -------------------------------------------------------------------------------- /src/strategy/price_risk.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader risk 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "config" 9 | "db" 10 | "logger" 11 | "math" 12 | "strconv" 13 | "trade_service" 14 | "util" 15 | ) 16 | 17 | const MeasurementError = 0.000001 18 | 19 | func check_price_limit(siteOrder *db.SiteOrder) bool { 20 | _price_threshold, prs := config.Config["price_threshold"] 21 | if !prs { 22 | _price_threshold = "5" 23 | } 24 | 25 | price_threshold := util.ToFloat(_price_threshold) 26 | 27 | if siteOrder.TradeType == trade_service.TradeType_BUY { 28 | if siteOrder.EstimatePrice-siteOrder.Price > price_threshold { 29 | return false 30 | } 31 | } else { 32 | if siteOrder.Price-siteOrder.EstimatePrice > price_threshold { 33 | return false 34 | } 35 | } 36 | 37 | return true 38 | } 39 | 40 | func Check_ticker_limit(price float64, tradeType trade_service.TradeType) bool { 41 | if price < MeasurementError { 42 | return false 43 | } 44 | 45 | _ticker_compare_count, prs := config.Config["ticker_compare_count"] 46 | if !prs { 47 | _ticker_compare_count = "3" 48 | } 49 | 50 | ticker_compare_count, err := strconv.ParseInt(_ticker_compare_count, 10, 64) 51 | if err != nil { 52 | ticker_compare_count = 3 53 | } 54 | 55 | tickers, err := db.GetNTickers(int(ticker_compare_count)) 56 | if err != nil { 57 | logger.Infoln("GetNTickers err:", err) 58 | return false 59 | } 60 | 61 | if len(tickers) < 1 { 62 | logger.Infoln(tickers, "tickers is empty") 63 | return false 64 | } 65 | 66 | // logger.Infoln(len(tickers), tickers) 67 | 68 | isOurTicker := false 69 | for i := 0; i < len(tickers); i++ { 70 | ticker_price := 0.0 71 | if tradeType == trade_service.TradeType_BUY { 72 | ticker_price = tickers[i].Ask 73 | } else if tradeType == trade_service.TradeType_SELL { 74 | ticker_price = tickers[i].Bid 75 | } else { 76 | logger.Infoln("invalid trade type", tradeType) 77 | return false 78 | } 79 | 80 | logger.Infoln("check ticker limit:", ticker_price, price, math.Abs(ticker_price-price), MeasurementError) 81 | if math.Abs(ticker_price-price) < MeasurementError { 82 | isOurTicker = true 83 | break 84 | } 85 | } 86 | 87 | return isOurTicker 88 | } 89 | -------------------------------------------------------------------------------- /src/strategy/ticker_timer.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "logger" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var tickers map[string]*time.Ticker 14 | var tickers_mutex = &sync.Mutex{} 15 | 16 | func init() { 17 | tickers = make(map[string]*time.Ticker) 18 | } 19 | 20 | func TaskTemplateTicker(seconds time.Duration, f func(exchange string) error, exchange string) { 21 | ticker := time.NewTicker(seconds * time.Second) // one second 22 | defer ticker.Stop() 23 | 24 | tickers_mutex.Lock() 25 | if tickers[exchange] != nil { 26 | logger.Errorln("is not nil", exchange, tickers[exchange], f) 27 | tickers_mutex.Unlock() 28 | return 29 | } 30 | 31 | tickers[exchange] = ticker 32 | tickers_mutex.Unlock() 33 | 34 | for t := range ticker.C { 35 | logger.Debugln(exchange, t) 36 | f(exchange) 37 | } 38 | } 39 | 40 | func Update_tickers() { 41 | update_exchanges := getConfExchanges() 42 | 43 | for current_exchange, _ := range tickers { 44 | is_remove := true 45 | for _, update_exchange := range update_exchanges { 46 | if current_exchange == update_exchange { 47 | is_remove = false 48 | break 49 | } 50 | } 51 | 52 | if is_remove { 53 | logger.Infoln("to del ticker", current_exchange) 54 | tickers_mutex.Lock() 55 | if _, ret := tickers[current_exchange]; ret { 56 | tickers[current_exchange].Stop() 57 | delete(tickers, current_exchange) 58 | } 59 | tickers_mutex.Unlock() 60 | } 61 | } 62 | 63 | for _, update_exchange := range update_exchanges { 64 | if _, ret := tickers[update_exchange]; !ret { 65 | logger.Infoln("to add ticker", update_exchange) 66 | go TaskTemplateTicker(1, _queryExchangeData, update_exchange) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/strategy/time_weighted.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "db" 9 | "fmt" 10 | "github.com/jinzhu/gorm" 11 | "logger" 12 | "trade_service" 13 | ) 14 | 15 | func processTimeWeighted(tx *gorm.DB, tradeOrder trade_service.TradeOrder) (err error) { 16 | exchange := tradeOrder.Exchange 17 | 18 | tradePrice := fmt.Sprintf("%0.2f", tradeOrder.EstimatePrice) 19 | tradeBTC := fmt.Sprintf("%0.4f", tradeOrder.EstimateBtc) 20 | tradeCNY := fmt.Sprintf("%0.2f", tradeOrder.EstimateCny) 21 | tradeType := tradeOrder.TradeType 22 | logger.Infoln("processTimeWeighted", exchange, tradeType, tradePrice, tradeBTC, tradeCNY) 23 | 24 | cur_unit_amount := unit_max_amount 25 | logger.Infoln("rm factor,cur_unit_amount:", cur_unit_amount) 26 | 27 | if tradeOrder.EstimateBtc < 2*cur_unit_amount { 28 | tradeOrder.OrderStatus = trade_service.OrderStatus_READY 29 | } else { 30 | tradeOrder.OrderStatus = trade_service.OrderStatus_SPLIT 31 | 32 | count := (int)(tradeOrder.EstimateBtc / cur_unit_amount) 33 | avg_price := tradeOrder.EstimateCny / tradeOrder.EstimateBtc 34 | 35 | logger.Infoln("processTimeWeighted", count, avg_price, cur_unit_amount) 36 | for i := 0; i < count; i++ { 37 | sub_tradeOrder := tradeOrder //延续/覆盖原有订单的信息 38 | //更新新信息 39 | btc_amount := 0.0 40 | if count == i+1 { 41 | btc_amount = tradeOrder.EstimateBtc - cur_unit_amount*float64(i) 42 | } else { 43 | btc_amount = cur_unit_amount 44 | } 45 | 46 | sub_tradeOrder.EstimatePrice = avg_price 47 | sub_tradeOrder.EstimateBtc = btc_amount 48 | sub_tradeOrder.EstimateCny = btc_amount * avg_price 49 | sub_tradeOrder.OrderStatus = trade_service.OrderStatus_READY 50 | logger.Infoln("InsertTradeOrder sub_tradeOrder begin", sub_tradeOrder) 51 | _, err = db.InsertTradeOrder(tx, &sub_tradeOrder) 52 | logger.Infoln("InsertTradeOrder sub_tradeOrder end", err, sub_tradeOrder) 53 | if err != nil { 54 | logger.Errorln("InsertTradeOrder sub_tradeOrder failed", err, sub_tradeOrder) 55 | return 56 | } 57 | } 58 | } 59 | 60 | logger.Infoln("processTimeWeighted UpdateTradeOrder begin:", tradeOrder) 61 | err = db.UpdateTradeOrder(tx, tradeOrder) 62 | if err != nil { 63 | logger.Errorln("processTimeWeighted UpdateTradeOrder failed", err, tradeOrder) 64 | return 65 | } 66 | logger.Infoln("processTimeWeighted UpdateTradeOrder end:", tradeOrder) 67 | 68 | return 69 | } 70 | 71 | func ProcessTimeWeighted(tx *gorm.DB) (err error) { 72 | tradeOrders, err := db.GetAllTradeOrders(tx, trade_service.OrderStatus_TIME_WEIGHTED) 73 | if err != nil { 74 | logger.Errorln("GetTradeOrders OrderStatus_TIME_WEIGHTED failed...") 75 | return 76 | } 77 | 78 | if len(tradeOrders) == 0 { 79 | return 80 | } 81 | 82 | logger.Infoln("processTimeWeighted tradeOrders", len(tradeOrders), tradeOrders) 83 | for key, _ := range tradeOrders { 84 | tradeOrder := tradeOrders[key] 85 | err = processTimeWeighted(tx, tradeOrder) 86 | if err != nil { 87 | return 88 | } 89 | } 90 | 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /src/strategy/trade_center.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "config" 9 | "db" 10 | "logger" 11 | "sync" 12 | "time" 13 | "util" 14 | ) 15 | 16 | const use_time_weighted_algorithm = true 17 | const enable_match = false 18 | 19 | var unit_min_amount, unit_max_amount float64 20 | var tickerFSM map[string]int 21 | var tickerFSM_mutex = &sync.Mutex{} 22 | 23 | func init() { 24 | tickerFSM = make(map[string]int) 25 | } 26 | 27 | func TaskTemplate(seconds time.Duration, f func()) { 28 | ticker := time.NewTicker(seconds * time.Second) // one second 29 | defer ticker.Stop() 30 | 31 | for _ = range ticker.C { 32 | f() 33 | } 34 | } 35 | 36 | func before_enter_task() { 37 | db.Init_sqlstr(config.Config["sqlconn"]) 38 | 39 | unit_min_amount = util.ToFloat(config.Config["unit_min_amount"]) 40 | if unit_min_amount < 0.01 { 41 | unit_min_amount = 0.01 42 | } 43 | 44 | unit_max_amount = util.ToFloat(config.Config["unit_max_amount"]) 45 | if unit_max_amount < unit_min_amount { 46 | unit_max_amount = 1 47 | } 48 | 49 | logger.Infoln("unit_min_amount,unit_max_amount:", unit_min_amount, unit_max_amount) 50 | 51 | initTotalReadyAmount() 52 | } 53 | 54 | func TradeCenter() { 55 | 56 | before_enter_task() 57 | // tickers, err := db.GetNTickers(3) 58 | // logger.Infoln(tickers, err) 59 | // return 60 | 61 | markets := getConfExchanges() 62 | for i := 0; i < len(markets); i++ { 63 | exchange := markets[i] 64 | 65 | tickerFSM[exchange] = 0 66 | 67 | _queryExchangeData(exchange) 68 | go TaskTemplateTicker(2, _queryDepth, exchange) 69 | go TaskTemplateTrader(2, ProgressFSM, exchange) 70 | } 71 | 72 | // go TaskTemplate(5, QueryTicker) 73 | go watchdogTicker() 74 | 75 | if use_time_weighted_algorithm { 76 | go TaskTemplate(1, ProcessDispathMatch) 77 | } 78 | } 79 | 80 | func ProcessDispathMatch() { 81 | db.TXWrapper(ProcessTimeWeighted) 82 | 83 | if enable_match { 84 | db.TXWrapper(ProcessMatchTx) 85 | } 86 | } 87 | 88 | func ProgressFSM(exchange string) (err error) { 89 | tickerFSM_mutex.Lock() 90 | tickerFSM[exchange]++ 91 | tickerFSM_mutex.Unlock() 92 | 93 | if tickerFSM[exchange] >= 10 { 94 | _queryFund(exchange) 95 | tickerFSM_mutex.Lock() 96 | tickerFSM[exchange] = 0 97 | tickerFSM_mutex.Unlock() 98 | } 99 | 100 | db.TXWrapperEx(ProcessReady, exchange) 101 | 102 | db.TXWrapperEx(ProcessOrdered, exchange) 103 | db.TXWrapperEx(ProcessTimeout, exchange) 104 | 105 | return 106 | } 107 | 108 | // monitor via recover 109 | type message struct { 110 | normal bool // true means exit normal, otherwise 111 | state map[string]interface{} // goroutine state 112 | } 113 | 114 | func worker(mess chan message) { 115 | defer func() { 116 | exit_message := message{state: make(map[string]interface{})} 117 | err := recover() 118 | if err != nil { 119 | logger.Errorln("worker recover err:", err) 120 | exit_message.normal = false 121 | } else { 122 | exit_message.normal = true 123 | } 124 | mess <- exit_message 125 | }() 126 | 127 | TaskTemplate(3, QueryTicker) 128 | } 129 | 130 | func supervisor(mess chan message) { 131 | m := <-mess 132 | switch m.normal { 133 | case true: 134 | logger.Errorln("exit normal, nothing serious!") 135 | case false: 136 | logger.Errorln("exit abnormal, something went wrong") 137 | } 138 | } 139 | 140 | func watchdogTicker() { 141 | mess := make(chan message, 1) 142 | 143 | go worker(mess) 144 | 145 | supervisor(mess) 146 | } 147 | -------------------------------------------------------------------------------- /src/strategy/trader_timer.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader strategy 3 | */ 4 | 5 | package strategy 6 | 7 | import ( 8 | "logger" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var traders map[string]*time.Ticker 14 | var traders_mutex = &sync.Mutex{} 15 | 16 | func init() { 17 | traders = make(map[string]*time.Ticker) 18 | } 19 | 20 | func TaskTemplateTrader(seconds time.Duration, f func(exchange string) error, exchange string) { 21 | ticker := time.NewTicker(seconds * time.Second) // one second 22 | defer ticker.Stop() 23 | 24 | traders_mutex.Lock() 25 | if traders[exchange] != nil { 26 | logger.Errorln("is not nil", exchange, traders[exchange], f) 27 | traders_mutex.Unlock() 28 | return 29 | } 30 | 31 | traders[exchange] = ticker 32 | traders_mutex.Unlock() 33 | 34 | for t := range ticker.C { 35 | logger.Debugln(exchange, t) 36 | f(exchange) 37 | } 38 | } 39 | 40 | func Update_traders() { 41 | update_exchanges := getConfExchanges() 42 | 43 | for _, update_exchange := range update_exchanges { 44 | if _, ret := traders[update_exchange]; !ret { 45 | logger.Infoln("to add trader", update_exchange) 46 | go TaskTemplateTrader(1, ProgressFSM, update_exchange) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/trade_client/trade_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "time" 8 | "trade_service" 9 | 10 | "github.com/apache/thrift/lib/go/thrift" 11 | ) 12 | 13 | const ( 14 | OKCoin_api_key string = "" 15 | OKCoin_secret_key string = "" 16 | Huobi_access_key string = "" 17 | Huobi_secret_key string = "" 18 | Chbtc_access_key string = "" 19 | Chbtc_secret_key string = "" 20 | ) 21 | 22 | func main() { 23 | startTime := currentTimeMillis() 24 | transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory()) 25 | protocolFactory := thrift.NewTBinaryProtocolFactoryDefault() 26 | 27 | transport, err := thrift.NewTSocket(net.JoinHostPort("127.0.0.1", "19090")) 28 | if err != nil { 29 | fmt.Fprintln(os.Stderr, "error resolving address:", err) 30 | os.Exit(1) 31 | } 32 | 33 | useTransport := transportFactory.GetTransport(transport) 34 | client := trade_service.NewTradeServiceClientFactory(useTransport, protocolFactory) 35 | if err := transport.Open(); err != nil { 36 | fmt.Fprintln(os.Stderr, "Error opening socket to 127.0.0.1:19090", " ", err) 37 | os.Exit(1) 38 | } 39 | defer transport.Close() 40 | 41 | // for i := 0; i < 1000; i++ { 42 | // paramMap := make(map[string]string) 43 | // paramMap["name"] = "qinerg" 44 | // paramMap["passwd"] = "123456" 45 | // r1, e1 := client.FunCall(currentTimeMillis(), "login", paramMap) 46 | // fmt.Println(i, "Call->", r1, e1) 47 | // } 48 | if true { 49 | var configs []*trade_service.ExchangeConfig 50 | hb_config := new(trade_service.ExchangeConfig) 51 | 52 | hb_config.Exchange = "huobi" 53 | hb_config.AccessKey = Huobi_access_key 54 | hb_config.SecretKey = Huobi_secret_key 55 | configs = append(configs, hb_config) 56 | 57 | ok_config := new(trade_service.ExchangeConfig) 58 | ok_config.Exchange = "okcoin" 59 | ok_config.AccessKey = OKCoin_api_key 60 | ok_config.SecretKey = OKCoin_secret_key 61 | configs = append(configs, ok_config) 62 | 63 | ch_config := new(trade_service.ExchangeConfig) 64 | ch_config.Exchange = "chbtc" 65 | ch_config.AccessKey = Chbtc_access_key 66 | ch_config.SecretKey = Chbtc_secret_key 67 | configs = append(configs, ch_config) 68 | 69 | fmt.Println("configs->", configs) 70 | err = client.Config(configs) 71 | fmt.Println("Config<-", err) 72 | } 73 | 74 | accounts, err := client.GetAccount() 75 | fmt.Println("accounts->", err, accounts) 76 | 77 | ticker, err := client.GetTicker() 78 | fmt.Println("ticker->", err, ticker) 79 | 80 | for i := 0; i < 1; i++ { 81 | ticker, err := client.GetTicker() 82 | fmt.Println("ticker->", err, ticker) 83 | 84 | if true { 85 | var buyOrder trade_service.Trade 86 | buyOrder.ClientID = "10" 87 | buyOrder.Amount = 50 88 | order, err := client.Buy(&buyOrder) 89 | fmt.Println("buy->", err, order) 90 | //time.Sleep(1 * time.Second) 91 | 92 | order, err = client.GetOrder(order.ID) 93 | fmt.Println("buy order->", err, order) 94 | } 95 | 96 | if true { 97 | var sellOrder trade_service.Trade 98 | sellOrder.ClientID = "1" 99 | sellOrder.Amount = 0.015 100 | order, err := client.Sell(&sellOrder) 101 | fmt.Println("Sell->", err, order) 102 | //time.Sleep(1 * time.Second) 103 | 104 | order, err = client.GetOrder(order.ID) 105 | fmt.Println("Sell order->", err, order) 106 | } 107 | } 108 | 109 | accounts, err = client.GetAccount() 110 | fmt.Println("accounts->", err, accounts) 111 | 112 | alertOrders, err := client.GetAlertOrders() 113 | fmt.Println("errorOrders->", err, len(alertOrders)) 114 | 115 | // for i := 0; i < 10; i++ { 116 | // order, err := client.Buy("1000000") 117 | // fmt.Println("Buy->", err, order) 118 | // time.Sleep(3 * time.Second) 119 | // } 120 | 121 | endTime := currentTimeMillis() 122 | fmt.Println("Program exit. time->", endTime, startTime, (endTime - startTime)) 123 | } 124 | 125 | // 转换成毫秒 126 | func currentTimeMillis() int64 { 127 | return time.Now().UnixNano() / 1000000 128 | } 129 | -------------------------------------------------------------------------------- /src/trade_client/trade_client.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../lib') 4 | 5 | from trade_service import TradeService 6 | from trade_service.ttypes import * 7 | 8 | from thrift import Thrift 9 | from thrift.transport import TSocket 10 | from thrift.transport import TTransport 11 | from thrift.protocol import TBinaryProtocol 12 | 13 | 14 | OKCoin_api_key = "" 15 | OKCoin_secret_key = "" 16 | Huobi_access_key = "" 17 | Huobi_secret_key = "" 18 | Chbtc_access_key = "" 19 | Chbtc_secret_key = "" 20 | 21 | 22 | try: 23 | 24 | # Make socket 25 | transport = TSocket.TSocket("127.0.0.1", 19090) 26 | 27 | # Buffering is critical. Raw sockets are very slow 28 | transport = TTransport.TFramedTransport(transport) 29 | 30 | # Wrap in a protocol 31 | protocol = TBinaryProtocol.TBinaryProtocol(transport) 32 | 33 | # Create a client to use the protocol encoder 34 | client = TradeService.Client(protocol) 35 | 36 | # Connect! 37 | transport.open() 38 | 39 | if True: 40 | configs = [] 41 | hb_config = ExchangeConfig("huobi", Huobi_access_key, Huobi_secret_key) 42 | configs.append(hb_config) 43 | 44 | ok_config = ExchangeConfig() 45 | ok_config.exchange = "okcoin" 46 | ok_config.access_key = OKCoin_api_key 47 | ok_config.secret_key = OKCoin_secret_key 48 | configs.append(ok_config) 49 | 50 | ch_config = ExchangeConfig() 51 | ch_config.exchange = "chbtc" 52 | ch_config.access_key = Chbtc_access_key 53 | ch_config.secret_key = Chbtc_secret_key 54 | configs.append(ch_config) 55 | 56 | print("config->", configs) 57 | client.config(configs) 58 | 59 | accounts = client.get_account() 60 | print("accounts->", accounts) 61 | 62 | ticker= client.get_ticker() 63 | print("ticker->", ticker) 64 | 65 | for i in range(1, 2): 66 | ticker= client.get_ticker() 67 | print("ticker->", i, ticker) 68 | 69 | if True: 70 | buyOrder = Trade("20", 5900) 71 | order= client.buy(buyOrder) 72 | print("buy->",order) 73 | 74 | order= client.get_order(order.id) 75 | print("buy order->", order) 76 | 77 | if False: 78 | sellOrder = Trade("2", 0.015) 79 | order= client.sell(sellOrder) 80 | print("Sell->", order) 81 | 82 | order = client.get_order(order.id) 83 | print("Sell order->", order) 84 | 85 | alertOrders= client.get_alert_orders() 86 | print("errorOrders->", len(alertOrders)) 87 | 88 | # Close! 89 | transport.close() 90 | 91 | except Thrift.TException, tx: 92 | print '%s' % (tx.message) -------------------------------------------------------------------------------- /src/trade_server/trade_handler.go: -------------------------------------------------------------------------------- 1 | package trade_server 2 | 3 | import ( 4 | "db" 5 | "logger" 6 | "strategy" 7 | "trade_service" 8 | ) 9 | 10 | type TradeServiceHandler struct { 11 | } 12 | 13 | func (this *TradeServiceHandler) Ping() (err error) { 14 | return nil 15 | } 16 | 17 | func (this *TradeServiceHandler) ConfigKeys(exchange_configs []*trade_service.ExchangeConfig) (err error) { 18 | logger.Infoln("-->ConfigKeys begin:") 19 | 20 | if err = db.SetExchangeConfig(exchange_configs); err == nil { 21 | strategy.Update_tickers() 22 | strategy.Update_traders() 23 | } 24 | 25 | logger.Infoln("-->ConfigKeys end:", err) 26 | return 27 | } 28 | 29 | func (this *TradeServiceHandler) ConfigAmount(amount_config *trade_service.AmountConfig) (err error) { 30 | logger.Infoln("-->ConfigAmount begin:") 31 | err = db.SetAmountConfig(amount_config) 32 | logger.Infoln("-->ConfigAmount end:", err) 33 | return 34 | } 35 | 36 | func (this *TradeServiceHandler) CheckPrice(price float64, trade_type trade_service.TradeType) (err error) { 37 | if !strategy.Check_ticker_limit(price, trade_type) { 38 | tradeResult_ := trade_service.NewTradeException() 39 | tradeResult_.Reason = trade_service.EX_PRICE_NOT_SYNC 40 | logger.Infoln(price, trade_type, tradeResult_) 41 | return tradeResult_ 42 | } 43 | 44 | return nil 45 | } 46 | 47 | // Parameters: 48 | // - Cny 49 | func (this *TradeServiceHandler) Buy(buyOrder *trade_service.Trade) (err error) { 50 | logger.Infoln("-->Buy begin:", buyOrder) 51 | 52 | tradeResult_ := strategy.Buy(buyOrder) 53 | 54 | logger.Infoln("-->Buy end:", buyOrder, tradeResult_) 55 | return tradeResult_ 56 | } 57 | 58 | // Parameters: 59 | // - Btc 60 | func (this *TradeServiceHandler) Sell(sellOrder *trade_service.Trade) (err error) { 61 | logger.Infoln("-->Sell begin:", sellOrder) 62 | 63 | tradeResult_ := strategy.Sell(sellOrder) 64 | 65 | logger.Infoln("-->Sell end:", sellOrder, tradeResult_) 66 | return tradeResult_ 67 | } 68 | 69 | func (this *TradeServiceHandler) GetAccount() (r []*trade_service.Account, err error) { 70 | logger.Infoln("-->GetAccount begin:") 71 | r, err = db.GetAccount() 72 | if r == nil { 73 | r = make([]*trade_service.Account, 0) 74 | } 75 | 76 | logger.Infoln("-->GetAccount end:", r, err) 77 | return 78 | } 79 | 80 | func (this *TradeServiceHandler) GetTicker() (r *trade_service.Ticker, err error) { 81 | // logger.Infoln("-->GetTicker begin:") 82 | r, err = db.GetTicker() 83 | // logger.Infoln("-->GetTicker end:", r, err) 84 | return 85 | } 86 | 87 | func (this *TradeServiceHandler) GetAlertOrders() (err error) { 88 | logger.Infoln("-->GetAlertOrders begin:") 89 | tradeOrders, err := db.GetAlertOrders() 90 | if err == nil && len(tradeOrders) > 0 { 91 | tradeResult_ := trade_service.NewTradeException() 92 | tradeResult_.Reason = trade_service.EX_EXIST_ERROR_ORDERS 93 | logger.Infoln("-->GetAlertOrders end:", tradeResult_) 94 | return tradeResult_ 95 | } 96 | 97 | logger.Infoln("-->GetAlertOrders end:", err) 98 | return 99 | } 100 | 101 | func (this *TradeServiceHandler) GetExchangeStatus() (r *trade_service.ExchangeStatus, err error) { 102 | logger.Infoln("-->GetExchangeStatus begin:") 103 | 104 | r = trade_service.NewExchangeStatus() 105 | markets := strategy.GetUsableExchange(trade_service.TradeType_BUY.String(), true) 106 | if len(markets) == 0 { 107 | r.Canbuy = false 108 | } else { 109 | r.Canbuy = true 110 | } 111 | 112 | markets = strategy.GetUsableExchange(trade_service.TradeType_SELL.String(), true) 113 | if len(markets) == 0 { 114 | r.Cansell = false 115 | } else { 116 | r.Cansell = true 117 | } 118 | 119 | logger.Infoln("-->GetExchangeStatus end:", err, r) 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /src/trade_server/trade_server.go: -------------------------------------------------------------------------------- 1 | package trade_server 2 | 3 | import ( 4 | "github.com/apache/thrift/lib/go/thrift" 5 | "logger" 6 | "os" 7 | "time" 8 | "trade_service" 9 | ) 10 | 11 | type TraderServer struct { 12 | host string 13 | handler *TradeServiceHandler 14 | processor *trade_service.TradeServiceProcessor 15 | transport *thrift.TServerSocket 16 | transportFactory thrift.TTransportFactory 17 | protocolFactory *thrift.TBinaryProtocolFactory 18 | server *thrift.TSimpleServer 19 | } 20 | 21 | func NewTraderServer(host string) *TraderServer { 22 | transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory()) 23 | protocolFactory := thrift.NewTBinaryProtocolFactoryDefault() 24 | //protocolFactory := thrift.NewTCompactProtocolFactory() 25 | 26 | transport, err := thrift.NewTServerSocketTimeout(host, 30*time.Second) 27 | if err != nil { 28 | logger.Infoln("NewTServerSocketTimeout Error!", err) 29 | os.Exit(1) 30 | } 31 | 32 | handler := &TradeServiceHandler{} 33 | processor := trade_service.NewTradeServiceProcessor(handler) 34 | 35 | server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory) 36 | return &TraderServer{ 37 | host: host, 38 | handler: handler, 39 | processor: processor, 40 | transport: transport, 41 | transportFactory: transportFactory, 42 | protocolFactory: protocolFactory, 43 | server: server, 44 | } 45 | } 46 | 47 | func (ts *TraderServer) Run() { 48 | logger.Infoln("Thrift server listening on", ts.host) 49 | ts.server.Serve() 50 | } 51 | 52 | func (ts *TraderServer) Stop() { 53 | logger.Infoln("Thrift stopping server...") 54 | ts.server.Stop() 55 | } 56 | -------------------------------------------------------------------------------- /src/util/buffer.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "logger" 6 | "strconv" 7 | ) 8 | 9 | // 内嵌bytes.Buffer,支持连写 10 | type Buffer struct { 11 | *bytes.Buffer 12 | } 13 | 14 | func NewBuffer() *Buffer { 15 | return &Buffer{Buffer: new(bytes.Buffer)} 16 | } 17 | 18 | func (this *Buffer) Append(s string) *Buffer { 19 | defer func() { 20 | if err := recover(); err != nil { 21 | logger.Errorln("*****内存不够了!******") 22 | } 23 | }() 24 | this.Buffer.WriteString(s) 25 | return this 26 | } 27 | 28 | func (this *Buffer) AppendInt(i int) *Buffer { 29 | return this.Append(strconv.Itoa(i)) 30 | } 31 | -------------------------------------------------------------------------------- /src/util/convert.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "reflect" 7 | "strconv" 8 | ) 9 | 10 | func ToString(s interface{}) string { 11 | if v, ok := s.(string); ok { 12 | return v 13 | } 14 | return fmt.Sprintf("%v", s) 15 | } 16 | 17 | func ToFloat(s interface{}) float64 { 18 | var ret float64 19 | switch v := s.(type) { 20 | case float64: 21 | ret = v 22 | case int64: 23 | ret = float64(v) 24 | case string: 25 | ret, _ = strconv.ParseFloat(v, 64) 26 | } 27 | return ret 28 | } 29 | 30 | func float2str(i float64) string { 31 | return strconv.FormatFloat(i, 'f', -1, 64) 32 | } 33 | 34 | // 将url.Values(表单数据)转换为Model(struct) 35 | func ConvertAssign(dest interface{}, form url.Values) error { 36 | destType := reflect.TypeOf(dest) 37 | if destType.Kind() != reflect.Ptr { 38 | return fmt.Errorf("convertAssign(non-pointer %s)", destType) 39 | } 40 | destValue := reflect.Indirect(reflect.ValueOf(dest)) 41 | if destValue.Kind() != reflect.Struct { 42 | return fmt.Errorf("convertAssign(non-struct %s)", destType) 43 | } 44 | destType = destValue.Type() 45 | fieldNum := destType.NumField() 46 | for i := 0; i < fieldNum; i++ { 47 | // struct 字段的反射类型(StructField) 48 | fieldType := destType.Field(i) 49 | // 非导出字段不处理 50 | if fieldType.PkgPath != "" { 51 | continue 52 | } 53 | tag := fieldType.Tag.Get("json") 54 | fieldValue := destValue.Field(i) 55 | val := form.Get(tag) 56 | // 字段本身的反射类型(field type) 57 | fieldValType := fieldType.Type 58 | switch fieldValType.Kind() { 59 | case reflect.Int: 60 | if len(form[tag]) > 1 { 61 | // TODO:多个值如何处理? 62 | } 63 | if val == "" { 64 | continue 65 | } 66 | tmp, err := strconv.Atoi(val) 67 | if err != nil { 68 | return err 69 | } 70 | fieldValue.SetInt(int64(tmp)) 71 | case reflect.String: 72 | if len(form[tag]) > 1 { 73 | // TODO:多个值如何处理? 74 | } 75 | fieldValue.SetString(val) 76 | default: 77 | 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | func Struct2Map(dest map[string]interface{}, src interface{}) error { 84 | if dest == nil { 85 | return fmt.Errorf("Struct2Map(dest is %v)", dest) 86 | } 87 | srcType := reflect.TypeOf(src) 88 | srcValue := reflect.Indirect(reflect.ValueOf(src)) 89 | if srcValue.Kind() != reflect.Struct { 90 | return fmt.Errorf("Struct2Map(non-struct %s)", srcType) 91 | } 92 | srcType = srcValue.Type() 93 | fieldNum := srcType.NumField() 94 | for i := 0; i < fieldNum; i++ { 95 | // struct 字段的反射类型(StructField) 96 | fieldType := srcType.Field(i) 97 | // 非导出字段不处理 98 | if fieldType.PkgPath != "" { 99 | continue 100 | } 101 | tag := fieldType.Tag.Get("json") 102 | fieldValue := srcValue.Field(i) 103 | dest[tag] = fieldValue.Interface() 104 | } 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /src/util/crypto.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/rand" 6 | "fmt" 7 | "io" 8 | "time" 9 | ) 10 | 11 | func Md5(text string) string { 12 | hashMd5 := md5.New() 13 | io.WriteString(hashMd5, text) 14 | return fmt.Sprintf("%x", hashMd5.Sum(nil)) 15 | } 16 | 17 | // 产生唯一的id 18 | func GenUUID() string { 19 | buf := make([]byte, 16) 20 | io.ReadFull(rand.Reader, buf) 21 | return fmt.Sprintf("%x%x", buf, time.Now().UnixNano()) 22 | } 23 | -------------------------------------------------------------------------------- /src/util/httpclient.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | type NetConfig struct { 10 | ConnectTimeout time.Duration 11 | ReadWriteTimeout time.Duration 12 | } 13 | 14 | func TimeoutDialer(config *NetConfig) func(net, addr string) (c net.Conn, err error) { 15 | return func(netw, addr string) (net.Conn, error) { 16 | conn, err := net.DialTimeout(netw, addr, config.ConnectTimeout) 17 | if err != nil { 18 | return nil, err 19 | } 20 | conn.SetDeadline(time.Now().Add(config.ReadWriteTimeout)) 21 | return conn, nil 22 | } 23 | } 24 | 25 | func NewTimeoutClient(args ...interface{}) *http.Client { 26 | // Default configuration 27 | config := &NetConfig{ 28 | ConnectTimeout: 10 * time.Second, 29 | ReadWriteTimeout: 10 * time.Second, 30 | } 31 | 32 | // merge the default with user input if there is one 33 | if len(args) == 1 { 34 | timeout := args[0].(time.Duration) 35 | config.ConnectTimeout = timeout 36 | config.ReadWriteTimeout = timeout 37 | } 38 | 39 | if len(args) == 2 { 40 | config.ConnectTimeout = args[0].(time.Duration) 41 | config.ReadWriteTimeout = args[1].(time.Duration) 42 | } 43 | 44 | return &http.Client{ 45 | Transport: &http.Transport{ 46 | Dial: TimeoutDialer(config), 47 | }, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/util/httprequest.go: -------------------------------------------------------------------------------- 1 | /* 2 | trader API Engine 3 | */ 4 | 5 | package util 6 | 7 | import ( 8 | "compress/gzip" 9 | "io" 10 | "io/ioutil" 11 | "logger" 12 | "net/http" 13 | "strings" 14 | ) 15 | 16 | func http_req(req *http.Request) (body string, err error) { 17 | 18 | logger.Debugln("http_req req:", req) 19 | 20 | c := NewTimeoutClient() 21 | 22 | resp, err := c.Do(req) 23 | if err != nil { 24 | logger.Errorln(err) 25 | return 26 | } 27 | defer resp.Body.Close() 28 | 29 | logger.Debugln("http_req resp:", resp) 30 | if resp.StatusCode%200 != 0 { 31 | logger.Errorln("http_req resp:", resp) 32 | return 33 | } 34 | 35 | if resp.StatusCode != 200 { 36 | logger.Infoln("http_req resp:", resp.StatusCode) 37 | } 38 | 39 | contentEncoding := resp.Header.Get("Content-Encoding") 40 | switch contentEncoding { 41 | case "gzip": 42 | body = DumpGZIP(resp.Body) 43 | default: 44 | bodyByte, err := ioutil.ReadAll(resp.Body) 45 | if err != nil { 46 | logger.Errorln("read the http stream failed") 47 | } else { 48 | body = string(bodyByte) 49 | } 50 | } 51 | 52 | logger.Debugln("http_req body:", body) 53 | return 54 | } 55 | 56 | func HttpPost(api_url, req_para string) (body string, err error) { 57 | req_para_reader := strings.NewReader(req_para) 58 | 59 | req, err := http.NewRequest("POST", api_url, req_para_reader) 60 | if err != nil { 61 | logger.Errorln(err) 62 | return 63 | } 64 | 65 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 66 | 67 | return http_req(req) 68 | } 69 | 70 | func HttpGet(url string) (body string, err error) { 71 | req, err := http.NewRequest("GET", url, nil) 72 | if err != nil { 73 | logger.Errorln(err) 74 | return 75 | } 76 | 77 | return http_req(req) 78 | } 79 | 80 | func DumpGZIP(r io.Reader) string { 81 | var body string 82 | reader, _ := gzip.NewReader(r) 83 | for { 84 | buf := make([]byte, 1024) 85 | n, err := reader.Read(buf) 86 | 87 | if err != nil && err != io.EOF { 88 | panic(err) 89 | } 90 | 91 | if n == 0 { 92 | break 93 | } 94 | body += string(buf) 95 | } 96 | return body 97 | } 98 | -------------------------------------------------------------------------------- /src/util/map.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // 获取map的key,返回所有key组成的slice 4 | func MapKeys(data map[string]interface{}) []string { 5 | keys := make([]string, 0, len(data)) 6 | for key, _ := range data { 7 | keys = append(keys, key) 8 | } 9 | return keys 10 | } 11 | 12 | // 获取map的key,返回所有key组成的slice 13 | func MapIntKeys(data map[int]int) []int { 14 | keys := make([]int, 0, len(data)) 15 | for key, _ := range data { 16 | keys = append(keys, key) 17 | } 18 | return keys 19 | } 20 | -------------------------------------------------------------------------------- /src/util/mapsort.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | type MapSorter []Item 4 | 5 | type Item struct { 6 | Key string 7 | Val string 8 | } 9 | 10 | func NewMapSorter(m map[string]string) MapSorter { 11 | ms := make(MapSorter, 0, len(m)) 12 | 13 | for k, v := range m { 14 | ms = append(ms, Item{k, v}) 15 | } 16 | 17 | return ms 18 | } 19 | 20 | func (ms MapSorter) Len() int { 21 | return len(ms) 22 | } 23 | 24 | func (ms MapSorter) Less(i, j int) bool { 25 | //return ms[i].Val < ms[j].Val // 按值排序 26 | return ms[i].Key < ms[j].Key // 按键排序 27 | } 28 | 29 | func (ms MapSorter) Swap(i, j int) { 30 | ms[i], ms[j] = ms[j], ms[i] 31 | } 32 | -------------------------------------------------------------------------------- /src/util/string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package utf8string provides an efficient way to index strings by rune rather than by byte. 6 | package util 7 | 8 | import ( 9 | "errors" 10 | "logger" 11 | "unicode/utf8" 12 | ) 13 | 14 | // String wraps a regular string with a small structure that provides more 15 | // efficient indexing by code point index, as opposed to byte index. 16 | // Scanning incrementally forwards or backwards is O(1) per index operation 17 | // (although not as fast a range clause going forwards). Random access is 18 | // O(N) in the length of the string, but the overhead is less than always 19 | // scanning from the beginning. 20 | // If the string is ASCII, random access is O(1). 21 | // Unlike the built-in string type, String has internal mutable state and 22 | // is not thread-safe. 23 | type String struct { 24 | str string 25 | numRunes int 26 | // If width > 0, the rune at runePos starts at bytePos and has the specified width. 27 | width int 28 | bytePos int 29 | runePos int 30 | nonASCII int // byte index of the first non-ASCII rune. 31 | } 32 | 33 | // NewString returns a new UTF-8 string with the provided contents. 34 | func NewString(contents string) *String { 35 | return new(String).Init(contents) 36 | } 37 | 38 | // Init initializes an existing String to hold the provided contents. 39 | // It returns a pointer to the initialized String. 40 | func (s *String) Init(contents string) *String { 41 | s.str = contents 42 | s.bytePos = 0 43 | s.runePos = 0 44 | for i := 0; i < len(contents); i++ { 45 | if contents[i] >= utf8.RuneSelf { 46 | // Not ASCII. 47 | s.numRunes = utf8.RuneCountInString(contents) 48 | _, s.width = utf8.DecodeRuneInString(contents) 49 | s.nonASCII = i 50 | return s 51 | } 52 | } 53 | // ASCII is simple. Also, the empty string is ASCII. 54 | s.numRunes = len(contents) 55 | s.width = 0 56 | s.nonASCII = len(contents) 57 | return s 58 | } 59 | 60 | // String returns the contents of the String. This method also means the 61 | // String is directly printable by fmt.Print. 62 | func (s *String) String() string { 63 | return s.str 64 | } 65 | 66 | // RuneCount returns the number of runes (Unicode code points) in the String. 67 | func (s *String) RuneCount() int { 68 | return s.numRunes 69 | } 70 | 71 | // IsASCII returns a boolean indicating whether the String contains only ASCII bytes. 72 | func (s *String) IsASCII() bool { 73 | return s.width == 0 74 | } 75 | 76 | // Slice returns the string sliced at rune positions [i:j]. 77 | func (s *String) Slice(i, j int) string { 78 | // ASCII is easy. Let the compiler catch the indexing error if there is one. 79 | if j < s.nonASCII { 80 | return s.str[i:j] 81 | } 82 | if i < 0 || j > s.numRunes || i > j { 83 | logger.Errorln("sliceOutOfRange") 84 | panic(sliceOutOfRange) 85 | } 86 | if i == j { 87 | return "" 88 | } 89 | // For non-ASCII, after At(i), bytePos is always the position of the indexed character. 90 | var low, high int 91 | switch { 92 | case i < s.nonASCII: 93 | low = i 94 | case i == s.numRunes: 95 | low = len(s.str) 96 | default: 97 | s.At(i) 98 | low = s.bytePos 99 | } 100 | switch { 101 | case j == s.numRunes: 102 | high = len(s.str) 103 | default: 104 | s.At(j) 105 | high = s.bytePos 106 | } 107 | return s.str[low:high] 108 | } 109 | 110 | // At returns the rune with index i in the String. The sequence of runes is the same 111 | // as iterating over the contents with a "for range" clause. 112 | func (s *String) At(i int) rune { 113 | // ASCII is easy. Let the compiler catch the indexing error if there is one. 114 | if i < s.nonASCII { 115 | return rune(s.str[i]) 116 | } 117 | 118 | // Now we do need to know the index is valid. 119 | if i < 0 || i >= s.numRunes { 120 | logger.Errorln("At sliceOutOfRange") 121 | panic(outOfRange) 122 | } 123 | 124 | var r rune 125 | 126 | // Five easy common cases: within 1 spot of bytePos/runePos, or the beginning, or the end. 127 | // With these cases, all scans from beginning or end work in O(1) time per rune. 128 | switch { 129 | 130 | case i == s.runePos-1: // backing up one rune 131 | r, s.width = utf8.DecodeLastRuneInString(s.str[0:s.bytePos]) 132 | s.runePos = i 133 | s.bytePos -= s.width 134 | return r 135 | case i == s.runePos+1: // moving ahead one rune 136 | s.runePos = i 137 | s.bytePos += s.width 138 | fallthrough 139 | case i == s.runePos: 140 | r, s.width = utf8.DecodeRuneInString(s.str[s.bytePos:]) 141 | return r 142 | case i == 0: // start of string 143 | r, s.width = utf8.DecodeRuneInString(s.str) 144 | s.runePos = 0 145 | s.bytePos = 0 146 | return r 147 | 148 | case i == s.numRunes-1: // last rune in string 149 | r, s.width = utf8.DecodeLastRuneInString(s.str) 150 | s.runePos = i 151 | s.bytePos = len(s.str) - s.width 152 | return r 153 | } 154 | 155 | // We need to do a linear scan. There are three places to start from: 156 | // 1) The beginning 157 | // 2) bytePos/runePos. 158 | // 3) The end 159 | // Choose the closest in rune count, scanning backwards if necessary. 160 | forward := true 161 | if i < s.runePos { 162 | // Between beginning and pos. Which is closer? 163 | // Since both i and runePos are guaranteed >= nonASCII, that's the 164 | // lowest location we need to start from. 165 | if i < (s.runePos-s.nonASCII)/2 { 166 | // Scan forward from beginning 167 | s.bytePos, s.runePos = s.nonASCII, s.nonASCII 168 | } else { 169 | // Scan backwards from where we are 170 | forward = false 171 | } 172 | } else { 173 | // Between pos and end. Which is closer? 174 | if i-s.runePos < (s.numRunes-s.runePos)/2 { 175 | // Scan forward from pos 176 | } else { 177 | // Scan backwards from end 178 | s.bytePos, s.runePos = len(s.str), s.numRunes 179 | forward = false 180 | } 181 | } 182 | if forward { 183 | // TODO: Is it much faster to use a range loop for this scan? 184 | for { 185 | r, s.width = utf8.DecodeRuneInString(s.str[s.bytePos:]) 186 | if s.runePos == i { 187 | break 188 | } 189 | s.runePos++ 190 | s.bytePos += s.width 191 | } 192 | } else { 193 | for { 194 | r, s.width = utf8.DecodeLastRuneInString(s.str[0:s.bytePos]) 195 | s.runePos-- 196 | s.bytePos -= s.width 197 | if s.runePos == i { 198 | break 199 | } 200 | } 201 | } 202 | return r 203 | } 204 | 205 | var outOfRange = errors.New("utf8string: index out of range") 206 | var sliceOutOfRange = errors.New("utf8string: slice index out of range") 207 | -------------------------------------------------------------------------------- /src/util/tool.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // 必须是int类型,否则panic 11 | func MustInt(s string) int { 12 | i, err := strconv.Atoi(s) 13 | if err != nil { 14 | panic(err) 15 | } 16 | return i 17 | } 18 | 19 | // 将in slice通过sep连接起来 20 | func Join(ins []int, sep string) string { 21 | strSlice := make([]string, len(ins)) 22 | for i, one := range ins { 23 | strSlice[i] = strconv.Itoa(one) 24 | } 25 | return strings.Join(strSlice, sep) 26 | } 27 | 28 | func Ip2long(ipstr string) uint32 { 29 | ip := net.ParseIP(ipstr) 30 | if ip == nil { 31 | return 0 32 | } 33 | ip = ip.To4() 34 | return binary.BigEndian.Uint32(ip) 35 | } 36 | -------------------------------------------------------------------------------- /src/util/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The btcbot Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // https://github.com/philsong/ 5 | // Author:Phil 78623269@qq.com 6 | 7 | package util 8 | 9 | import ( 10 | "fmt" 11 | "logger" 12 | "math" 13 | "math/rand" 14 | "os" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | const MIN = 0.000001 20 | 21 | // MIN 为用户自定义的比较精度 22 | func IsEqual(f1, f2 float64) bool { 23 | return math.Dim(f1, f2) < MIN 24 | } 25 | 26 | func AddRecord(record, filename string) { 27 | file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777) 28 | if err != nil { 29 | return 30 | } 31 | defer file.Close() 32 | 33 | file.WriteString(fmt.Sprintf("%s\n", record)) 34 | } 35 | 36 | func RandomString(l int) string { 37 | rand.Seed(time.Now().UnixNano()) 38 | var result string 39 | 40 | for i := 0; i < l; i++ { 41 | result += string(randdigit()) 42 | } 43 | return result 44 | } 45 | 46 | func randdigit() uint8 { 47 | answers := "0123456789" 48 | 49 | return answers[rand.Intn(len(answers))] 50 | } 51 | 52 | func IntegerToString(value int64) (s string) { 53 | s = strconv.FormatInt(value, 10) 54 | return 55 | } 56 | 57 | func StringToInteger(s string) (value int64) { 58 | value, err := strconv.ParseInt(s, 10, 64) 59 | if err != nil { 60 | value = 0 61 | } 62 | return 63 | } 64 | 65 | func FloatToString(value float64) (s string) { 66 | s = strconv.FormatFloat(value, 'f', -1, 64) 67 | return 68 | } 69 | 70 | func StringToFloat(in string) float64 { 71 | out, err := strconv.ParseFloat(in, 64) 72 | if err != nil { 73 | logger.Errorln("don't know the type, crash!", in) 74 | } 75 | 76 | return out 77 | } 78 | 79 | func InterfaceToFloat64(iv interface{}) (retV float64) { 80 | switch ivTo := iv.(type) { 81 | case float64: 82 | retV = ivTo 83 | case string: 84 | { 85 | var err error 86 | retV, err = strconv.ParseFloat(ivTo, 64) 87 | if err != nil { 88 | logger.Errorln("convert failed, crash!") 89 | return 0 90 | } 91 | } 92 | default: 93 | logger.Errorln(ivTo) 94 | logger.Errorln("don't know the type, crash!") 95 | return 0 96 | } 97 | 98 | return retV 99 | } 100 | 101 | func Exist(filename string) bool { 102 | _, err := os.Stat(filename) 103 | return err == nil || os.IsExist(err) 104 | } 105 | 106 | func DeleteFile(filepath string) { 107 | if Exist(filepath) { 108 | os.Remove(filepath) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/util/validate.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // 校验表单数据 12 | func Validate(data url.Values, rules map[string]map[string]map[string]string) (errMsg string) { 13 | for field, rule := range rules { 14 | val := data.Get(field) 15 | // 检查【必填】 16 | if requireInfo, ok := rule["require"]; ok { 17 | if val == "" { 18 | errMsg = requireInfo["error"] 19 | return 20 | } 21 | } 22 | // 检查【长度】 23 | if lengthInfo, ok := rule["length"]; ok { 24 | valLen := len(val) 25 | if lenRange, ok := lengthInfo["range"]; ok { 26 | errMsg = checkRange(valLen, lenRange, lengthInfo["error"]) 27 | if errMsg != "" { 28 | return 29 | } 30 | } 31 | } 32 | // 检查【int类型】以及可能的范围 33 | if intInfo, ok := rule["int"]; ok { 34 | valInt, err := strconv.Atoi(val) 35 | if err != nil { 36 | errMsg = field + "类型错误!" 37 | return 38 | } 39 | if intRange, ok := intInfo["range"]; ok { 40 | errMsg = checkRange(valInt, intRange, intInfo["error"]) 41 | if errMsg != "" { 42 | return 43 | } 44 | } 45 | } 46 | // 检查【邮箱】 47 | if emailInfo, ok := rule["email"]; ok { 48 | validEmail := regexp.MustCompile(`^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$`) 49 | if !validEmail.MatchString(val) { 50 | errMsg = emailInfo["error"] 51 | return 52 | } 53 | } 54 | // 检查【两值比较】 55 | if compareInfo, ok := rule["compare"]; ok { 56 | compared := compareInfo["field"] // 被比较的字段 57 | // 比较规则 58 | switch compareInfo["rule"] { 59 | case "=": 60 | if val != data.Get(compared) { 61 | errMsg = compareInfo["error"] 62 | return 63 | } 64 | case "<": 65 | case ">": 66 | default: 67 | 68 | } 69 | } 70 | } 71 | return 72 | } 73 | 74 | // checkRange 检查范围值是否合法。 75 | // src为要检查的值;destRange为目标范围;msg出错时信息参数 76 | func checkRange(src int, destRange string, msg string) (errMsg string) { 77 | parts := strings.SplitN(destRange, ",", 2) 78 | parts[0] = strings.TrimSpace(parts[0]) 79 | parts[1] = strings.TrimSpace(parts[1]) 80 | min, max := 0, 0 81 | if parts[0] == "" { 82 | max = MustInt(parts[1]) 83 | if src > max { 84 | errMsg = fmt.Sprintf(msg, max) 85 | return 86 | } 87 | } 88 | if parts[1] == "" { 89 | min = MustInt(parts[0]) 90 | if src < min { 91 | errMsg = fmt.Sprintf(msg, min) 92 | return 93 | } 94 | } 95 | if min == 0 { 96 | min = MustInt(parts[0]) 97 | } 98 | if max == 0 { 99 | max = MustInt(parts[1]) 100 | } 101 | if src < min || src > max { 102 | errMsg = fmt.Sprintf(msg, min, max) 103 | return 104 | } 105 | return 106 | } 107 | --------------------------------------------------------------------------------