118 lines
3.2 KiB
Go
118 lines
3.2 KiB
Go
package indicator
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"git.qtrade.icu/lychiyu/bbgo/pkg/types"
|
|
)
|
|
|
|
type trade struct {
|
|
price float64
|
|
volume float64 // +: buy, -: sell
|
|
timestamp types.Time
|
|
}
|
|
|
|
// The Volume Profile is a technical analysis tool that is used to visualize the distribution of trading volume at different price levels
|
|
// in a security. It is typically plotted as a histogram or heatmap on the price chart, with the x-axis representing the price levels and
|
|
// the y-axis representing the trading volume. The Volume Profile can be used to identify areas of support and resistance, as well as
|
|
// potential entry and exit points for trades.
|
|
type VolumeProfile struct {
|
|
types.IntervalWindow
|
|
Delta float64
|
|
profile map[float64]float64
|
|
trades []trade
|
|
minPrice float64
|
|
maxPrice float64
|
|
}
|
|
|
|
func (inc *VolumeProfile) Update(price, volume float64, timestamp types.Time) {
|
|
inc.minPrice = math.Inf(1)
|
|
inc.maxPrice = math.Inf(-1)
|
|
if inc.profile == nil {
|
|
inc.profile = make(map[float64]float64)
|
|
}
|
|
inc.profile[math.Round(price/inc.Delta)] += volume
|
|
filter := timestamp.Time().Add(-time.Duration(inc.Window) * inc.Interval.Duration())
|
|
inc.trades = append(inc.trades, trade{
|
|
price: price,
|
|
volume: volume,
|
|
timestamp: timestamp,
|
|
})
|
|
var i int
|
|
for i = 0; i < len(inc.trades); i++ {
|
|
td := inc.trades[i]
|
|
if td.timestamp.After(filter) {
|
|
inc.trades = inc.trades[i:len(inc.trades)]
|
|
break
|
|
}
|
|
inc.profile[math.Round(td.price/inc.Delta)] -= td.volume
|
|
}
|
|
|
|
for i = 0; i < len(inc.trades); i++ {
|
|
k := math.Round(inc.trades[i].price / inc.Delta)
|
|
if k < inc.minPrice {
|
|
inc.minPrice = k
|
|
}
|
|
if k > inc.maxPrice {
|
|
inc.maxPrice = k
|
|
}
|
|
}
|
|
}
|
|
|
|
// The Point of Control (POC) is a term used in the context of Volume Profile analysis. It refers to the price level at which the most
|
|
// volume has been traded in a security over a specified period of time. The POC is typically identified by looking for the highest
|
|
// peak in the Volume Profile, and is considered to be an important level of support or resistance. It can be used by traders to
|
|
// identify potential entry and exit points for trades, or to confirm other technical analysis signals.
|
|
|
|
// Get Resistance Level by finding PoC
|
|
func (inc *VolumeProfile) PointOfControlAboveEqual(price float64, limit ...float64) (resultPrice float64, vol float64) {
|
|
filter := inc.maxPrice
|
|
if len(limit) > 0 {
|
|
filter = limit[0]
|
|
}
|
|
if inc.Delta == 0 {
|
|
panic("Delta for volumeprofile shouldn't be zero")
|
|
}
|
|
start := math.Round(price / inc.Delta)
|
|
vol = math.Inf(-1)
|
|
if start > filter {
|
|
return 0, 0
|
|
}
|
|
for ; start <= filter; start += inc.Delta {
|
|
abs := math.Abs(inc.profile[start])
|
|
if vol < abs {
|
|
vol = abs
|
|
resultPrice = start
|
|
}
|
|
|
|
}
|
|
return resultPrice, vol
|
|
}
|
|
|
|
// Get Support Level by finding PoC
|
|
func (inc *VolumeProfile) PointOfControlBelowEqual(price float64, limit ...float64) (resultPrice float64, vol float64) {
|
|
filter := inc.minPrice
|
|
if len(limit) > 0 {
|
|
filter = limit[0]
|
|
}
|
|
if inc.Delta == 0 {
|
|
panic("Delta for volumeprofile shouldn't be zero")
|
|
}
|
|
start := math.Round(price / inc.Delta)
|
|
vol = math.Inf(-1)
|
|
|
|
if start < filter {
|
|
return 0, 0
|
|
}
|
|
|
|
for ; start >= filter; start -= inc.Delta {
|
|
abs := math.Abs(inc.profile[start])
|
|
if vol < abs {
|
|
vol = abs
|
|
resultPrice = start
|
|
}
|
|
}
|
|
return resultPrice, vol
|
|
}
|