aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gopkg.lock22
-rw-r--r--Gopkg.toml8
-rw-r--r--api/market_config.go52
-rw-r--r--api/routes.go11
-rw-r--r--cmd/web/js/api.js11
-rw-r--r--markets/poloniex.go149
6 files changed, 251 insertions, 2 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 7f0f166..88c2eda 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -78,6 +78,12 @@
78 78
79[[projects]] 79[[projects]]
80 branch = "master" 80 branch = "master"
81 name = "github.com/jloup/poloniex"
82 packages = ["."]
83 revision = "e75e6fd7991c1d71576ad97de73fc922f24a5fd2"
84
85[[projects]]
86 branch = "master"
81 name = "github.com/jloup/utils" 87 name = "github.com/jloup/utils"
82 packages = ["."] 88 packages = ["."]
83 revision = "6055a8f761d5892502228aa62249e122f8bd392d" 89 revision = "6055a8f761d5892502228aa62249e122f8bd392d"
@@ -99,6 +105,12 @@
99 version = "v1.0.0" 105 version = "v1.0.0"
100 106
101[[projects]] 107[[projects]]
108 branch = "master"
109 name = "github.com/shopspring/decimal"
110 packages = ["."]
111 revision = "e3482495ff4cba75613e4177ed79825c890058a9"
112
113[[projects]]
102 name = "github.com/ugorji/go" 114 name = "github.com/ugorji/go"
103 packages = ["codec"] 115 packages = ["codec"]
104 revision = "9831f2c3ac1068a78f50999a30db84270f647af6" 116 revision = "9831f2c3ac1068a78f50999a30db84270f647af6"
@@ -112,7 +124,13 @@
112 "blowfish", 124 "blowfish",
113 "ssh/terminal" 125 "ssh/terminal"
114 ] 126 ]
115 revision = "650f4a345ab4e5b245a3034b110ebc7299e68186" 127 revision = "432090b8f568c018896cd8a0fb0345872bbac6ce"
128
129[[projects]]
130 branch = "master"
131 name = "golang.org/x/net"
132 packages = ["websocket"]
133 revision = "cbe0f9307d0156177f9dd5dc85da1a31abc5f2fb"
116 134
117[[projects]] 135[[projects]]
118 branch = "master" 136 branch = "master"
@@ -138,6 +156,6 @@
138[solve-meta] 156[solve-meta]
139 analyzer-name = "dep" 157 analyzer-name = "dep"
140 analyzer-version = 1 158 analyzer-version = 1
141 inputs-digest = "c9af022a586632799c6259f6c48eef8dad7080e36b96e8cb5cb905b316c4cb9b" 159 inputs-digest = "d3c9b3094ed174bcf1631e3a998a75d557c65c195d7a8fd5ca9912f71f334ce1"
142 solver-name = "gps-cdcl" 160 solver-name = "gps-cdcl"
143 solver-version = 1 161 solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index f4686b5..4feee4d 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -56,3 +56,11 @@
56[prune] 56[prune]
57 go-tests = true 57 go-tests = true
58 unused-packages = true 58 unused-packages = true
59
60[[constraint]]
61 branch = "master"
62 name = "github.com/jloup/poloniex"
63
64[[constraint]]
65 branch = "master"
66 name = "github.com/shopspring/decimal"
diff --git a/api/market_config.go b/api/market_config.go
index 3fd10ae..ce79184 100644
--- a/api/market_config.go
+++ b/api/market_config.go
@@ -3,7 +3,9 @@ package api
3import ( 3import (
4 "fmt" 4 "fmt"
5 5
6 "github.com/shopspring/decimal"
6 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db" 7 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db"
8 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/markets"
7) 9)
8 10
9type MarketConfigQuery struct { 11type MarketConfigQuery struct {
@@ -42,6 +44,56 @@ func (q MarketConfigQuery) Run() (interface{}, *Error) {
42 return config.Config, nil 44 return config.Config, nil
43} 45}
44 46
47type MarketBalanceQuery struct {
48 In struct {
49 User db.User
50 Market string
51 Currency string
52 }
53}
54
55var Poloniex *markets.Poloniex
56
57func init() {
58 Poloniex = markets.NewPoloniex()
59 Poloniex.StartTicker()
60}
61
62func (q MarketBalanceQuery) ValidateParams() *Error {
63 if q.In.Market != "poloniex" {
64 return &Error{BadRequest, "invalid market name", fmt.Errorf("'%v' is not a valid market name", q.In.Market)}
65 }
66
67 // TODO: we should request market for available currencies.
68 if q.In.Currency != "BTC" || q.In.Currency != "USDT" {
69 return &Error{BadRequest, "invalid currency, accept [BTC, USDT]", fmt.Errorf("'%v' is not a valid currency", q.In.Currency)}
70 }
71
72 return nil
73}
74
75func (q MarketBalanceQuery) Run() (interface{}, *Error) {
76 config, err := db.GetUserMarketConfig(q.In.User.Id, q.In.Market)
77 if err != nil {
78 return nil, NewInternalError(err)
79 }
80
81 if config.Config["key"] == "" || config.Config["secret"] == "" {
82 return nil, &Error{BadRequest, "your credentials for this market are not setup", fmt.Errorf("'%v' credentials are not setup", q.In.Market)}
83 }
84
85 balance, err := Poloniex.GetBalance(config.Config["key"], config.Config["secret"], q.In.Currency)
86 if err != nil {
87 return nil, NewInternalError(err)
88 }
89
90 result := struct {
91 Balance decimal.Decimal `json:balance`
92 }{balance}
93
94 return &result, nil
95}
96
45type UpdateMarketConfigQuery struct { 97type UpdateMarketConfigQuery struct {
46 In struct { 98 In struct {
47 User db.User 99 User db.User
diff --git a/api/routes.go b/api/routes.go
index d7e712c..385db9a 100644
--- a/api/routes.go
+++ b/api/routes.go
@@ -41,6 +41,7 @@ var Groups = []Group{
41 []Route{ 41 []Route{
42 {"GET", []gin.HandlerFunc{GetMarketConfig}, "/:name"}, 42 {"GET", []gin.HandlerFunc{GetMarketConfig}, "/:name"},
43 {"POST", []gin.HandlerFunc{UpdateMarketConfig}, "/:name/update"}, 43 {"POST", []gin.HandlerFunc{UpdateMarketConfig}, "/:name/update"},
44 {"GET", []gin.HandlerFunc{GetMarketBalance}, "/:name/balance"},
44 }, 45 },
45 }, 46 },
46} 47}
@@ -111,6 +112,16 @@ func GetMarketConfig(c *gin.Context) {
111 RunQuery(query, c) 112 RunQuery(query, c)
112} 113}
113 114
115func GetMarketBalance(c *gin.Context) {
116 query := &MarketBalanceQuery{}
117
118 query.In.User = GetUser(c)
119 query.In.Market = c.Param("name")
120 query.In.Currency = "BTC"
121
122 RunQuery(query, c)
123}
124
114func UpdateMarketConfig(c *gin.Context) { 125func UpdateMarketConfig(c *gin.Context) {
115 query := &UpdateMarketConfigQuery{} 126 query := &UpdateMarketConfigQuery{}
116 127
diff --git a/cmd/web/js/api.js b/cmd/web/js/api.js
index e2acd1d..5c19fdf 100644
--- a/cmd/web/js/api.js
+++ b/cmd/web/js/api.js
@@ -53,6 +53,17 @@ var ApiEndpoints = {
53 return '/market/' + params.name; 53 return '/market/' + params.name;
54 } 54 }
55 }, 55 },
56 'MARKET_BALANCE': {
57 'type': 'GET',
58 'auth': true,
59 'parameters': [
60 {'name': 'name', 'mandatory': true, 'inquery': false},
61 {'name': 'currency', 'mandatory': true, 'inquery': true},
62 ],
63 'buildUrl': function(params) {
64 return '/market/' + params.name + '/balance';
65 }
66 },
56 'UPDATE_MARKET': { 67 'UPDATE_MARKET': {
57 'type': 'POST', 68 'type': 'POST',
58 'auth': true, 69 'auth': true,
diff --git a/markets/poloniex.go b/markets/poloniex.go
new file mode 100644
index 0000000..c55a3da
--- /dev/null
+++ b/markets/poloniex.go
@@ -0,0 +1,149 @@
1package markets
2
3import (
4 "fmt"
5
6 "github.com/jloup/poloniex"
7 "github.com/shopspring/decimal"
8)
9
10type CurrencyPair struct {
11 Name string
12 Rate decimal.Decimal
13}
14
15type Poloniex struct {
16 TickerCache map[string]CurrencyPair
17
18 publicClient *poloniex.Poloniex
19 updateTickerChan chan CurrencyPair
20}
21
22func NewPoloniex() *Poloniex {
23 client, _ := poloniex.NewClient("", "")
24
25 return &Poloniex{
26 TickerCache: make(map[string]CurrencyPair),
27 updateTickerChan: nil,
28 publicClient: client,
29 }
30}
31
32func (p *Poloniex) GetBalance(apiKey, apiSecret, base_currency string) (decimal.Decimal, error) {
33 client, _ := poloniex.NewClient(apiKey, apiSecret)
34
35 accounts, err := client.TradeReturnAvailableAccountBalances()
36 if err != nil {
37 return decimal.Zero, err
38 }
39
40 marginBalance, err := p.computeAccountBalance(accounts.Margin, base_currency)
41 if err != nil {
42 return decimal.Zero, err
43 }
44
45 exchangeBalance, err := p.computeAccountBalance(accounts.Exchange, base_currency)
46 if err != nil {
47 return decimal.Zero, err
48 }
49
50 return decimal.Sum(marginBalance, exchangeBalance), nil
51}
52
53func (p *Poloniex) computeAccountBalance(account map[string]decimal.Decimal, base_currency string) (decimal.Decimal, error) {
54 var total decimal.Decimal
55
56 for currency, amount := range account {
57 pair, err := p.GetCurrencyPair(base_currency, currency)
58 if err != nil {
59 return decimal.Zero, err
60 }
61
62 total = total.Add(amount.Mul(pair.Rate))
63 }
64
65 return total, nil
66}
67
68func (p *Poloniex) GetCurrencyPair(curr1, curr2 string) (CurrencyPair, error) {
69 pairName := fmt.Sprintf("%s_%s", curr1, curr2)
70
71 fmt.Println(pairName)
72 if curr1 == curr2 {
73 return CurrencyPair{pairName, decimal.NewFromFloat(1.0)}, nil
74 }
75
76 pair, ok := p.TickerCache[pairName]
77 if !ok {
78 pair, err := p.fetchTicker(pairName)
79 if err != nil {
80 return CurrencyPair{}, err
81 }
82
83 return pair, nil
84 }
85
86 return pair, nil
87}
88
89func (p *Poloniex) fetchTicker(pair string) (CurrencyPair, error) {
90 tickers, err := p.publicClient.PubReturnTickers()
91 if err != nil {
92 return CurrencyPair{}, err
93 }
94
95 if ticker, ok := tickers[pair]; ok {
96 pair := CurrencyPair{Name: pair, Rate: ticker.Last}
97
98 if p.updateTickerChan != nil {
99 p.updateTickerChan <- pair
100 }
101
102 return pair, nil
103 } else {
104 return CurrencyPair{}, fmt.Errorf("pair '%v' not in tickers", pair)
105 }
106}
107
108func (p *Poloniex) StartTicker() error {
109 stream, err := poloniex.NewWSClient()
110 if err != nil {
111 return err
112 }
113
114 err = stream.SubscribeTicker()
115 if err != nil {
116 return err
117 }
118
119 p.updateTickerChan = make(chan CurrencyPair)
120
121 go func() {
122 for {
123 quit := false
124 select {
125 case data, ok := <-stream.Subs["ticker"]:
126 if !ok {
127 quit = true
128 } else {
129 ticker := data.(poloniex.WSTicker)
130 p.TickerCache[ticker.CurrencyPair] = CurrencyPair{Name: ticker.CurrencyPair, Rate: decimal.NewFromFloat(ticker.Last)}
131 }
132
133 case pair, ok := <-p.updateTickerChan:
134 if !ok {
135 quit = true
136 } else {
137 p.TickerCache[pair.Name] = pair
138 }
139 }
140 if quit {
141 //TODO: logit
142 p.updateTickerChan = nil
143 break
144 }
145 }
146 }()
147
148 return nil
149}