/
price.go
129 lines (110 loc) · 3.58 KB
/
price.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright (c) 2016 The Decred developers
// Copyright (c) 2018 The ExchangeCoin team
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package ticketbuyer
import (
"strings"
"time"
"github.com/EXCCoin/exccd/exccutil"
)
var (
// vwapReqTries is the maximum number of times to try
// TicketVWAP before failing.
vwapReqTries = 20
// vwapReqTryDelay is the time in seconds to wait before
// doing another TicketVWAP request.
vwapReqTryDelay = time.Millisecond * 500
)
// priceMode is the mode to use for the average ticket price.
type avgPriceMode int
const (
// AvgPriceVWAPMode indicates to use only the VWAP.
AvgPriceVWAPMode = iota
// AvgPricePoolMode indicates to use only the average
// price in the ticket pool.
AvgPricePoolMode
// AvgPriceDualMode indicates to use bothe the VWAP and
// the average pool price.
AvgPriceDualMode
)
// vwapHeightOffsetErrStr is a definitive portion of the RPC error returned
// when the chain has not yet sufficiently synced to the chain tip.
var vwapHeightOffsetErrStr = "beyond blockchain tip height"
// containsVWAPHeightOffsetError specifies whether or not the error
// matches the error returned when the chain has not yet sufficiently
// synced to the chain tip.
func containsVWAPHeightOffsetError(err error) bool {
if err == nil {
return false
}
return strings.Contains(err.Error(), vwapHeightOffsetErrStr)
}
// calcAverageTicketPrice calculates the average price of a ticket based on
// the parameters set by the user.
func (t *TicketPurchaser) calcAverageTicketPrice(height int64) (exccutil.Amount, error) {
// Pull and store relevant data about the blockchain. Calculate a
// "reasonable" ticket price by using the VWAP for the last delta many
// blocks or the average price of all tickets in the ticket pool.
// If the user has selected dual mode, it will use an average of
// these two prices.
var avgPricePoolAmt exccutil.Amount
if t.priceMode == AvgPricePoolMode || t.priceMode == AvgPriceDualMode {
poolValue, err := t.exccdChainSvr.GetTicketPoolValue()
if err != nil {
return 0, err
}
bestBlockH, err := t.exccdChainSvr.GetBestBlockHash()
if err != nil {
return 0, err
}
blkHeader, err := t.exccdChainSvr.GetBlockHeader(bestBlockH)
if err != nil {
return 0, err
}
poolSize := blkHeader.PoolSize
// Do not allow zero pool sizes to prevent a possible
// panic below.
if poolSize == 0 {
poolSize++
}
avgPricePoolAmt = poolValue / exccutil.Amount(poolSize)
if t.priceMode == AvgPricePoolMode {
return avgPricePoolAmt, err
}
}
var ticketVWAP exccutil.Amount
if t.priceMode == AvgPriceVWAPMode || t.priceMode == AvgPriceDualMode {
// Don't let the starting height be <0, which in the case of
// uint32 ends up a really big number.
startVWAPHeight := uint32(0)
if height-int64(t.cfg.AvgPriceVWAPDelta) > 0 {
startVWAPHeight = uint32(height - int64(t.cfg.AvgPriceVWAPDelta))
}
endVWAPHeight := uint32(height)
// Sometimes the chain is a little slow to update, retry
// if the chain gives us an issue.
var err error
for i := 0; i < vwapReqTries; i++ {
ticketVWAP, err = t.exccdChainSvr.TicketVWAP(&startVWAPHeight,
&endVWAPHeight)
if err != nil && containsVWAPHeightOffsetError(err) {
log.Tracef("Failed to fetch ticket VWAP "+
"on attempt %v: %v", i, err.Error())
err = nil
time.Sleep(vwapReqTryDelay)
continue
}
if err == nil {
break
}
}
if err != nil {
return 0, err
}
if t.priceMode == AvgPriceVWAPMode {
return ticketVWAP, err
}
}
return (ticketVWAP + avgPricePoolAmt) / 2, nil
}