mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-14 15:09:51 +00:00
* Modifications for a smoother live run * Fixes data appending * Successfully allows multi-currency live trading. Adds multiple currencies to live DCA strategy * Attempting to get cash and carry working * Poor attempts at sorting out data and appending it properly with USD in mind * =designs new live data handler * Updates cash and carry strat to work * adds test coverage. begins closeallpositions function * Updates cash and carry to work live * New kline.Event type. Cancels orders on close. Rn types * =Fixes USD funding issue * =fixes tests * fixes tests AGAIN * adds coverage to close all orders * crummy tests, should override * more tests * more tests * more coverage * removes scourge of currency.Pair maps. More tests * missed currency stuff * Fixes USD data issue & collateral issue. Needs to close ALL orders * Now triggers updates on the very first data entry * All my problems are solved now???? * fixes tests, extends coverage * there is some really funky candle stuff going on * my brain is melting * better shutdown management, fixes freezing bug * fixes data duplication issues, adds retries to requests * reduces logging, adds verbose options * expands coverage over all new functionality * fixes fun bug from curr == curr to curr.Equal(curr) * fixes setup issues and tests * starts adding external wallet amounts for funding * more setup for assets * setup live fund calcs and placing orders * successfully performs automated cash and carry * merge fixes * funding properly set at all times * fixes some bugs, need to address currencystatistics still * adds 'appeneded' trait, attempts to fix some stats * fixes stat bugs, adds cool new fetchfees feature * fixes terrible processing bugs * tightens realorder stats, sadly loses some live stats * this actually sets everything correctly for bothcd ..cd ..cd ..cd ..cd ..! * fix tests * coverage * beautiful new test coverage * docs * adds new fee getter delayer * commits from the correct directory * Lint * adds verbose to fund manager * Fix bug in t2b2 strat. Update dca live config. Docs * go mod tidy * update buf * buf + test improvement * Post merge fixes * fixes surprise offset bug * fix sizing restrictions for cash and carry * fix server lints * merge fixes * test fixesss * lintle fixles * slowloris * rn run to task, bug fixes, close all on close * rpc lint and fixes * bugfix: order manager not processing orders properly * somewhat addresses nits * absolutely broken end of day commit * absolutely massive knockon effects from nits * massive knockon effects continue * fixes things * address remaining nits * jk now fixes things * addresses the easier nits * more nit fixers * more niterinos addressederinos * refactors holdings and does some nits * so buf * addresses some nits, fixes holdings bugs * cleanup * attempts to fix alert chans to prevent many chans waiting? * terrible code, will revert * to be reviewed in detail tomorrow * Fixes up channel system * smashes those nits * fixes extra candles, fixes collateral bug, tests * fixes data races, introduces reflection * more checks n tests * Fixes cash and carry issues. Fixes more cool bugs * fixes ~typer~ typo * replace spot strats from ftx to binance * fixes all the tests I just destroyed * removes example path, rm verbose * 1) what 2) removes FTX references from the Backtester * renamed, non-working strategies * Removes FTX references almost as fast as sbf removes funds * regen docs, add contrib names,sort contrib names * fixes merge renamings * Addresses nits. Fixes setting API credentials. Fixes Binance limit retrieval * Fixes live order bugs with real orders and without * Apply suggestions from code review Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update backtester/engine/live.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update backtester/engine/live.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update backtester/config/strategyconfigbuilder/main.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * updates docs * even better docs Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
344 lines
8.9 KiB
Go
344 lines
8.9 KiB
Go
package data
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
|
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
)
|
|
|
|
// NewHandlerHolder returns a new HandlerHolder
|
|
func NewHandlerHolder() *HandlerHolder {
|
|
return &HandlerHolder{
|
|
data: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler),
|
|
}
|
|
}
|
|
|
|
// SetDataForCurrency assigns a Data Handler to the Data map by exchange, asset and currency
|
|
func (h *HandlerHolder) SetDataForCurrency(e string, a asset.Item, p currency.Pair, k Handler) error {
|
|
if h == nil {
|
|
return fmt.Errorf("%w handler holder", gctcommon.ErrNilPointer)
|
|
}
|
|
h.m.Lock()
|
|
defer h.m.Unlock()
|
|
if h.data == nil {
|
|
h.data = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler)
|
|
}
|
|
e = strings.ToLower(e)
|
|
m1, ok := h.data[e]
|
|
if !ok {
|
|
m1 = make(map[asset.Item]map[*currency.Item]map[*currency.Item]Handler)
|
|
h.data[e] = m1
|
|
}
|
|
|
|
m2, ok := m1[a]
|
|
if !ok {
|
|
m2 = make(map[*currency.Item]map[*currency.Item]Handler)
|
|
m1[a] = m2
|
|
}
|
|
|
|
m3, ok := m2[p.Base.Item]
|
|
if !ok {
|
|
m3 = make(map[*currency.Item]Handler)
|
|
m2[p.Base.Item] = m3
|
|
}
|
|
|
|
m3[p.Quote.Item] = k
|
|
return nil
|
|
}
|
|
|
|
// GetAllData returns all set Data in the Data map
|
|
func (h *HandlerHolder) GetAllData() ([]Handler, error) {
|
|
if h == nil {
|
|
return nil, fmt.Errorf("%w handler holder", gctcommon.ErrNilPointer)
|
|
}
|
|
h.m.Lock()
|
|
defer h.m.Unlock()
|
|
var resp []Handler
|
|
for _, exchMap := range h.data {
|
|
for _, assetMap := range exchMap {
|
|
for _, baseMap := range assetMap {
|
|
for _, handler := range baseMap {
|
|
resp = append(resp, handler)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// GetDataForCurrency returns the Handler for a specific exchange, asset, currency
|
|
func (h *HandlerHolder) GetDataForCurrency(ev common.Event) (Handler, error) {
|
|
if h == nil {
|
|
return nil, fmt.Errorf("%w handler holder", gctcommon.ErrNilPointer)
|
|
}
|
|
if ev == nil {
|
|
return nil, common.ErrNilEvent
|
|
}
|
|
h.m.Lock()
|
|
defer h.m.Unlock()
|
|
exch := ev.GetExchange()
|
|
a := ev.GetAssetType()
|
|
p := ev.Pair()
|
|
handler, ok := h.data[exch][a][p.Base.Item][p.Quote.Item]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s %s %s %w", exch, a, p, ErrHandlerNotFound)
|
|
}
|
|
return handler, nil
|
|
}
|
|
|
|
// Reset returns the struct to defaults
|
|
func (h *HandlerHolder) Reset() error {
|
|
if h == nil {
|
|
return gctcommon.ErrNilPointer
|
|
}
|
|
h.m.Lock()
|
|
defer h.m.Unlock()
|
|
h.data = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler)
|
|
return nil
|
|
}
|
|
|
|
// GetDetails returns data about the Base Holder
|
|
func (b *Base) GetDetails() (string, asset.Item, currency.Pair, error) {
|
|
if b == nil {
|
|
return "", asset.Empty, currency.EMPTYPAIR, fmt.Errorf("%w base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
return b.latest.GetExchange(), b.latest.GetAssetType(), b.latest.Pair(), nil
|
|
}
|
|
|
|
// Reset loaded Data to blank state
|
|
func (b *Base) Reset() error {
|
|
if b == nil {
|
|
return gctcommon.ErrNilPointer
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
b.stream = nil
|
|
b.latest = nil
|
|
b.offset = 0
|
|
b.isLiveData = false
|
|
return nil
|
|
}
|
|
|
|
// GetStream will return entire Data list
|
|
func (b *Base) GetStream() (Events, error) {
|
|
if b == nil {
|
|
return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
stream := make([]Event, len(b.stream))
|
|
copy(stream, b.stream)
|
|
|
|
return stream, nil
|
|
}
|
|
|
|
// Offset returns the current iteration of candle Data the backtester is assessing
|
|
func (b *Base) Offset() (int64, error) {
|
|
if b == nil {
|
|
return 0, fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
return b.offset, nil
|
|
}
|
|
|
|
// SetStream sets the Data stream for candle analysis
|
|
func (b *Base) SetStream(s []Event) error {
|
|
if b == nil {
|
|
return fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
|
|
sort.Slice(s, func(i, j int) bool {
|
|
return s[i].GetTime().Before(s[j].GetTime())
|
|
})
|
|
for x := range s {
|
|
if s[x] == nil {
|
|
return fmt.Errorf("%w Event", gctcommon.ErrNilPointer)
|
|
}
|
|
if s[x].GetExchange() == "" || !s[x].GetAssetType().IsValid() || s[x].Pair().IsEmpty() || s[x].GetTime().IsZero() {
|
|
return ErrInvalidEventSupplied
|
|
}
|
|
if len(b.stream) > 0 {
|
|
if s[x].GetExchange() != b.stream[0].GetExchange() ||
|
|
s[x].GetAssetType() != b.stream[0].GetAssetType() ||
|
|
!s[x].Pair().Equal(b.stream[0].Pair()) {
|
|
return fmt.Errorf("%w cannot set base stream from %v %v %v to %v %v %v", errMisMatchedEvent, s[x].GetExchange(), s[x].GetAssetType(), s[x].Pair(), b.stream[0].GetExchange(), b.stream[0].GetAssetType(), b.stream[0].Pair())
|
|
}
|
|
}
|
|
// due to the Next() function, we cannot take
|
|
// stream offsets as is, and we re-set them
|
|
s[x].SetOffset(int64(x) + 1)
|
|
}
|
|
|
|
b.stream = s
|
|
return nil
|
|
}
|
|
|
|
// AppendStream appends new datas onto the stream, however, will not
|
|
// add duplicates. Used for live analysis
|
|
func (b *Base) AppendStream(s ...Event) error {
|
|
if b == nil {
|
|
return fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
if len(s) == 0 {
|
|
return errNothingToAdd
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
candles:
|
|
for x := range s {
|
|
if s[x] == nil {
|
|
return fmt.Errorf("%w Event", gctcommon.ErrNilPointer)
|
|
}
|
|
if s[x].GetExchange() == "" || !s[x].GetAssetType().IsValid() || s[x].Pair().IsEmpty() || s[x].GetTime().IsZero() {
|
|
return ErrInvalidEventSupplied
|
|
}
|
|
if len(b.stream) > 0 {
|
|
if s[x].GetExchange() != b.stream[0].GetExchange() ||
|
|
s[x].GetAssetType() != b.stream[0].GetAssetType() ||
|
|
!s[x].Pair().Equal(b.stream[0].Pair()) {
|
|
return fmt.Errorf("%w %v %v %v received %v %v %v", errMisMatchedEvent, b.stream[0].GetExchange(), b.stream[0].GetAssetType(), b.stream[0].Pair(), s[x].GetExchange(), s[x].GetAssetType(), s[x].Pair())
|
|
}
|
|
// todo change b.stream to map
|
|
for y := len(b.stream) - 1; y >= 0; y-- {
|
|
if s[x].GetTime().Equal(b.stream[y].GetTime()) {
|
|
continue candles
|
|
}
|
|
}
|
|
}
|
|
|
|
b.stream = append(b.stream, s[x])
|
|
}
|
|
|
|
sort.Slice(b.stream, func(i, j int) bool {
|
|
return b.stream[i].GetTime().Before(b.stream[j].GetTime())
|
|
})
|
|
for i := range b.stream {
|
|
b.stream[i].SetOffset(int64(i) + 1)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Next will return the next event in the list and also shift the offset one
|
|
func (b *Base) Next() (Event, error) {
|
|
if b == nil {
|
|
return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
if int64(len(b.stream)) <= b.offset {
|
|
return nil, fmt.Errorf("%w data length %v offset %v", ErrEndOfData, len(b.stream), b.offset)
|
|
}
|
|
ret := b.stream[b.offset]
|
|
b.offset++
|
|
b.latest = ret
|
|
return ret, nil
|
|
}
|
|
|
|
// History will return all previous Data events that have happened
|
|
func (b *Base) History() (Events, error) {
|
|
if b == nil {
|
|
return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
|
|
stream := make([]Event, len(b.stream[:b.offset]))
|
|
copy(stream, b.stream[:b.offset])
|
|
|
|
return stream, nil
|
|
}
|
|
|
|
// Latest will return latest Data event
|
|
func (b *Base) Latest() (Event, error) {
|
|
if b == nil {
|
|
return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
|
|
if b.latest == nil && int64(len(b.stream)) >= b.offset+1 {
|
|
b.latest = b.stream[b.offset]
|
|
}
|
|
return b.latest, nil
|
|
}
|
|
|
|
// List returns all future Data events from the current iteration
|
|
// ill-advised to use this in strategies because you don't know the future in real life
|
|
func (b *Base) List() (Events, error) {
|
|
if b == nil {
|
|
return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
|
|
stream := make([]Event, len(b.stream[b.offset:]))
|
|
copy(stream, b.stream[b.offset:])
|
|
|
|
return stream, nil
|
|
}
|
|
|
|
// IsLastEvent determines whether the latest event is the last event
|
|
// for live Data, this will be false, as all appended Data is the latest available Data
|
|
// and this signal cannot be completely relied upon
|
|
func (b *Base) IsLastEvent() (bool, error) {
|
|
if b == nil {
|
|
return false, fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
|
|
return b.latest != nil && b.latest.GetOffset() == int64(len(b.stream)) && !b.isLiveData,
|
|
nil
|
|
}
|
|
|
|
// IsLive returns if the Data source is a live one
|
|
// less scrutiny on checks is required on live Data sourcing
|
|
func (b *Base) IsLive() (bool, error) {
|
|
if b == nil {
|
|
return false, fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
|
|
return b.isLiveData, nil
|
|
}
|
|
|
|
// SetLive sets if the Data source is a live one
|
|
// less scrutiny on checks is required on live Data sourcing
|
|
func (b *Base) SetLive(isLive bool) error {
|
|
if b == nil {
|
|
return fmt.Errorf("%w Base", gctcommon.ErrNilPointer)
|
|
}
|
|
b.m.Lock()
|
|
defer b.m.Unlock()
|
|
|
|
b.isLiveData = isLive
|
|
return nil
|
|
}
|
|
|
|
// First returns the first element of a slice
|
|
func (e Events) First() (Event, error) {
|
|
if len(e) == 0 {
|
|
return nil, ErrEmptySlice
|
|
}
|
|
return e[0], nil
|
|
}
|
|
|
|
// Last returns the last element of a slice
|
|
func (e Events) Last() (Event, error) {
|
|
if len(e) == 0 {
|
|
return nil, ErrEmptySlice
|
|
}
|
|
return e[len(e)-1], nil
|
|
}
|