maps: expansion of Key concept (#1349)

* moves everything to use single map keys, also breaks

* full rollout

* tests

* fix a little bug

* minor test fixups

* Fix Key use

* rm 🔑 from 🔑 struct name
This commit is contained in:
Scott
2023-10-04 09:19:41 +10:00
committed by GitHub
parent 033a72b61a
commit 91d699be9d
47 changed files with 1478 additions and 1339 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/common"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
@@ -14,7 +15,7 @@ import (
// NewHandlerHolder returns a new HandlerHolder
func NewHandlerHolder() *HandlerHolder {
return &HandlerHolder{
data: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler),
data: make(map[key.ExchangePairAsset]Handler),
}
}
@@ -26,28 +27,15 @@ func (h *HandlerHolder) SetDataForCurrency(e string, a asset.Item, p currency.Pa
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)
h.data = make(map[key.ExchangePairAsset]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
h.data[key.ExchangePairAsset{
Exchange: e,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}] = k
return nil
}
@@ -58,15 +46,9 @@ func (h *HandlerHolder) GetAllData() ([]Handler, error) {
}
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)
}
}
}
resp := make([]Handler, 0, len(h.data))
for _, handler := range h.data {
resp = append(resp, handler)
}
return resp, nil
}
@@ -84,7 +66,12 @@ func (h *HandlerHolder) GetDataForCurrency(ev common.Event) (Handler, error) {
exch := ev.GetExchange()
a := ev.GetAssetType()
p := ev.Pair()
handler, ok := h.data[exch][a][p.Base.Item][p.Quote.Item]
handler, ok := h.data[key.ExchangePairAsset{
Exchange: exch,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("%s %s %s %w", exch, a, p, ErrHandlerNotFound)
}
@@ -98,7 +85,7 @@ func (h *HandlerHolder) Reset() error {
}
h.m.Lock()
defer h.m.Unlock()
h.data = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler)
h.data = make(map[key.ExchangePairAsset]Handler)
return nil
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/common"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
@@ -37,7 +38,12 @@ func TestSetDataForCurrency(t *testing.T) {
if d.data == nil {
t.Error("expected not nil")
}
if d.data[exch][a][p.Base.Item][p.Quote.Item] != nil {
if d.data[key.ExchangePairAsset{
Exchange: exch,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}] != nil {
t.Error("expected nil")
}
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/backtester/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
@@ -28,7 +29,7 @@ var (
// HandlerHolder stores an event handler per exchange asset pair
type HandlerHolder struct {
m sync.Mutex
data map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler
data map[key.ExchangePairAsset]Handler
}
// Holder interface dictates what a Data holder is expected to do

View File

@@ -622,6 +622,9 @@ func (bt *BackTest) Stop() error {
log.Errorf(common.Backtester, "Could not close all positions on stop: %s", err)
}
}
if !bt.hasProcessedAnEvent {
return nil
}
err := bt.Statistic.CalculateAllResults()
if err != nil {
return err

View File

@@ -35,6 +35,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/report"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
"github.com/thrasher-corp/gocryptotrader/database/drivers"
@@ -442,11 +443,7 @@ func TestFullCycle(t *testing.T) {
tt := time.Now()
stats := &statistics.Statistic{}
stats.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
stats.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
stats.ExchangeAssetPairStatistics[ex][a] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
stats.ExchangeAssetPairStatistics[ex][a][cp.Base.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic)
stats.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic)
port, err := portfolio.Setup(&size.Size{
BuySide: exchange.MinMax{},
SellSide: exchange.MinMax{},
@@ -580,10 +577,7 @@ func TestFullCycleMulti(t *testing.T) {
tt := time.Now()
stats := &statistics.Statistic{}
stats.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
stats.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
stats.ExchangeAssetPairStatistics[ex][a] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
stats.ExchangeAssetPairStatistics[ex][a][cp.Base.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic)
stats.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic)
port, err := portfolio.Setup(&size.Size{
BuySide: exchange.MinMax{},

View File

@@ -32,6 +32,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/report"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/common/key"
gctconfig "github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
gctdatabase "github.com/thrasher-corp/gocryptotrader/database"
@@ -192,7 +193,7 @@ func (bt *BackTest) SetupFromConfig(cfg *config.Config, templatePath, output str
}
portfolioRisk := &risk.Risk{
CurrencySettings: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*risk.CurrencySettings),
CurrencySettings: make(map[key.ExchangePairAsset]*risk.CurrencySettings),
}
bt.Funding = funds
@@ -220,9 +221,6 @@ func (bt *BackTest) SetupFromConfig(cfg *config.Config, templatePath, output str
}
for i := range cfg.CurrencySettings {
if portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName] == nil {
portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*risk.CurrencySettings)
}
a := cfg.CurrencySettings[i].Asset
if !a.IsValid() {
return fmt.Errorf(
@@ -234,12 +232,10 @@ func (bt *BackTest) SetupFromConfig(cfg *config.Config, templatePath, output str
cfg.CurrencySettings[i].Quote,
err)
}
if portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a] == nil {
portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a] = make(map[*currency.Item]map[*currency.Item]*risk.CurrencySettings)
}
if portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a][cfg.CurrencySettings[i].Base.Item] == nil {
portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a][cfg.CurrencySettings[i].Base.Item] = make(map[*currency.Item]*risk.CurrencySettings)
if portfolioRisk.CurrencySettings == nil {
portfolioRisk.CurrencySettings = make(map[key.ExchangePairAsset]*risk.CurrencySettings)
}
var curr currency.Pair
var b, q currency.Code
b = cfg.CurrencySettings[i].Base
@@ -264,7 +260,12 @@ func (bt *BackTest) SetupFromConfig(cfg *config.Config, templatePath, output str
portSet.MaximumOrdersWithLeverageRatio = cfg.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrdersWithLeverageRatio
portSet.MaxLeverageRate = cfg.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrderLeverageRate
}
portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a][curr.Base.Item][curr.Quote.Item] = portSet
portfolioRisk.CurrencySettings[key.ExchangePairAsset{
Exchange: cfg.CurrencySettings[i].ExchangeName,
Base: cfg.CurrencySettings[i].Base.Item,
Quote: cfg.CurrencySettings[i].Quote.Item,
Asset: a,
}] = portSet
if cfg.CurrencySettings[i].MakerFee != nil &&
cfg.CurrencySettings[i].TakerFee != nil &&
cfg.CurrencySettings[i].MakerFee.GreaterThan(*cfg.CurrencySettings[i].TakerFee) {
@@ -396,7 +397,7 @@ func (bt *BackTest) SetupFromConfig(cfg *config.Config, templatePath, output str
StrategyNickname: cfg.Nickname,
StrategyDescription: bt.Strategy.Description(),
StrategyGoal: cfg.Goal,
ExchangeAssetPairStatistics: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic),
ExchangeAssetPairStatistics: make(map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic),
RiskFreeRate: cfg.StatisticSettings.RiskFreeRate,
CandleInterval: cfg.DataSettings.Interval,
FundManager: bt.Funding,

View File

@@ -18,7 +18,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/futures"
@@ -56,7 +56,12 @@ func (p *Portfolio) OnSignal(ev signal.Event, exchangeSettings *exchange.Setting
return o, errInvalidDirection
}
lookup := p.exchangeAssetPairPortfolioSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair().Base.Item][ev.Pair().Quote.Item]
lookup := p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: ev.GetExchange(),
Base: ev.Pair().Base.Item,
Quote: ev.Pair().Quote.Item,
Asset: ev.GetAssetType(),
}]
if lookup == nil {
return nil, fmt.Errorf("%w for %v %v %v",
errNoPortfolioSettings,
@@ -235,7 +240,12 @@ func (p *Portfolio) OnFill(ev fill.Event, funds funding.IFundReleaser) (fill.Eve
if ev == nil {
return nil, common.ErrNilEvent
}
lookup := p.exchangeAssetPairPortfolioSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair().Base.Item][ev.Pair().Quote.Item]
lookup := p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: ev.GetExchange(),
Base: ev.Pair().Base.Item,
Quote: ev.Pair().Quote.Item,
Asset: ev.GetAssetType(),
}]
if lookup == nil {
return nil, fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, ev.GetExchange(), ev.GetAssetType(), ev.Pair())
}
@@ -299,25 +309,24 @@ func (p *Portfolio) addComplianceSnapshot(fillEvent fill.Event) error {
}
// GetLatestOrderSnapshotForEvent gets orders related to the event
func (p *Portfolio) GetLatestOrderSnapshotForEvent(e common.Event) (compliance.Snapshot, error) {
eapSettings, ok := p.exchangeAssetPairPortfolioSettings[e.GetExchange()][e.GetAssetType()][e.Pair().Base.Item][e.Pair().Quote.Item]
func (p *Portfolio) GetLatestOrderSnapshotForEvent(ev common.Event) (compliance.Snapshot, error) {
eapSettings, ok := p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: ev.GetExchange(),
Base: ev.Pair().Base.Item,
Quote: ev.Pair().Quote.Item,
Asset: ev.GetAssetType(),
}]
if !ok {
return compliance.Snapshot{}, fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, e.GetExchange(), e.GetAssetType(), e.Pair())
return compliance.Snapshot{}, fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, ev.GetExchange(), ev.GetAssetType(), ev.Pair())
}
return eapSettings.ComplianceManager.GetLatestSnapshot(), nil
}
// GetLatestOrderSnapshots returns the latest snapshots from all stored pair data
func (p *Portfolio) GetLatestOrderSnapshots() ([]compliance.Snapshot, error) {
var resp []compliance.Snapshot
for _, exchangeMap := range p.exchangeAssetPairPortfolioSettings {
for _, assetMap := range exchangeMap {
for _, baseMap := range assetMap {
for _, quoteMap := range baseMap {
resp = append(resp, quoteMap.ComplianceManager.GetLatestSnapshot())
}
}
}
resp := make([]compliance.Snapshot, 0, len(p.exchangeAssetPairPortfolioSettings))
for _, d := range p.exchangeAssetPairPortfolioSettings {
resp = append(resp, d.ComplianceManager.GetLatestSnapshot())
}
if len(resp) == 0 {
return nil, errNoPortfolioSettings
@@ -338,7 +347,12 @@ func (p *Portfolio) GetLatestComplianceSnapshot(exchangeName string, a asset.Ite
// getComplianceManager returns the order snapshots for a given exchange, asset, pair
func (p *Portfolio) getComplianceManager(exchangeName string, a asset.Item, cp currency.Pair) (*compliance.Manager, error) {
lookup := p.exchangeAssetPairPortfolioSettings[exchangeName][a][cp.Base.Item][cp.Quote.Item]
lookup := p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: exchangeName,
Base: cp.Base.Item,
Quote: cp.Quote.Item,
Asset: a,
}]
if lookup == nil {
return nil, fmt.Errorf("%w for %v %v %v could not retrieve compliance manager", errNoPortfolioSettings, exchangeName, a, cp)
}
@@ -504,80 +518,75 @@ func (p *Portfolio) CreateLiquidationOrdersForExchange(ev data.Event, funds fund
return nil, fmt.Errorf("%w, requires funding manager", gctcommon.ErrNilPointer)
}
var closingOrders []order.Event
assetPairSettings, ok := p.exchangeAssetPairPortfolioSettings[ev.GetExchange()]
if !ok {
return nil, config.ErrExchangeNotFound
}
for item, baseMap := range assetPairSettings {
for b, quoteMap := range baseMap {
for q, settings := range quoteMap {
switch {
case item.IsFutures():
positions := settings.FuturesTracker.GetPositions()
if len(positions) == 0 {
continue
}
pos := positions[len(positions)-1]
if !pos.LatestSize.IsPositive() {
continue
}
direction := gctorder.Short
if pos.LatestDirection == gctorder.Short {
direction = gctorder.Long
}
closingOrders = append(closingOrders, &order.Order{
Base: &event.Base{
Offset: ev.GetOffset(),
Exchange: pos.Exchange,
Time: ev.GetTime(),
Interval: ev.GetInterval(),
CurrencyPair: pos.Pair,
UnderlyingPair: ev.GetUnderlyingPair(),
AssetType: pos.Asset,
Reasons: []string{"LIQUIDATED"},
},
Direction: direction,
Status: gctorder.Liquidated,
ClosePrice: ev.GetClosePrice(),
Amount: pos.LatestSize,
AllocatedFunds: pos.LatestSize,
OrderType: gctorder.Market,
LiquidatingPosition: true,
})
case item == asset.Spot:
allFunds, err := funds.GetAllFunding()
if err != nil {
return nil, err
}
for i := range allFunds {
if allFunds[i].Asset.IsFutures() {
continue
}
if allFunds[i].Currency.IsFiatCurrency() || allFunds[i].Currency.IsStableCurrency() {
// close orders for assets
// funding manager will zero for fiat/stable
continue
}
cp := currency.NewPair(b.Currency(), q.Currency())
closingOrders = append(closingOrders, &order.Order{
Base: &event.Base{
Offset: ev.GetOffset(),
Exchange: ev.GetExchange(),
Time: ev.GetTime(),
Interval: ev.GetInterval(),
CurrencyPair: cp,
AssetType: item,
Reasons: []string{"LIQUIDATED"},
},
Direction: gctorder.Sell,
Status: gctorder.Liquidated,
Amount: allFunds[i].Available,
OrderType: gctorder.Market,
AllocatedFunds: allFunds[i].Available,
LiquidatingPosition: true,
})
}
for mapKey, settings := range p.exchangeAssetPairPortfolioSettings {
if !mapKey.MatchesExchange(ev.GetExchange()) {
continue
}
switch {
case mapKey.Asset.IsFutures():
positions := settings.FuturesTracker.GetPositions()
if len(positions) == 0 {
continue
}
pos := positions[len(positions)-1]
if !pos.LatestSize.IsPositive() {
continue
}
direction := gctorder.Short
if pos.LatestDirection == gctorder.Short {
direction = gctorder.Long
}
closingOrders = append(closingOrders, &order.Order{
Base: &event.Base{
Offset: ev.GetOffset(),
Exchange: pos.Exchange,
Time: ev.GetTime(),
Interval: ev.GetInterval(),
CurrencyPair: pos.Pair,
UnderlyingPair: ev.GetUnderlyingPair(),
AssetType: pos.Asset,
Reasons: []string{"LIQUIDATED"},
},
Direction: direction,
Status: gctorder.Liquidated,
ClosePrice: ev.GetClosePrice(),
Amount: pos.LatestSize,
AllocatedFunds: pos.LatestSize,
OrderType: gctorder.Market,
LiquidatingPosition: true,
})
case mapKey.Asset == asset.Spot:
allFunds, err := funds.GetAllFunding()
if err != nil {
return nil, err
}
for i := range allFunds {
if allFunds[i].Asset.IsFutures() {
continue
}
if allFunds[i].Currency.IsFiatCurrency() || allFunds[i].Currency.IsStableCurrency() {
// close orders for assets
// funding manager will zero for fiat/stable
continue
}
cp := currency.NewPair(mapKey.Base.Currency(), mapKey.Quote.Currency())
closingOrders = append(closingOrders, &order.Order{
Base: &event.Base{
Offset: ev.GetOffset(),
Exchange: ev.GetExchange(),
Time: ev.GetTime(),
Interval: ev.GetInterval(),
CurrencyPair: cp,
AssetType: mapKey.Asset,
Reasons: []string{"LIQUIDATED"},
},
Direction: gctorder.Sell,
Status: gctorder.Liquidated,
Amount: allFunds[i].Available,
OrderType: gctorder.Market,
AllocatedFunds: allFunds[i].Available,
LiquidatingPosition: true,
})
}
}
}
@@ -606,7 +615,12 @@ func (p *Portfolio) getFuturesSettingsFromEvent(e common.Event) (*Settings, erro
func (p *Portfolio) getSettings(exch string, item asset.Item, pair currency.Pair) (*Settings, error) {
exch = strings.ToLower(exch)
settings, ok := p.exchangeAssetPairPortfolioSettings[exch][item][pair.Base.Item][pair.Quote.Item]
settings, ok := p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if !ok {
return nil, fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, exch, item, pair)
}
@@ -658,20 +672,15 @@ func (p *Portfolio) UpdateHoldings(e data.Event, funds funding.IFundReleaser) er
// GetLatestHoldingsForAllCurrencies will return the current holdings for all loaded currencies
// this is useful to assess the position of your entire portfolio in order to help with risk decisions
func (p *Portfolio) GetLatestHoldingsForAllCurrencies() []holdings.Holding {
var resp []holdings.Holding
for _, exchangeMap := range p.exchangeAssetPairPortfolioSettings {
for _, assetMap := range exchangeMap {
for _, baseMap := range assetMap {
for _, quoteMap := range baseMap {
holds, err := quoteMap.GetLatestHoldings()
if err != nil {
continue
}
resp = append(resp, *holds)
}
}
resp := make([]holdings.Holding, 0, len(p.exchangeAssetPairPortfolioSettings))
for _, d := range p.exchangeAssetPairPortfolioSettings {
holds, err := d.GetLatestHoldings()
if err != nil {
continue
}
resp = append(resp, *holds)
}
return resp
}

View File

@@ -19,7 +19,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/binance"
@@ -35,7 +35,7 @@ var leet = decimal.NewFromInt(1337)
func TestReset(t *testing.T) {
t.Parallel()
p := &Portfolio{
exchangeAssetPairPortfolioSettings: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings),
exchangeAssetPairPortfolioSettings: make(map[key.ExchangePairAsset]*Settings),
}
err := p.Reset()
if !errors.Is(err, nil) {
@@ -674,7 +674,12 @@ func TestGetSnapshotAtTime(t *testing.T) {
t.Errorf("received: %v, expected: %v", err, nil)
}
tt := time.Now()
s, ok := p.exchangeAssetPairPortfolioSettings[testExchange][asset.Spot][cp.Base.Item][cp.Quote.Item]
s, ok := p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: testExchange,
Base: cp.Base.Item,
Quote: cp.Quote.Item,
Asset: asset.Spot,
}]
if !ok {
t.Fatal("couldn't get settings")
}
@@ -728,7 +733,12 @@ func TestGetLatestSnapshot(t *testing.T) {
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
s, ok := p.exchangeAssetPairPortfolioSettings[testExchange][asset.Spot][cp.Base.Item][cp.Quote.Item]
s, ok := p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: testExchange,
Base: cp.Base.Item,
Quote: cp.Quote.Item,
Asset: asset.Spot,
}]
if !ok {
t.Fatal("couldn't get settings")
}
@@ -843,11 +853,13 @@ func TestCalculatePNL(t *testing.T) {
FuturesTracker: mpt,
}
p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType] = make(map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][pair.Base.Item] = make(map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][pair.Base.Item][pair.Quote.Item] = s
p.exchangeAssetPairPortfolioSettings = make(map[key.ExchangePairAsset]*Settings)
p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: testExchange,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: ev.AssetType,
}] = s
ev.Close = leet
err = s.ComplianceManager.AddSnapshot(&compliance.Snapshot{
Timestamp: tt0,
@@ -1123,11 +1135,13 @@ func TestGetLatestPNLForEvent(t *testing.T) {
FuturesTracker: mpt,
}
p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType] = make(map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][ev.Pair().Base.Item] = make(map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][ev.Pair().Base.Item][ev.Pair().Quote.Item] = s
p.exchangeAssetPairPortfolioSettings = make(map[key.ExchangePairAsset]*Settings)
p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: testExchange,
Base: ev.Pair().Base.Item,
Quote: ev.Pair().Quote.Item,
Asset: asset.Futures,
}] = s
expectedError = nil
err = s.FuturesTracker.TrackNewOrder(&gctorder.Detail{
Exchange: ev.GetExchange(),
@@ -1408,10 +1422,9 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) {
t.Parallel()
p := &Portfolio{}
var expectedError = common.ErrNilEvent
_, err := p.CreateLiquidationOrdersForExchange(nil, nil)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, common.ErrNilEvent) {
t.Fatalf("received '%v' expected '%v'", err, common.ErrNilEvent)
}
b := &event.Base{}
@@ -1419,23 +1432,20 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) {
ev := &kline.Kline{
Base: b,
}
expectedError = gctcommon.ErrNilPointer
_, err = p.CreateLiquidationOrdersForExchange(ev, nil)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Fatalf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
funds := &funding.FundManager{}
expectedError = config.ErrExchangeNotFound
_, err = p.CreateLiquidationOrdersForExchange(ev, funds)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
ff := &binance.Binance{}
ff.Name = testExchange
cp := currency.NewPair(currency.BTC, currency.USDT)
expectedError = nil
err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Futures, Pair: cp})
if !errors.Is(err, gctcommon.ErrNotYetImplemented) {
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented)
@@ -1446,8 +1456,8 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) {
}
ev.Exchange = ff.Name
_, err = p.CreateLiquidationOrdersForExchange(ev, funds)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
_, err = p.getSettings(ff.Name, asset.Futures, cp)
@@ -1482,42 +1492,44 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) {
}
err = settings.FuturesTracker.TrackNewOrder(od)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v', expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType] = make(map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][ev.Pair().Base.Item] = make(map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][ev.Pair().Base.Item][ev.Pair().Quote.Item] = settings
p.exchangeAssetPairPortfolioSettings = make(map[key.ExchangePairAsset]*Settings)
p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: testExchange,
Base: ev.Pair().Base.Item,
Quote: ev.Pair().Quote.Item,
Asset: asset.Spot,
}] = settings
ev.Exchange = ff.Name
ev.AssetType = asset.Futures
ev.CurrencyPair = cp
_, err = p.CreateLiquidationOrdersForExchange(ev, funds)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
// spot order
item, err := funding.CreateItem(ff.Name, asset.Spot, currency.BTC, decimal.Zero, decimal.Zero)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
err = funds.AddItem(item)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
err = item.IncreaseAvailable(leet)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
orders, err := p.CreateLiquidationOrdersForExchange(ev, funds)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
if len(orders) != 0 {
t.Errorf("expected two orders generated, received '%v'", len(orders))
if len(orders) != 1 {
t.Errorf("expected one order generated, received '%v'", len(orders))
}
}
@@ -1537,48 +1549,43 @@ func TestGetPositionStatus(t *testing.T) {
func TestCheckLiquidationStatus(t *testing.T) {
t.Parallel()
p := &Portfolio{}
var expectedError = common.ErrNilEvent
err := p.CheckLiquidationStatus(nil, nil, nil)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v', expected '%v'", err, expectedError)
if !errors.Is(err, common.ErrNilEvent) {
t.Errorf("received '%v', expected '%v'", err, common.ErrNilEvent)
}
ev := &kline.Kline{
Base: &event.Base{},
}
expectedError = gctcommon.ErrNilPointer
err = p.CheckLiquidationStatus(ev, nil, nil)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v', expected '%v'", err, expectedError)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNilPointer)
}
item := asset.Futures
pair := currency.NewPair(currency.BTC, currency.USDT)
expectedError = nil
contract, err := funding.CreateItem(testExchange, item, pair.Base, decimal.NewFromInt(100), decimal.Zero)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v", err, nil)
}
collateral, err := funding.CreateItem(testExchange, item, pair.Quote, decimal.NewFromInt(100), decimal.Zero)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v", err, nil)
}
collat, err := funding.CreateCollateral(contract, collateral)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v", err, nil)
}
expectedError = gctcommon.ErrNilPointer
err = p.CheckLiquidationStatus(ev, collat, nil)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v', expected '%v'", err, expectedError)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNilPointer)
}
pnl := &PNLSummary{}
expectedError = futures.ErrNotFuturesAsset
err = p.CheckLiquidationStatus(ev, collat, pnl)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v', expected '%v'", err, expectedError)
if !errors.Is(err, futures.ErrNotFuturesAsset) {
t.Errorf("received '%v', expected '%v'", err, futures.ErrNotFuturesAsset)
}
pnl.Asset = asset.Futures
@@ -1587,7 +1594,6 @@ func TestCheckLiquidationStatus(t *testing.T) {
ev.CurrencyPair = pair
exch := &binance.Binance{}
exch.Name = ev.Exchange
expectedError = nil
err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: exch, Asset: asset.Futures, Pair: pair})
if !errors.Is(err, gctcommon.ErrNotYetImplemented) {
t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNotYetImplemented)
@@ -1622,17 +1628,19 @@ func TestCheckLiquidationStatus(t *testing.T) {
}
err = settings.FuturesTracker.TrackNewOrder(od)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v', expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType] = make(map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][pair.Base.Item] = make(map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][pair.Base.Item][pair.Quote.Item] = settings
p.exchangeAssetPairPortfolioSettings = make(map[key.ExchangePairAsset]*Settings)
p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: testExchange,
Base: ev.Pair().Base.Item,
Quote: ev.Pair().Quote.Item,
Asset: asset.Futures,
}] = settings
err = p.CheckLiquidationStatus(ev, collat, pnl)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v', expected '%v'", err, expectedError)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -44,7 +45,7 @@ type Portfolio struct {
riskFreeRate decimal.Decimal
sizeManager SizeHandler
riskManager risk.Handler
exchangeAssetPairPortfolioSettings map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings
exchangeAssetPairPortfolioSettings map[key.ExchangePairAsset]*Settings
}
// Handler contains all functions expected to operate a portfolio manager

View File

@@ -9,6 +9,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
)
@@ -25,7 +26,12 @@ func (r *Risk) EvaluateOrder(o order.Event, latestHoldings []holdings.Holding, s
ex := o.GetExchange()
a := o.GetAssetType()
p := o.Pair().Format(currency.EMPTYFORMAT)
lookup, ok := r.CurrencySettings[ex][a][p.Base.Item][p.Quote.Item]
lookup, ok := r.CurrencySettings[key.ExchangePairAsset{
Exchange: ex,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("%v %v %v %w", ex, a, p, errNoCurrencySettings)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order"
@@ -68,16 +69,18 @@ func TestEvaluateOrder(t *testing.T) {
},
}
h := []holdings.Holding{}
r.CurrencySettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencySettings)
r.CurrencySettings[e] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencySettings)
r.CurrencySettings[e][a] = make(map[*currency.Item]map[*currency.Item]*CurrencySettings)
r.CurrencySettings[e][a][p.Base.Item] = make(map[*currency.Item]*CurrencySettings)
r.CurrencySettings = make(map[key.ExchangePairAsset]*CurrencySettings)
_, err = r.EvaluateOrder(o, h, compliance.Snapshot{})
if !errors.Is(err, errNoCurrencySettings) {
t.Error(err)
}
r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item] = &CurrencySettings{
r.CurrencySettings[key.ExchangePairAsset{
Exchange: e,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}] = &CurrencySettings{
MaximumOrdersWithLeverageRatio: decimal.NewFromFloat(0.3),
MaxLeverageRate: decimal.NewFromFloat(0.3),
MaximumHoldingRatio: decimal.NewFromFloat(0.3),
@@ -96,7 +99,12 @@ func TestEvaluateOrder(t *testing.T) {
Pair: currency.NewPair(currency.DOGE, currency.USDT),
})
o.Leverage = decimal.NewFromFloat(1.1)
r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item].MaximumHoldingRatio = decimal.Zero
r.CurrencySettings[key.ExchangePairAsset{
Exchange: e,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}].MaximumHoldingRatio = decimal.Zero
_, err = r.EvaluateOrder(o, h, compliance.Snapshot{})
if !errors.Is(err, errLeverageNotAllowed) {
t.Error(err)
@@ -108,14 +116,24 @@ func TestEvaluateOrder(t *testing.T) {
}
r.MaximumLeverage = decimal.NewFromInt(33)
r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item].MaxLeverageRate = decimal.NewFromInt(33)
r.CurrencySettings[key.ExchangePairAsset{
Exchange: e,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}].MaxLeverageRate = decimal.NewFromInt(33)
_, err = r.EvaluateOrder(o, h, compliance.Snapshot{})
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
r.MaximumLeverage = decimal.NewFromInt(33)
r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item].MaxLeverageRate = decimal.NewFromInt(33)
r.CurrencySettings[key.ExchangePairAsset{
Exchange: e,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}].MaxLeverageRate = decimal.NewFromInt(33)
_, err = r.EvaluateOrder(o, h, compliance.Snapshot{
Orders: []compliance.SnapshotOrder{
@@ -131,7 +149,12 @@ func TestEvaluateOrder(t *testing.T) {
}
h = append(h, holdings.Holding{Pair: p, BaseValue: decimal.NewFromInt(1337)}, holdings.Holding{Pair: p, BaseValue: decimal.NewFromFloat(1337.42)})
r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item].MaximumHoldingRatio = decimal.NewFromFloat(0.1)
r.CurrencySettings[key.ExchangePairAsset{
Exchange: e,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}].MaximumHoldingRatio = decimal.NewFromFloat(0.1)
_, err = r.EvaluateOrder(o, h, compliance.Snapshot{})
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)

View File

@@ -7,8 +7,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/common/key"
)
var (
@@ -24,7 +23,7 @@ type Handler interface {
// Risk contains all currency settings in order to evaluate potential orders
type Risk struct {
CurrencySettings map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencySettings
CurrencySettings map[key.ExchangePairAsset]*CurrencySettings
CanUseLeverage bool
MaximumLeverage decimal.Decimal
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/risk"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/futures"
)
@@ -37,7 +37,7 @@ func (p *Portfolio) Reset() error {
if p == nil {
return gctcommon.ErrNilPointer
}
p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings = make(map[key.ExchangePairAsset]*Settings)
p.riskFreeRate = decimal.Zero
p.sizeManager = nil
p.riskManager = nil
@@ -60,24 +60,10 @@ func (p *Portfolio) SetCurrencySettingsMap(setup *exchange.Settings) error {
}
if p.exchangeAssetPairPortfolioSettings == nil {
p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings = make(map[key.ExchangePairAsset]*Settings)
}
name := strings.ToLower(setup.Exchange.GetName())
m, ok := p.exchangeAssetPairPortfolioSettings[name]
if !ok {
m = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings)
p.exchangeAssetPairPortfolioSettings[name] = m
}
m2, ok := m[setup.Asset]
if !ok {
m2 = make(map[*currency.Item]map[*currency.Item]*Settings)
m[setup.Asset] = m2
}
m3, ok := m2[setup.Pair.Base.Item]
if !ok {
m3 = make(map[*currency.Item]*Settings)
m2[setup.Pair.Base.Item] = m3
}
settings := &Settings{
Exchange: setup.Exchange,
exchangeName: name,
@@ -112,6 +98,11 @@ func (p *Portfolio) SetCurrencySettingsMap(setup *exchange.Settings) error {
}
settings.FuturesTracker = tracker
}
m3[setup.Pair.Quote.Item] = settings
p.exchangeAssetPairPortfolioSettings[key.ExchangePairAsset{
Exchange: name,
Base: setup.Pair.Base.Item,
Quote: setup.Pair.Quote.Item,
Asset: setup.Asset,
}] = settings
return nil
}

View File

@@ -15,6 +15,11 @@ import (
// CalculateResults calculates all statistics for the exchange, asset, currency pair
func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) error {
first := c.Events[0]
if first.DataEvent == nil {
// you can call stop while a backtester run is running
// if the first data event isn't present, then it hasn't been properly run
return errNoDataAtOffset
}
sep := fmt.Sprintf("%v %v %v |\t", first.DataEvent.GetExchange(), first.DataEvent.GetAssetType(), first.DataEvent.Pair())
firstPrice := first.ClosePrice

View File

@@ -157,7 +157,7 @@ func TestCalculateResults(t *testing.T) {
}
}
func TestPrintResults(_ *testing.T) {
func TestPrintResults(t *testing.T) {
cs := CurrencyPairStatistic{}
tt1 := time.Now()
tt2 := time.Now().Add(gctkline.OneDay.Duration())
@@ -249,7 +249,10 @@ func TestPrintResults(_ *testing.T) {
}
cs.Events = append(cs.Events, ev, ev2)
cs.PrintResults(exch, a, p, true)
err := cs.PrintResults(exch, a, p, true)
if err != nil {
t.Error(err)
}
}
func TestCalculateHighestCommittedFunds(t *testing.T) {

View File

@@ -8,15 +8,14 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
gctmath "github.com/thrasher-corp/gocryptotrader/common/math"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
)
// CalculateFundingStatistics calculates funding statistics for total USD strategy results
// along with individual funding item statistics
func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic, riskFreeRate decimal.Decimal, interval gctkline.Interval) (*FundingStatistics, error) {
func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[key.ExchangePairAsset]*CurrencyPairStatistic, riskFreeRate decimal.Decimal, interval gctkline.Interval) (*FundingStatistics, error) {
if currStats == nil {
return nil, gctcommon.ErrNilPointer
}
@@ -31,28 +30,25 @@ func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[str
Report: report,
}
for i := range report.Items {
exchangeAssetStats, ok := currStats[report.Items[i].Exchange][report.Items[i].Asset]
if !ok {
if report.Items[i].AppendedViaAPI {
// items added via API may not have been processed along with typical events
// are not relevant to calculating statistics
continue
}
return nil, fmt.Errorf("%w for %v %v",
errNoRelevantStatsFound,
report.Items[i].Exchange,
report.Items[i].Asset)
}
var relevantStats []relatedCurrencyPairStatistics
for b, baseMap := range exchangeAssetStats {
for q, v := range baseMap {
if b.Currency().Equal(report.Items[i].Currency) {
relevantStats = append(relevantStats, relatedCurrencyPairStatistics{isBaseCurrency: true, stat: v})
for k, v := range currStats {
if !k.MatchesExchangeAsset(report.Items[0].Exchange, report.Items[0].Asset) {
if report.Items[i].AppendedViaAPI {
// items added via API may not have been processed along with typical events
// are not relevant to calculating statistics
continue
}
if q.Currency().Equal(report.Items[i].Currency) {
relevantStats = append(relevantStats, relatedCurrencyPairStatistics{stat: v})
}
return nil, fmt.Errorf("%w for %v %v",
errNoRelevantStatsFound,
report.Items[i].Exchange,
report.Items[i].Asset)
}
if k.Base.Currency().Equal(report.Items[i].Currency) {
relevantStats = append(relevantStats, relatedCurrencyPairStatistics{isBaseCurrency: true, stat: v})
continue
}
if k.Quote.Currency().Equal(report.Items[i].Currency) {
relevantStats = append(relevantStats, relatedCurrencyPairStatistics{stat: v})
}
}
var fundingStat *FundingItemStatistics

View File

@@ -11,6 +11,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/data/kline"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/engine"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -77,10 +78,10 @@ func TestCalculateFundingStatistics(t *testing.T) {
t.Errorf("received %v expected %v", err, funding.ErrUSDTrackingDisabled)
}
cs := make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
cs := make(map[key.ExchangePairAsset]*CurrencyPairStatistic)
_, err = CalculateFundingStatistics(f, cs, decimal.Zero, gctkline.OneHour)
if !errors.Is(err, errNoRelevantStatsFound) {
t.Errorf("received %v expected %v", err, errNoRelevantStatsFound)
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
f, err = funding.SetupFundingManager(&engine.ExchangeManager{}, true, false, false)
@@ -99,10 +100,12 @@ func TestCalculateFundingStatistics(t *testing.T) {
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
cs["binance"] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
cs["binance"][asset.Spot] = make(map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
cs["binance"][asset.Spot][currency.LTC.Item] = make(map[*currency.Item]*CurrencyPairStatistic)
cs["binance"][asset.Spot][currency.LTC.Item][currency.USD.Item] = &CurrencyPairStatistic{}
cs[key.ExchangePairAsset{
Exchange: "binance",
Base: currency.LTC.Item,
Quote: currency.USD.Item,
Asset: asset.Spot,
}] = &CurrencyPairStatistic{}
_, err = CalculateFundingStatistics(f, cs, decimal.Zero, gctkline.OneHour)
if !errors.Is(err, errMissingSnapshots) {
t.Errorf("received %v expected %v", err, errMissingSnapshots)
@@ -115,9 +118,12 @@ func TestCalculateFundingStatistics(t *testing.T) {
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
cs["binance"][asset.Spot][currency.BTC.Item] = make(map[*currency.Item]*CurrencyPairStatistic)
cs["binance"][asset.Spot][currency.BTC.Item][currency.USDT.Item] = &CurrencyPairStatistic{}
cs[key.ExchangePairAsset{
Exchange: "binance",
Base: currency.LTC.Item,
Quote: currency.USD.Item,
Asset: asset.Spot,
}] = &CurrencyPairStatistic{}
_, err = CalculateFundingStatistics(f, cs, decimal.Zero, gctkline.OneHour)
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)

View File

@@ -74,41 +74,35 @@ func (s *Statistic) PrintAllEventsChronologically() {
log.Infoln(common.Statistics, common.CMDColours.H1+"------------------Events-------------------------------------"+common.CMDColours.Default)
var errs error
var results []eventOutputHolder
for _, exchangeMap := range s.ExchangeAssetPairStatistics {
for _, assetMap := range exchangeMap {
for _, baseMap := range assetMap {
for _, currencyStatistic := range baseMap {
for i := range currencyStatistic.Events {
var result string
var tt time.Time
var err error
switch {
case currencyStatistic.Events[i].FillEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].FillEvent)
if err != nil {
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].FillEvent.GetTime()
case currencyStatistic.Events[i].SignalEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].SignalEvent)
if err != nil {
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].SignalEvent.GetTime()
case currencyStatistic.Events[i].DataEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].DataEvent)
if err != nil {
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].DataEvent.GetTime()
}
results = addEventOutputToTime(results, tt, result)
}
for _, currencyStatistic := range s.ExchangeAssetPairStatistics {
for i := range currencyStatistic.Events {
var result string
var tt time.Time
var err error
switch {
case currencyStatistic.Events[i].FillEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].FillEvent)
if err != nil {
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].FillEvent.GetTime()
case currencyStatistic.Events[i].SignalEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].SignalEvent)
if err != nil {
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].SignalEvent.GetTime()
case currencyStatistic.Events[i].DataEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].DataEvent)
if err != nil {
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].DataEvent.GetTime()
}
results = addEventOutputToTime(results, tt, result)
}
}
@@ -206,12 +200,18 @@ func (s *Statistic) CreateLog(data common.Event) (string, error) {
}
// PrintResults outputs all calculated statistics to the command line
func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.Pair, usingExchangeLevelFunding bool) {
func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.Pair, usingExchangeLevelFunding bool) error {
if len(c.Events) == 0 {
return errCurrencyStatisticsUnset
}
sort.Slice(c.Events, func(i, j int) bool {
return c.Events[i].Time.Before(c.Events[j].Time)
})
last := c.Events[len(c.Events)-1]
first := c.Events[0]
if first.DataEvent == nil {
return errNoDataAtOffset
}
c.StartingClosePrice.Value = first.DataEvent.GetClosePrice()
c.StartingClosePrice.Time = first.Time
c.EndingClosePrice.Value = last.DataEvent.GetClosePrice()
@@ -302,6 +302,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
log.Infof(common.CurrencyStatistics, "%s Final Unrealised PNL: %s", sep, convert.DecimalToHumanFriendlyString(unrealised.PNL, 8, ".", ","))
log.Infof(common.CurrencyStatistics, "%s Final Realised PNL: %s", sep, convert.DecimalToHumanFriendlyString(realised.PNL, 8, ".", ","))
}
return nil
}
// PrintResults outputs all calculated funding statistics to the command line

View File

@@ -15,8 +15,8 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -33,7 +33,7 @@ func (s *Statistic) Reset() error {
s.EndDate = time.Time{}
s.CandleInterval = 0
s.RiskFreeRate = decimal.Zero
s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
s.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*CurrencyPairStatistic)
s.CurrencyStatistics = nil
s.TotalBuyOrders = 0
s.TotalLongOrders = 0
@@ -62,46 +62,38 @@ func (s *Statistic) SetEventForOffset(ev common.Event) error {
a := ev.GetAssetType()
p := ev.Pair()
if s.ExchangeAssetPairStatistics == nil {
s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
s.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*CurrencyPairStatistic)
}
m, ok := s.ExchangeAssetPairStatistics[ex]
if !ok {
m = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
s.ExchangeAssetPairStatistics[ex] = m
mapKey := key.ExchangePairAsset{
Exchange: ex,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}
m2, ok := m[a]
stats, ok := s.ExchangeAssetPairStatistics[mapKey]
if !ok {
m2 = make(map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
m[a] = m2
}
m3, ok := m2[p.Base.Item]
if !ok {
m3 = make(map[*currency.Item]*CurrencyPairStatistic)
m2[p.Base.Item] = m3
}
lookup, ok := m3[p.Quote.Item]
if !ok {
lookup = &CurrencyPairStatistic{
Exchange: ev.GetExchange(),
Asset: ev.GetAssetType(),
Currency: ev.Pair(),
stats = &CurrencyPairStatistic{
Exchange: ex,
Asset: a,
Currency: p,
UnderlyingPair: ev.GetUnderlyingPair(),
}
m3[p.Quote.Item] = lookup
s.ExchangeAssetPairStatistics[mapKey] = stats
}
for i := range lookup.Events {
if lookup.Events[i].Offset != ev.GetOffset() {
for i := range stats.Events {
if stats.Events[i].Offset != ev.GetOffset() {
continue
}
return applyEventAtOffset(ev, &lookup.Events[i])
return applyEventAtOffset(ev, &stats.Events[i])
}
// add to events and then apply the supplied event to it
lookup.Events = append(lookup.Events, DataAtOffset{
stats.Events = append(stats.Events, DataAtOffset{
Offset: ev.GetOffset(),
Time: ev.GetTime(),
})
err := applyEventAtOffset(ev, &lookup.Events[len(lookup.Events)-1])
err := applyEventAtOffset(ev, &stats.Events[len(stats.Events)-1])
if err != nil {
return err
}
@@ -146,7 +138,12 @@ func (s *Statistic) AddHoldingsForTime(h *holdings.Holding) error {
if s.ExchangeAssetPairStatistics == nil {
return errExchangeAssetPairStatsUnset
}
lookup := s.ExchangeAssetPairStatistics[h.Exchange][h.Asset][h.Pair.Base.Item][h.Pair.Quote.Item]
lookup := s.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: h.Exchange,
Base: h.Pair.Base.Item,
Quote: h.Pair.Quote.Item,
Asset: h.Asset,
}]
if lookup == nil {
return fmt.Errorf("%w for %v %v %v to set holding event", errCurrencyStatisticsUnset, h.Exchange, h.Asset, h.Pair)
}
@@ -167,7 +164,12 @@ func (s *Statistic) AddPNLForTime(pnl *portfolio.PNLSummary) error {
if s.ExchangeAssetPairStatistics == nil {
return errExchangeAssetPairStatsUnset
}
lookup := s.ExchangeAssetPairStatistics[pnl.Exchange][pnl.Asset][pnl.Pair.Base.Item][pnl.Pair.Quote.Item]
lookup := s.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: pnl.Exchange,
Base: pnl.Pair.Base.Item,
Quote: pnl.Pair.Quote.Item,
Asset: pnl.Asset,
}]
if lookup == nil {
return fmt.Errorf("%w for %v %v %v to set pnl", errCurrencyStatisticsUnset, pnl.Exchange, pnl.Asset, pnl.Pair)
}
@@ -195,7 +197,12 @@ func (s *Statistic) AddComplianceSnapshotForTime(c *compliance.Snapshot, e commo
exch := e.GetExchange()
a := e.GetAssetType()
p := e.Pair()
lookup := s.ExchangeAssetPairStatistics[exch][a][p.Base.Item][p.Quote.Item]
lookup := s.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: exch,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if lookup == nil {
return fmt.Errorf("%w for %v %v %v to set compliance snapshot", errCurrencyStatisticsUnset, exch, a, p)
}
@@ -214,53 +221,50 @@ func (s *Statistic) CalculateAllResults() error {
log.Infoln(common.Statistics, "Calculating backtesting results")
s.PrintAllEventsChronologically()
currCount := 0
var finalResults []FinalResultsHolder
finalResults := make([]FinalResultsHolder, 0, len(s.ExchangeAssetPairStatistics))
var err error
for exchangeName, exchangeMap := range s.ExchangeAssetPairStatistics {
for assetItem, assetMap := range exchangeMap {
for b, baseMap := range assetMap {
for q, stats := range baseMap {
currCount++
last := stats.Events[len(stats.Events)-1]
if last.PNL != nil {
s.HasCollateral = true
}
err = stats.CalculateResults(s.RiskFreeRate)
if err != nil {
log.Errorln(common.Statistics, err)
}
stats.FinalHoldings = last.Holdings
stats.InitialHoldings = stats.Events[0].Holdings
if last.ComplianceSnapshot == nil {
return errMissingSnapshots
}
stats.FinalOrders = *last.ComplianceSnapshot
s.StartDate = stats.Events[0].Time
s.EndDate = last.Time
cp := currency.NewPair(b.Currency(), q.Currency())
stats.PrintResults(exchangeName, assetItem, cp, s.FundManager.IsUsingExchangeLevelFunding())
for mapKey, stats := range s.ExchangeAssetPairStatistics {
currCount++
last := stats.Events[len(stats.Events)-1]
if last.PNL != nil {
s.HasCollateral = true
}
err = stats.CalculateResults(s.RiskFreeRate)
if err != nil {
log.Errorln(common.Statistics, err)
}
stats.FinalHoldings = last.Holdings
stats.InitialHoldings = stats.Events[0].Holdings
if last.ComplianceSnapshot == nil {
return errMissingSnapshots
}
stats.FinalOrders = *last.ComplianceSnapshot
s.StartDate = stats.Events[0].Time
s.EndDate = last.Time
cp := currency.NewPair(mapKey.Base.Currency(), mapKey.Quote.Currency())
err = stats.PrintResults(mapKey.Exchange, mapKey.Asset, cp, s.FundManager.IsUsingExchangeLevelFunding())
if err != nil {
return err
}
finalResults = append(finalResults, FinalResultsHolder{
Exchange: exchangeName,
Asset: assetItem,
Pair: cp,
MaxDrawdown: stats.MaxDrawdown,
MarketMovement: stats.MarketMovement,
StrategyMovement: stats.StrategyMovement,
})
if assetItem.IsFutures() {
s.TotalLongOrders += stats.BuyOrders
s.TotalShortOrders += stats.SellOrders
} else {
s.TotalBuyOrders += stats.BuyOrders
s.TotalSellOrders += stats.SellOrders
}
s.TotalOrders += stats.TotalOrders
if stats.ShowMissingDataWarning {
s.WasAnyDataMissing = true
}
}
}
finalResults = append(finalResults, FinalResultsHolder{
Exchange: mapKey.Exchange,
Asset: mapKey.Asset,
Pair: cp,
MaxDrawdown: stats.MaxDrawdown,
MarketMovement: stats.MarketMovement,
StrategyMovement: stats.StrategyMovement,
})
if mapKey.Asset.IsFutures() {
s.TotalLongOrders += stats.BuyOrders
s.TotalShortOrders += stats.SellOrders
} else {
s.TotalBuyOrders += stats.BuyOrders
s.TotalSellOrders += stats.SellOrders
}
s.TotalOrders += stats.TotalOrders
if stats.ShowMissingDataWarning {
s.WasAnyDataMissing = true
}
}
s.FundingStatistics, err = CalculateFundingStatistics(s.FundManager, s.ExchangeAssetPairStatistics, s.RiskFreeRate, s.CandleInterval)
@@ -336,14 +340,8 @@ func (s *Statistic) SetStrategyName(name string) {
// Serialise outputs the Statistic struct in json
func (s *Statistic) Serialise() (string, error) {
s.CurrencyStatistics = nil
for _, exchangeMap := range s.ExchangeAssetPairStatistics {
for _, assetMap := range exchangeMap {
for _, baseMap := range assetMap {
for _, stats := range baseMap {
s.CurrencyStatistics = append(s.CurrencyStatistics, stats)
}
}
}
for _, stats := range s.ExchangeAssetPairStatistics {
s.CurrencyStatistics = append(s.CurrencyStatistics, stats)
}
resp, err := json.MarshalIndent(s, "", " ")

View File

@@ -18,6 +18,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/engine"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -85,7 +86,12 @@ func TestAddDataEventForTime(t *testing.T) {
if s.ExchangeAssetPairStatistics == nil {
t.Error("expected not nil")
}
if len(s.ExchangeAssetPairStatistics[exch][a][p.Base.Item][p.Quote.Item].Events) != 1 {
if len(s.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: exch,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}].Events) != 1 {
t.Error("expected 1 event")
}
}
@@ -105,7 +111,7 @@ func TestAddSignalEventForTime(t *testing.T) {
if !errors.Is(err, common.ErrNilEvent) {
t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent)
}
s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
s.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*CurrencyPairStatistic)
b := &event.Base{}
err = s.SetEventForOffset(&signal.Signal{
Base: b,
@@ -154,7 +160,7 @@ func TestAddExchangeEventForTime(t *testing.T) {
if !errors.Is(err, common.ErrNilEvent) {
t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent)
}
s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
s.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*CurrencyPairStatistic)
b := &event.Base{}
b.Exchange = exch
@@ -203,7 +209,7 @@ func TestAddFillEventForTime(t *testing.T) {
if !errors.Is(err, common.ErrNilEvent) {
t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent)
}
s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
s.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*CurrencyPairStatistic)
b := &event.Base{}
err = s.SetEventForOffset(&fill.Fill{
Base: b,
@@ -255,7 +261,7 @@ func TestAddHoldingsForTime(t *testing.T) {
if !errors.Is(err, errExchangeAssetPairStatsUnset) {
t.Errorf("received: %v, expected: %v", err, errExchangeAssetPairStatsUnset)
}
s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
s.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*CurrencyPairStatistic)
err = s.AddHoldingsForTime(&holdings.Holding{})
if !errors.Is(err, errCurrencyStatisticsUnset) {
t.Errorf("received: %v, expected: %v", err, errCurrencyStatisticsUnset)
@@ -324,7 +330,7 @@ func TestAddComplianceSnapshotForTime(t *testing.T) {
if !errors.Is(err, errExchangeAssetPairStatsUnset) {
t.Errorf("received: %v, expected: %v", err, errExchangeAssetPairStatsUnset)
}
s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic)
s.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*CurrencyPairStatistic)
b := &event.Base{}
err = s.AddComplianceSnapshotForTime(&compliance.Snapshot{}, &fill.Fill{Base: b})
if !errors.Is(err, errCurrencyStatisticsUnset) {
@@ -726,10 +732,22 @@ func TestCalculateTheResults(t *testing.T) {
t.Errorf("received: %v, expected: %v", err, nil)
}
s.ExchangeAssetPairStatistics[exch][a][p.Base.Item][p.Quote.Item].Events[1].Holdings.QuoteInitialFunds = eleet
s.ExchangeAssetPairStatistics[exch][a][p.Base.Item][p.Quote.Item].Events[1].Holdings.TotalValue = eleeet
s.ExchangeAssetPairStatistics[exch][a][p2.Base.Item][p2.Quote.Item].Events[1].Holdings.QuoteInitialFunds = eleet
s.ExchangeAssetPairStatistics[exch][a][p2.Base.Item][p2.Quote.Item].Events[1].Holdings.TotalValue = eleeet
mapKey1 := key.ExchangePairAsset{
Exchange: exch,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}
mapKey2 := key.ExchangePairAsset{
Exchange: exch,
Base: p2.Base.Item,
Quote: p2.Quote.Item,
Asset: a,
}
s.ExchangeAssetPairStatistics[mapKey1].Events[1].Holdings.QuoteInitialFunds = eleet
s.ExchangeAssetPairStatistics[mapKey1].Events[1].Holdings.TotalValue = eleeet
s.ExchangeAssetPairStatistics[mapKey2].Events[1].Holdings.QuoteInitialFunds = eleet
s.ExchangeAssetPairStatistics[mapKey2].Events[1].Holdings.TotalValue = eleeet
funds, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, false, false)
if !errors.Is(err, nil) {

View File

@@ -14,6 +14,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
@@ -34,28 +35,28 @@ var (
// Statistic holds all statistical information for a backtester run, from drawdowns to ratios.
// Any currency specific information is handled in currencystatistics
type Statistic struct {
StrategyName string `json:"strategy-name"`
StrategyDescription string `json:"strategy-description"`
StrategyNickname string `json:"strategy-nickname"`
StrategyGoal string `json:"strategy-goal"`
StartDate time.Time `json:"start-date"`
EndDate time.Time `json:"end-date"`
CandleInterval gctkline.Interval `json:"candle-interval"`
RiskFreeRate decimal.Decimal `json:"risk-free-rate"`
ExchangeAssetPairStatistics map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic `json:"exchange-asset-pair-statistics"`
CurrencyStatistics []*CurrencyPairStatistic `json:"currency-statistics"`
TotalBuyOrders int64 `json:"total-buy-orders"`
TotalLongOrders int64 `json:"total-long-orders"`
TotalShortOrders int64 `json:"total-short-orders"`
TotalSellOrders int64 `json:"total-sell-orders"`
TotalOrders int64 `json:"total-orders"`
BiggestDrawdown *FinalResultsHolder `json:"biggest-drawdown,omitempty"`
BestStrategyResults *FinalResultsHolder `json:"best-start-results,omitempty"`
BestMarketMovement *FinalResultsHolder `json:"best-market-movement,omitempty"`
WasAnyDataMissing bool `json:"was-any-data-missing"`
FundingStatistics *FundingStatistics `json:"funding-statistics"`
FundManager funding.IFundingManager `json:"-"`
HasCollateral bool `json:"has-collateral"`
StrategyName string `json:"strategy-name"`
StrategyDescription string `json:"strategy-description"`
StrategyNickname string `json:"strategy-nickname"`
StrategyGoal string `json:"strategy-goal"`
StartDate time.Time `json:"start-date"`
EndDate time.Time `json:"end-date"`
CandleInterval gctkline.Interval `json:"candle-interval"`
RiskFreeRate decimal.Decimal `json:"risk-free-rate"`
ExchangeAssetPairStatistics map[key.ExchangePairAsset]*CurrencyPairStatistic `json:"-"`
CurrencyStatistics []*CurrencyPairStatistic `json:"currency-statistics"`
TotalBuyOrders int64 `json:"total-buy-orders"`
TotalLongOrders int64 `json:"total-long-orders"`
TotalShortOrders int64 `json:"total-short-orders"`
TotalSellOrders int64 `json:"total-sell-orders"`
TotalOrders int64 `json:"total-orders"`
BiggestDrawdown *FinalResultsHolder `json:"biggest-drawdown,omitempty"`
BestStrategyResults *FinalResultsHolder `json:"best-start-results,omitempty"`
BestMarketMovement *FinalResultsHolder `json:"best-market-movement,omitempty"`
WasAnyDataMissing bool `json:"was-any-data-missing"`
FundingStatistics *FundingStatistics `json:"funding-statistics"`
FundManager funding.IFundingManager `json:"-"`
HasCollateral bool `json:"has-collateral"`
}
// FinalResultsHolder holds important stats about a currency's performance

View File

@@ -6,8 +6,8 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
// createUSDTotalsChart used for creating a chart in the HTML report
@@ -91,53 +91,47 @@ func createHoldingsOverTimeChart(stats []statistics.FundingItemStatistics) (*Cha
// createPNLCharts shows a running history of all realised and unrealised PNL values
// over time
func createPNLCharts(items map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) (*Chart, error) {
func createPNLCharts(items map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic) (*Chart, error) {
if items == nil {
return nil, fmt.Errorf("%w missing currency pair statistics", gctcommon.ErrNilPointer)
}
response := &Chart{
AxisType: "linear",
}
for exch, assetMap := range items {
for item, baseMap := range assetMap {
for b, quoteMap := range baseMap {
for q, result := range quoteMap {
id := fmt.Sprintf("%v %v %v%v",
exch,
item,
b,
q)
uPNLName := fmt.Sprintf("%v Unrealised PNL", id)
rPNLName := fmt.Sprintf("%v Realised PNL", id)
for mapKey, result := range items {
id := fmt.Sprintf("%v %v %v%v",
mapKey.Exchange,
mapKey.Asset,
mapKey.Base,
mapKey.Quote)
uPNLName := fmt.Sprintf("%v Unrealised PNL", id)
rPNLName := fmt.Sprintf("%v Realised PNL", id)
unrealisedPNL := ChartLine{Name: uPNLName}
realisedPNL := ChartLine{Name: rPNLName}
for i := range result.Events {
if result.Events[i].PNL != nil {
realisedPNL.LinePlots = append(realisedPNL.LinePlots, LinePlot{
Value: result.Events[i].PNL.GetRealisedPNL().PNL.InexactFloat64(),
UnixMilli: result.Events[i].Time.UnixMilli(),
})
unrealisedPNL.LinePlots = append(unrealisedPNL.LinePlots, LinePlot{
Value: result.Events[i].PNL.GetUnrealisedPNL().PNL.InexactFloat64(),
UnixMilli: result.Events[i].Time.UnixMilli(),
})
}
}
if len(unrealisedPNL.LinePlots) == 0 || len(realisedPNL.LinePlots) == 0 {
continue
}
response.Data = append(response.Data, unrealisedPNL, realisedPNL)
}
unrealisedPNL := ChartLine{Name: uPNLName}
realisedPNL := ChartLine{Name: rPNLName}
for i := range result.Events {
if result.Events[i].PNL != nil {
realisedPNL.LinePlots = append(realisedPNL.LinePlots, LinePlot{
Value: result.Events[i].PNL.GetRealisedPNL().PNL.InexactFloat64(),
UnixMilli: result.Events[i].Time.UnixMilli(),
})
unrealisedPNL.LinePlots = append(unrealisedPNL.LinePlots, LinePlot{
Value: result.Events[i].PNL.GetUnrealisedPNL().PNL.InexactFloat64(),
UnixMilli: result.Events[i].Time.UnixMilli(),
})
}
}
if len(unrealisedPNL.LinePlots) == 0 || len(realisedPNL.LinePlots) == 0 {
continue
}
response.Data = append(response.Data, unrealisedPNL, realisedPNL)
}
return response, nil
}
// createFuturesSpotDiffChart highlights the difference in futures and spot prices
// over time
func createFuturesSpotDiffChart(items map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) (*Chart, error) {
func createFuturesSpotDiffChart(items map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic) (*Chart, error) {
if items == nil {
return nil, fmt.Errorf("%w missing currency pair statistics", gctcommon.ErrNilPointer)
}
@@ -146,32 +140,26 @@ func createFuturesSpotDiffChart(items map[string]map[asset.Item]map[*currency.It
AxisType: "linear",
}
for _, assetMap := range items {
for item, baseMap := range assetMap {
for b, quoteMap := range baseMap {
for q, result := range quoteMap {
cp := currency.NewPair(b.Currency(), q.Currency())
if item.IsFutures() {
p := result.UnderlyingPair.Format(currency.EMPTYFORMAT)
diff, ok := currs[p]
if !ok {
diff = linkCurrencyDiff{}
}
diff.FuturesPair = cp
diff.SpotPair = p
diff.FuturesEvents = result.Events
currs[p] = diff
} else {
p := cp.Format(currency.EMPTYFORMAT)
diff, ok := currs[p]
if !ok {
diff = linkCurrencyDiff{}
}
diff.SpotEvents = result.Events
currs[p] = diff
}
}
for mapKey, result := range items {
cp := currency.NewPair(mapKey.Base.Currency(), mapKey.Quote.Currency())
if mapKey.Asset.IsFutures() {
p := result.UnderlyingPair.Format(currency.EMPTYFORMAT)
diff, ok := currs[p]
if !ok {
diff = linkCurrencyDiff{}
}
diff.FuturesPair = cp
diff.SpotPair = p
diff.FuturesEvents = result.Events
currs[p] = diff
} else {
p := cp.Format(currency.EMPTYFORMAT)
diff, ok := currs[p]
if !ok {
diff = linkCurrencyDiff{}
}
diff.SpotEvents = result.Events
currs[p] = diff
}
}

View File

@@ -11,6 +11,7 @@ import (
evkline "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/futures"
@@ -108,11 +109,13 @@ func TestCreatePNLCharts(t *testing.T) {
tt := time.Now()
var d Data
d.Statistics = &statistics.Statistic{}
d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item] = &statistics.CurrencyPairStatistic{
d.Statistics.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: testExchange,
Base: currency.BTC.Item,
Quote: currency.USDT.Item,
Asset: asset.Spot,
}] = &statistics.CurrencyPairStatistic{
Events: []statistics.DataAtOffset{
{
PNL: &portfolio.PNLSummary{
@@ -172,11 +175,13 @@ func TestCreateFuturesSpotDiffChart(t *testing.T) {
cp2 := currency.NewPair(currency.BTC, currency.DOGE)
var d Data
d.Statistics = &statistics.Statistic{}
d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USD.Item] = &statistics.CurrencyPairStatistic{
d.Statistics.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: testExchange,
Base: currency.BTC.Item,
Quote: currency.USD.Item,
Asset: asset.Spot,
}] = &statistics.CurrencyPairStatistic{
Currency: cp,
Events: []statistics.DataAtOffset{
{
@@ -196,9 +201,12 @@ func TestCreateFuturesSpotDiffChart(t *testing.T) {
},
},
}
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Futures] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Futures][currency.BTC.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Futures][currency.BTC.Item][currency.DOGE.Item] = &statistics.CurrencyPairStatistic{
d.Statistics.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: testExchange,
Base: currency.BTC.Item,
Quote: currency.DOGE.Item,
Asset: asset.Futures,
}] = &statistics.CurrencyPairStatistic{
UnderlyingPair: cp,
Currency: cp2,
Events: []statistics.DataAtOffset{

View File

@@ -9,6 +9,7 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/backtester/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/log"
@@ -148,8 +149,12 @@ func (d *Data) enhanceCandles() error {
Watermark: fmt.Sprintf("%s - %s - %s", cases.Title(language.English).String(lookup.Exchange), lookup.Asset.String(), lookup.Pair.Upper()),
}
statsForCandles :=
d.Statistics.ExchangeAssetPairStatistics[lookup.Exchange][lookup.Asset][lookup.Pair.Base.Item][lookup.Pair.Quote.Item]
statsForCandles := d.Statistics.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: lookup.Exchange,
Base: lookup.Pair.Base.Item,
Quote: lookup.Pair.Quote.Item,
Asset: lookup.Asset,
}]
if statsForCandles == nil {
continue
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
@@ -234,23 +235,22 @@ func TestGenerateReport(t *testing.T) {
},
StrategyName: "testStrat",
RiskFreeRate: decimal.NewFromFloat(0.03),
ExchangeAssetPairStatistics: map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic{
e: {
a: {
p.Base.Item: {
p.Quote.Item: &statistics.CurrencyPairStatistic{
LowestClosePrice: statistics.ValueAtTime{Value: decimal.NewFromInt(100)},
HighestClosePrice: statistics.ValueAtTime{Value: decimal.NewFromInt(200)},
MarketMovement: decimal.NewFromInt(100),
StrategyMovement: decimal.NewFromInt(100),
CompoundAnnualGrowthRate: decimal.NewFromInt(1),
BuyOrders: 1,
SellOrders: 1,
ArithmeticRatios: &statistics.Ratios{},
GeometricRatios: &statistics.Ratios{},
},
},
},
ExchangeAssetPairStatistics: map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic{
{
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
Exchange: e,
}: {
LowestClosePrice: statistics.ValueAtTime{Value: decimal.NewFromInt(100)},
HighestClosePrice: statistics.ValueAtTime{Value: decimal.NewFromInt(200)},
MarketMovement: decimal.NewFromInt(100),
StrategyMovement: decimal.NewFromInt(100),
CompoundAnnualGrowthRate: decimal.NewFromInt(1),
BuyOrders: 1,
SellOrders: 1,
ArithmeticRatios: &statistics.Ratios{},
GeometricRatios: &statistics.Ratios{},
},
},
TotalBuyOrders: 1337,
@@ -339,11 +339,13 @@ func TestEnhanceCandles(t *testing.T) {
t.Errorf("received: %v, expected: %v", err, nil)
}
d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item] = &statistics.CurrencyPairStatistic{}
d.Statistics.ExchangeAssetPairStatistics = make(map[key.ExchangePairAsset]*statistics.CurrencyPairStatistic)
d.Statistics.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: testExchange,
Base: currency.BTC.Item,
Quote: currency.USDT.Item,
Asset: asset.Spot,
}] = &statistics.CurrencyPairStatistic{}
err = d.SetKlineData(&gctkline.Item{
Exchange: testExchange,
@@ -402,7 +404,12 @@ func TestEnhanceCandles(t *testing.T) {
t.Errorf("received: %v, expected: %v", err, nil)
}
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item].FinalOrders = compliance.Snapshot{
d.Statistics.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: testExchange,
Base: currency.BTC.Item,
Quote: currency.USDT.Item,
Asset: asset.Spot,
}].FinalOrders = compliance.Snapshot{
Orders: []compliance.SnapshotOrder{
{
ClosePrice: decimal.NewFromInt(1335),
@@ -419,7 +426,12 @@ func TestEnhanceCandles(t *testing.T) {
t.Errorf("received: %v, expected: %v", err, nil)
}
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item].FinalOrders = compliance.Snapshot{
d.Statistics.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: testExchange,
Base: currency.BTC.Item,
Quote: currency.USDT.Item,
Asset: asset.Spot,
}].FinalOrders = compliance.Snapshot{
Orders: []compliance.SnapshotOrder{
{
ClosePrice: decimal.NewFromInt(1335),
@@ -439,7 +451,12 @@ func TestEnhanceCandles(t *testing.T) {
t.Errorf("received: %v, expected: %v", err, nil)
}
d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item].FinalOrders = compliance.Snapshot{
d.Statistics.ExchangeAssetPairStatistics[key.ExchangePairAsset{
Exchange: testExchange,
Base: currency.BTC.Item,
Quote: currency.USDT.Item,
Asset: asset.Spot,
}].FinalOrders = compliance.Snapshot{
Orders: []compliance.SnapshotOrder{
{
ClosePrice: decimal.NewFromInt(1335),

View File

@@ -36,7 +36,7 @@
<script type="application/javascript" src="https://code.highcharts.com/stock/modules/hollowcandlestick.js"></script>
<script type="application/javascript" src="https://code.highcharts.com/modules/exporting.js"></script>
<script type="application/javascript" src="https://code.highcharts.com/modules/export-data.js"></script>
</head>
<body>
@@ -329,28 +329,22 @@
</tr>
</thead>
<tbody>
{{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}}
{{ range $asset, $unused := .}}
{{ range $base, $unused := .}}
{{ range $quote, $unused := .}}
<tr>
<td>{{ $exchange}}</td>
<td>{{ $asset}}</td>
<td>{{ $base}}-{{$quote}}</td>
<td>{{ $.Prettify.Decimal8 .InitialHoldings.BaseInitialFunds }} {{.FinalHoldings.Pair.Base}}</td>
<td>{{ $.Prettify.Decimal8 .InitialHoldings.QuoteInitialFunds }} {{.FinalHoldings.Pair.Quote}}</td>
<td>{{ $.Prettify.Decimal8 .InitialHoldings.TotalInitialValue }} {{.FinalHoldings.Pair.Quote}}</td>
<td>{{ $.Prettify.Decimal8 .FinalHoldings.BaseSize }} {{ .FinalHoldings.Pair.Base}}</td>
<td>{{ $.Prettify.Decimal8 .FinalHoldings.QuoteSize }} {{ .FinalHoldings.Pair.Quote}}</td>
<td>{{ $.Prettify.Decimal8 .FinalHoldings.TotalValue }} {{ .FinalHoldings.Pair.Quote}}</td>
<td>{{ .IsStrategyProfitable }}</td>
<td> {{ .DoesPerformanceBeatTheMarket }}</td>
<td>{{ $.Prettify.Decimal8 .StrategyMovement }}%</td>
<td>{{ $.Prettify.Decimal8 .MarketMovement}}%</td>
</tr>
{{end}}
{{end}}
{{end}}
{{ range $mapKey, $stats := .Statistics.ExchangeAssetPairStatistics}}
<tr>
<td>{{ $mapKey.Exchange}}</td>
<td>{{ $mapKey.Asset}}</td>
<td>{{ $mapKey.Base.Currency}}-{{$mapKey.Quote.Currency}}</td>
<td>{{ $.Prettify.Decimal8 $stats.InitialHoldings.BaseInitialFunds }} {{$stats.FinalHoldings.Pair.Base}}</td>
<td>{{ $.Prettify.Decimal8 $stats.InitialHoldings.QuoteInitialFunds }} {{$stats.FinalHoldings.Pair.Quote}}</td>
<td>{{ $.Prettify.Decimal8 $stats.InitialHoldings.TotalInitialValue }} {{$stats.FinalHoldings.Pair.Quote}}</td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.BaseSize }} {{ $stats.FinalHoldings.Pair.Base}}</td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.QuoteSize }} {{ $stats.FinalHoldings.Pair.Quote}}</td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.TotalValue }} {{ $stats.FinalHoldings.Pair.Quote}}</td>
<td>{{ $stats.IsStrategyProfitable }}</td>
<td> {{ $stats.DoesPerformanceBeatTheMarket }}</td>
<td>{{ $.Prettify.Decimal8 $stats.StrategyMovement }}%</td>
<td>{{ $.Prettify.Decimal8 $stats.MarketMovement}}%</td>
</tr>
{{end}}
</tbody>
</table>
@@ -472,18 +466,18 @@
<tbody>
{{ range .Statistics.FundingStatistics.Report.Items}}
{{ if .AppendedViaAPI}}
{{else}}
<tr>
<td>{{.Exchange}}</td>
<td>{{.Asset}}</td>
<td>{{.Currency}}</td>
<td>{{.PairedWith}}</td>
<td>{{ $.Prettify.Decimal8 .InitialFunds}} {{.Currency}}</td>
<td>{{ $.Prettify.Decimal8 .FinalFunds}} {{.Currency}}</td>
<td>{{ $.Prettify.Decimal64 .TransferFee}}</td>
<td>{{ .IsCollateral }}</td>
</tr>
{{end}}
{{else}}
<tr>
<td>{{.Exchange}}</td>
<td>{{.Asset}}</td>
<td>{{.Currency}}</td>
<td>{{.PairedWith}}</td>
<td>{{ $.Prettify.Decimal8 .InitialFunds}} {{.Currency}}</td>
<td>{{ $.Prettify.Decimal8 .FinalFunds}} {{.Currency}}</td>
<td>{{ $.Prettify.Decimal64 .TransferFee}}</td>
<td>{{ .IsCollateral }}</td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
@@ -1229,240 +1223,235 @@
</table>
</div>
</div>
{{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}}
{{ range $asset, $unused := .}}
{{ range $base, $val := .}}
{{ range $quote, $val := .}}
<div class="card card-cascade narrower">
<div class="view view-cascade bg-primary">
<h2 id="currency-statistics" class="px-4 card-header-title text-light">Pair Statistics for {{$exchange}} {{ $asset}} {{ $base}}-{{$quote}}</h2>
</div>
<div class="card-body card-body-cascade ">
{{ range $mapKey, $stats := .Statistics.ExchangeAssetPairStatistics}}
<div class="card card-cascade narrower">
<div class="view view-cascade bg-primary">
<h2 id="currency-statistics" class="px-4 card-header-title text-light">Pair Statistics for {{$mapKey.Exchange}} {{ $mapKey.Asset}} {{ $mapKey.Base.Currency}}-{{$mapKey.Quote.Currency}}</h2>
</div>
<div class="card-body card-body-cascade ">
<table class="table table-hover table-bordered table-striped">
<tbody>
{{ if $stats.Asset.IsFutures }}
<tr>
<td><b>Long Orders</b></td>
<td>{{ $.Prettify.Int $stats.BuyOrders}}</td>
</tr>
<tr>
<td><b>Short Orders</b></td>
<td>{{ $.Prettify.Int $stats.SellOrders}}</td>
</tr>
<tr>
<td><b>Lowest Unrealised PNL</b></td>
<td>{{ $.Prettify.Decimal8 $stats.LowestUnrealisedPNL.Value}} at {{ $stats.LowestUnrealisedPNL.Time}}</td>
</tr>
<tr>
<td><b>Highest Unrealised PNL</b></td>
<td>{{ $.Prettify.Decimal8 $stats.HighestUnrealisedPNL.Value}} at {{ $stats.HighestUnrealisedPNL.Time}}</td>
</tr>
<tr>
<td><b>Lowest Realised PNL</b></td>
<td>{{ $.Prettify.Decimal8 $stats.LowestRealisedPNL.Value}} at {{ $stats.LowestRealisedPNL.Time}}</td>
</tr>
<tr>
<td><b>Highest Realised PNL</b></td>
<td>{{ $.Prettify.Decimal8 $stats.HighestRealisedPNL.Value}} at {{ $stats.HighestRealisedPNL.Time}}</td>
</tr>
{{ else }}
<tr>
<td><b>Base Initial Funds</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.BaseInitialFunds}} {{$stats.FinalHoldings.Pair.Base}}</td>
</tr>
<tr>
<td><b>Quote Initial Funds</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.QuoteInitialFunds}} {{$stats.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Buy Orders</b></td>
<td>{{ $.Prettify.Int $stats.BuyOrders}}</td>
</tr>
<tr>
<td><b>Buy Amount</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.BoughtAmount}} {{$stats.FinalHoldings.Pair.Base}} </td>
</tr>
<tr>
<td><b>Sell Orders</b></td>
<td>{{ $.Prettify.Int $stats.SellOrders}}</td>
</tr>
<tr>
<td><b>Sell Amount</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.SoldAmount}} {{$stats.FinalHoldings.Pair.Base}}</td>
</tr>
{{end}}
<tr>
<td><b>Total Orders</b></td>
<td>{{ $.Prettify.Int $stats.TotalOrders}}</td>
</tr>
{{ if $stats.MaxDrawdown.Highest.Value.IsZero }}
{{else}}
<tr>
<td><b>Biggest Drawdown</b></td>
<td><b>Start:</b> {{ $stats.MaxDrawdown.Highest.Time }} <b>End:</b> {{ $stats.MaxDrawdown.Lowest.Time }} <b>Drop:</b> {{ $.Prettify.Decimal8 $stats.MaxDrawdown.DrawdownPercent}}%</td>
</tr>
{{ end }}
<tr>
<td><b>Starting Close Price</b></td>
<td>{{ $.Prettify.Decimal8 $stats.StartingClosePrice.Value}} {{$stats.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Ending Close Price</b></td>
<td>{{ $.Prettify.Decimal8 $stats.EndingClosePrice.Value}} {{ $stats.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Lowest Close Price</b></td>
<td>{{ $.Prettify.Decimal8 $stats.LowestClosePrice.Value}} {{$stats.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Highest Close Price</b></td>
<td>{{ $.Prettify.Decimal8 $stats.HighestClosePrice.Value}} {{ $stats.FinalHoldings.Pair.Quote}}</td>
</tr>
{{ if $stats.Asset.IsFutures }}
{{else}}
<tr>
<td><b>Highest Committed Funds</b></td>
<td>{{ $.Prettify.Decimal8 $stats.HighestCommittedFunds.Value}} at {{ $stats.HighestCommittedFunds.Time}}</td>
</tr>
{{end}}
<tr>
<td><b>Market Movement</b></td>
<td>{{ $.Prettify.Decimal8 $stats.MarketMovement}}%</td>
</tr>
{{ if $stats.Asset.IsFutures }}
{{else}}
{{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }}
<tr>
<td><b>Strategy Movement</b></td>
<td>{{ $.Prettify.Decimal8 $stats.StrategyMovement}}%</td>
</tr>
<tr>
<td><b>Did it beat the market?</b></td>
<td>{{ .DoesPerformanceBeatTheMarket }}</td>
</tr>
{{ end }}
{{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }}
<tr>
<td><b>Final Holdings Value</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.BaseValue}} {{ $stats.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Total Value</b></td>
<td><b>{{ $.Prettify.Decimal8 $stats.FinalHoldings.TotalValue}} {{ $stats.FinalHoldings.Pair.Quote}}</b></td>
</tr>
{{else}}
<tr>
<td><b>Total Value Lost to Volume Sizing</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.TotalValueLostToVolumeSizing}} {{$stats.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Total Value Lost to Slippage</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.TotalValueLostToSlippage}} {{ $stats.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Total Value Lost</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.TotalValueLost}} {{$stats.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Total Fees</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.TotalFees}} {{ $stats.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Final Funds</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.QuoteSize}} {{ $stats.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Final Holdings</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.BaseSize}} {{$stats.FinalHoldings.Pair.Base}}</td>
</tr>
{{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }}
<tr>
<td><b>Final Holdings Value</b></td>
<td>{{ $.Prettify.Decimal8 $stats.FinalHoldings.BaseValue}} {{ $stats.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Total Value</b></td>
<td><b>{{ $.Prettify.Decimal8 $stats.FinalHoldings.TotalValue}} {{ $stats.FinalHoldings.Pair.Quote}}</b></td>
</tr>
{{end }}
{{end}}
{{end}}
</tbody>
</table>
{{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }}
Rates
<table class="table table-hover table-bordered table-striped">
<tbody>
<tr>
<td><b>Risk Free Rate</b></td>
<td>{{$.Statistics.RiskFreeRate}}%</td>
</tr>
<tr>
<td><b>Compound Annual Growth Rate</b></td>
{{ if $stats.CompoundAnnualGrowthRate.IsZero}}
<td>N/A</td>
{{else}}
<td>{{ $.Prettify.Decimal8 $stats.CompoundAnnualGrowthRate}}%</td>
{{end}}
</tr>
</tbody>
</table>
{{ if gt $stats.TotalOrders 1}}
{{if $stats.ShowMissingDataWarning}}
<h3 class="bg-warning">Missing data was detected during this backtesting run<br />
Ratio calculations will be skewed</h3>
{{end}}
Arithmetic Ratios
<table class="table table-hover table-bordered table-striped">
<tbody>
{{ if $val.Asset.IsFutures }}
<tr>
<td><b>Long Orders</b></td>
<td>{{ $.Prettify.Int $val.BuyOrders}}</td>
</tr>
<tr>
<td><b>Short Orders</b></td>
<td>{{ $.Prettify.Int $val.SellOrders}}</td>
</tr>
<tr>
<td><b>Lowest Unrealised PNL</b></td>
<td>{{ $.Prettify.Decimal8 $val.LowestUnrealisedPNL.Value}} at {{ $val.LowestUnrealisedPNL.Time}}</td>
</tr>
<tr>
<td><b>Highest Unrealised PNL</b></td>
<td>{{ $.Prettify.Decimal8 $val.HighestUnrealisedPNL.Value}} at {{ $val.HighestUnrealisedPNL.Time}}</td>
</tr>
<tr>
<td><b>Lowest Realised PNL</b></td>
<td>{{ $.Prettify.Decimal8 $val.LowestRealisedPNL.Value}} at {{ $val.LowestRealisedPNL.Time}}</td>
</tr>
<tr>
<td><b>Highest Realised PNL</b></td>
<td>{{ $.Prettify.Decimal8 $val.HighestRealisedPNL.Value}} at {{ $val.HighestRealisedPNL.Time}}</td>
</tr>
{{ else }}
<tr>
<td><b>Base Initial Funds</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseInitialFunds}} {{$val.FinalHoldings.Pair.Base}}</td>
</tr>
<tr>
<td><b>Quote Initial Funds</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.QuoteInitialFunds}} {{$val.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Buy Orders</b></td>
<td>{{ $.Prettify.Int $val.BuyOrders}}</td>
</tr>
<tr>
<td><b>Buy Amount</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.BoughtAmount}} {{$val.FinalHoldings.Pair.Base}} </td>
</tr>
<tr>
<td><b>Sell Orders</b></td>
<td>{{ $.Prettify.Int $val.SellOrders}}</td>
</tr>
<tr>
<td><b>Sell Amount</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.SoldAmount}} {{$val.FinalHoldings.Pair.Base}}</td>
</tr>
{{end}}
<tr>
<td><b>Total Orders</b></td>
<td>{{ $.Prettify.Int $val.TotalOrders}}</td>
</tr>
{{ if $val.MaxDrawdown.Highest.Value.IsZero }}
{{else}}
<tr>
<td><b>Biggest Drawdown</b></td>
<td><b>Start:</b> {{ $val.MaxDrawdown.Highest.Time }} <b>End:</b> {{ $val.MaxDrawdown.Lowest.Time }} <b>Drop:</b> {{ $.Prettify.Decimal8 $val.MaxDrawdown.DrawdownPercent}}%</td>
</tr>
{{ end }}
<tr>
<td><b>Starting Close Price</b></td>
<td>{{ $.Prettify.Decimal8 $val.StartingClosePrice.Value}} {{$val.FinalHoldings.Pair.Quote}}</td>
<td><b>Sharpe Ratio</b></td>
<td>{{$stats.ArithmeticRatios.SharpeRatio}}</td>
</tr>
<tr>
<td><b>Ending Close Price</b></td>
<td>{{ $.Prettify.Decimal8 $val.EndingClosePrice.Value}} {{ $val.FinalHoldings.Pair.Quote }}</td>
<td><b>Sortino Ratio</b></td>
<td>{{$stats.ArithmeticRatios.SortinoRatio}}</td>
</tr>
<tr>
<td><b>Lowest Close Price</b></td>
<td>{{ $.Prettify.Decimal8 $val.LowestClosePrice.Value}} {{$val.FinalHoldings.Pair.Quote}}</td>
<td><b>Information Ratio</b></td>
<td>{{$stats.ArithmeticRatios.InformationRatio}}</td>
</tr>
<tr>
<td><b>Highest Close Price</b></td>
<td>{{ $.Prettify.Decimal8 $val.HighestClosePrice.Value}} {{ $val.FinalHoldings.Pair.Quote}}</td>
<td><b>Calmar Ratio</b></td>
<td>{{$stats.ArithmeticRatios.CalmarRatio}}</td>
</tr>
{{ if $val.Asset.IsFutures }}
{{else}}
<tr>
<td><b>Highest Committed Funds</b></td>
<td>{{ $.Prettify.Decimal8 $val.HighestCommittedFunds.Value}} at {{ $val.HighestCommittedFunds.Time}}</td>
</tr>
{{end}}
<tr>
<td><b>Market Movement</b></td>
<td>{{ $.Prettify.Decimal8 $val.MarketMovement}}%</td>
</tr>
{{ if $val.Asset.IsFutures }}
{{else}}
{{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }}
<tr>
<td><b>Strategy Movement</b></td>
<td>{{ $.Prettify.Decimal8 $val.StrategyMovement}}%</td>
</tr>
<tr>
<td><b>Did it beat the market?</b></td>
<td>{{ .DoesPerformanceBeatTheMarket }}</td>
</tr>
{{ end }}
{{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }}
<tr>
<td><b>Final Holdings Value</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseValue}} {{ $val.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Total Value</b></td>
<td><b>{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValue}} {{ $val.FinalHoldings.Pair.Quote}}</b></td>
</tr>
{{else}}
<tr>
<td><b>Total Value Lost to Volume Sizing</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLostToVolumeSizing}} {{$val.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Total Value Lost to Slippage</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLostToSlippage}} {{ $val.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Total Value Lost</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLost}} {{$val.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Total Fees</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalFees}} {{ $val.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Final Funds</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.QuoteSize}} {{ $val.FinalHoldings.Pair.Quote}}</td>
</tr>
<tr>
<td><b>Final Holdings</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseSize}} {{$val.FinalHoldings.Pair.Base}}</td>
</tr>
{{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }}
<tr>
<td><b>Final Holdings Value</b></td>
<td>{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseValue}} {{ $val.FinalHoldings.Pair.Quote }}</td>
</tr>
<tr>
<td><b>Total Value</b></td>
<td><b>{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValue}} {{ $val.FinalHoldings.Pair.Quote}}</b></td>
</tr>
{{end }}
{{end}}
{{end}}
</tbody>
</table>
{{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }}
Rates
<table class="table table-hover table-bordered table-striped">
<tbody>
<tr>
<td><b>Risk Free Rate</b></td>
<td>{{$.Statistics.RiskFreeRate}}%</td>
</tr>
<tr>
<td><b>Compound Annual Growth Rate</b></td>
{{ if $val.CompoundAnnualGrowthRate.IsZero}}
<td>N/A</td>
{{else}}
<td>{{ $.Prettify.Decimal8 $val.CompoundAnnualGrowthRate}}%</td>
{{end}}
</tr>
</tbody>
</table>
{{ if gt $val.TotalOrders 1}}
{{if $val.ShowMissingDataWarning}}
<h3 class="bg-warning">Missing data was detected during this backtesting run<br />
Ratio calculations will be skewed</h3>
{{end}}
Arithmetic Ratios
<table class="table table-hover table-bordered table-striped">
<tbody>
<tr>
<td><b>Sharpe Ratio</b></td>
<td>{{$val.ArithmeticRatios.SharpeRatio}}</td>
</tr>
<tr>
<td><b>Sortino Ratio</b></td>
<td>{{$val.ArithmeticRatios.SortinoRatio}}</td>
</tr>
<tr>
<td><b>Information Ratio</b></td>
<td>{{$val.ArithmeticRatios.InformationRatio}}</td>
</tr>
<tr>
<td><b>Calmar Ratio</b></td>
<td>{{$val.ArithmeticRatios.CalmarRatio}}</td>
</tr>
</tbody>
</table>
Geometric Ratios
<table class="table table-hover table-bordered table-striped">
<tbody>
<tr>
<td><b>Sharpe Ratio</b></td>
<td>{{$val.GeometricRatios.SharpeRatio}}</td>
</tr>
<tr>
<td><b>Sortino Ratio</b></td>
<td>{{$val.GeometricRatios.SortinoRatio}}</td>
</tr>
<tr>
<td><b>Information Ratio</b></td>
<td>{{$val.GeometricRatios.InformationRatio}}</td>
</tr>
<tr>
<td><b>Calmar Ratio</b></td>
<td>{{$val.GeometricRatios.CalmarRatio}}</td>
</tr>
</tbody>
</table>
{{end}}
{{end }}
{{end }}
</div>
</div>
{{end}}
{{end}}
{{end}}
Geometric Ratios
<table class="table table-hover table-bordered table-striped">
<tbody>
<tr>
<td><b>Sharpe Ratio</b></td>
<td>{{$stats.GeometricRatios.SharpeRatio}}</td>
</tr>
<tr>
<td><b>Sortino Ratio</b></td>
<td>{{$stats.GeometricRatios.SortinoRatio}}</td>
</tr>
<tr>
<td><b>Information Ratio</b></td>
<td>{{$stats.GeometricRatios.InformationRatio}}</td>
</tr>
<tr>
<td><b>Calmar Ratio</b></td>
<td>{{$stats.GeometricRatios.CalmarRatio}}</td>
</tr>
</tbody>
</table>
{{end}}
{{end }}
{{end }}
</div>
</div>
{{ if $.Config.StrategySettings.DisableUSDTracking }}
{{ if $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding }}
<div class="card card-cascade narrower">
@@ -1474,86 +1463,86 @@
{{end}}
{{ range .Statistics.FundingStatistics.Items }}
{{ if .ReportItem.AppendedViaAPI}}
{{else}}
<div class="card card-cascade narrower">
<div class="view view-cascade bg-primary">
<h2 id="funding-statistics" class="px-4 card-header-title text-light"> Funding Statistics for {{.ReportItem.Exchange}} {{.ReportItem.Asset}} {{.ReportItem.Currency}}</h2>
</div>
<div class="card-body card-body-cascade ">
<table class="table table-hover table-bordered table-striped">
<tbody>
{{ if .ReportItem.IsCollateral}}
<tr>
<td><b>Initial Collateral</b></td>
<td>{{ $.Prettify.Decimal8 .ReportItem.InitialFunds}}</td>
</tr>
<tr>
<td><b>Final Collateral</b></td>
<td>{{ $.Prettify.Decimal8 .ReportItem.FinalFunds}}</td>
</tr>
{{ else }}
<tr>
<td><b>Initial Funds</b></td>
<td>{{ $.Prettify.Decimal8 .ReportItem.InitialFunds}}</td>
</tr>
<tr>
<td><b>Final Funds</b></td>
<td>{{ $.Prettify.Decimal8 .ReportItem.FinalFunds}}</td>
</tr>
{{end }}
<tr>
<td><b>Difference</b></td>
{{ if .ReportItem.ShowInfinite}}
<td>Infinity%</td>
{{else}}
<td>{{ $.Prettify.Decimal8 .ReportItem.Difference}}%</td>
{{end}}
</tr>
{{ if eq $.Config.StrategySettings.DisableUSDTracking false }}
{{else}}
<div class="card card-cascade narrower">
<div class="view view-cascade bg-primary">
<h2 id="funding-statistics" class="px-4 card-header-title text-light"> Funding Statistics for {{.ReportItem.Exchange}} {{.ReportItem.Asset}} {{.ReportItem.Currency}}</h2>
</div>
<div class="card-body card-body-cascade ">
<table class="table table-hover table-bordered table-striped">
<tbody>
{{ if .ReportItem.IsCollateral}}
{{ else }}
{{ if .ReportItem.Currency.IsFiatCurrency}}
{{else}}
<tr>
<td><b>Starting Close Price</b></td>
<td>{{$.Prettify.Decimal8 .StartingClosePrice.Value}} at {{.StartingClosePrice.Time}}</td>
</tr>
<tr>
<td><b>Ending Close Price</b></td>
<td>{{$.Prettify.Decimal8 .EndingClosePrice.Value}} at {{.EndingClosePrice.Time}}</td>
</tr>
<tr>
<td><b>Highest Close Price</b></td>
<td>{{$.Prettify.Decimal8 .HighestClosePrice.Value}} at {{.HighestClosePrice.Time}}</td>
</tr>
<tr>
<td><b>Lowest Close Price</b></td>
<td>{{$.Prettify.Decimal8 .LowestClosePrice.Value}} at {{.LowestClosePrice.Time}}</td>
</tr>
<tr>
<td><b>Market Movement</b></td>
<td>{{$.Prettify.Decimal8 .MarketMovement}}%</td>
</tr>
{{end }}
<tr>
<td><b>Did Strategy Beat The Market?</b></td>
<td>{{.DidStrategyBeatTheMarket}}</td>
<td><b>Initial Collateral</b></td>
<td>{{ $.Prettify.Decimal8 .ReportItem.InitialFunds}}</td>
</tr>
<tr>
<td><b>Compound Annual Growth Rate</b></td>
{{ if .CompoundAnnualGrowthRate.IsZero}}
<td>N/A</td>
{{else}}
<td>{{$.Prettify.Decimal8 .CompoundAnnualGrowthRate}}%</td>
{{end}}
<td><b>Final Collateral</b></td>
<td>{{ $.Prettify.Decimal8 .ReportItem.FinalFunds}}</td>
</tr>
{{ else }}
<tr>
<td><b>Initial Funds</b></td>
<td>{{ $.Prettify.Decimal8 .ReportItem.InitialFunds}}</td>
</tr>
<tr>
<td><b>Final Funds</b></td>
<td>{{ $.Prettify.Decimal8 .ReportItem.FinalFunds}}</td>
</tr>
{{end }}
{{end}}
</tbody>
</table>
<tr>
<td><b>Difference</b></td>
{{ if .ReportItem.ShowInfinite}}
<td>Infinity%</td>
{{else}}
<td>{{ $.Prettify.Decimal8 .ReportItem.Difference}}%</td>
{{end}}
</tr>
{{ if eq $.Config.StrategySettings.DisableUSDTracking false }}
{{ if .ReportItem.IsCollateral}}
{{ else }}
{{ if .ReportItem.Currency.IsFiatCurrency}}
{{else}}
<tr>
<td><b>Starting Close Price</b></td>
<td>{{$.Prettify.Decimal8 .StartingClosePrice.Value}} at {{.StartingClosePrice.Time}}</td>
</tr>
<tr>
<td><b>Ending Close Price</b></td>
<td>{{$.Prettify.Decimal8 .EndingClosePrice.Value}} at {{.EndingClosePrice.Time}}</td>
</tr>
<tr>
<td><b>Highest Close Price</b></td>
<td>{{$.Prettify.Decimal8 .HighestClosePrice.Value}} at {{.HighestClosePrice.Time}}</td>
</tr>
<tr>
<td><b>Lowest Close Price</b></td>
<td>{{$.Prettify.Decimal8 .LowestClosePrice.Value}} at {{.LowestClosePrice.Time}}</td>
</tr>
<tr>
<td><b>Market Movement</b></td>
<td>{{$.Prettify.Decimal8 .MarketMovement}}%</td>
</tr>
{{end }}
<tr>
<td><b>Did Strategy Beat The Market?</b></td>
<td>{{.DidStrategyBeatTheMarket}}</td>
</tr>
<tr>
<td><b>Compound Annual Growth Rate</b></td>
{{ if .CompoundAnnualGrowthRate.IsZero}}
<td>N/A</td>
{{else}}
<td>{{$.Prettify.Decimal8 .CompoundAnnualGrowthRate}}%</td>
{{end}}
</tr>
{{end }}
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{end}}
{{end}}
{{end}}
{{ if eq $.Config.StrategySettings.DisableUSDTracking false }}
<div class="card card-cascade narrower">
@@ -1669,44 +1658,38 @@
<h2 id="orders" class="px-4 card-header-title text-light">Orders</h2>
</div>
<div class="card-body card-body-cascade ">
{{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}}
{{ range $asset, $unused := .}}
{{ range $base, $unused := .}}
{{ range $quote, $val := .}}
<div >
<h3>{{$exchange}} {{$asset}} {{ $base }}-{{$quote}}</h3>
</div>
<div >
<table class="table table-hover table-bordered table-striped">
<tr>
<th>Date</th>
<th>Close Price</th>
<th>Side</th>
<th>Price</th>
<th>Amount</th>
<th>Fee</th>
<th>Total</th>
<th>Slippage Rate</th>
</tr>
<tbody >
{{range $val.FinalOrders.Orders}}
<tr>
<td>{{ .Order.Date }}</td>
<td>{{ $.Prettify.Decimal8 .ClosePrice}} {{$quote}}</td>
<td>{{ .Order.Side }}</td>
<td>{{$.Prettify.Float8 .Order.Price }} {{$quote}}</td>
<td>{{$.Prettify.Float8 .Order.Amount }} {{$base}}</td>
<td>{{$.Prettify.Float8 .Order.Fee }} {{$quote}}</td>
<td>{{ $.Prettify.Decimal8 .CostBasis }} {{.Order.FeeAsset}}</td>
<td>{{ $.Prettify.Decimal8 .SlippageRate }}%</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{ range $mapKey, $val := .Statistics.ExchangeAssetPairStatistics}}
<div >
<h3>{{$mapKey.Exchange}} {{$mapKey.Asset}} {{ $mapKey.Base }}-{{$mapKey.Quote}}</h3>
</div>
<div >
<table class="table table-hover table-bordered table-striped">
<tr>
<th>Date</th>
<th>Close Price</th>
<th>Side</th>
<th>Price</th>
<th>Amount</th>
<th>Fee</th>
<th>Total</th>
<th>Slippage Rate</th>
</tr>
<tbody >
{{range $val.FinalOrders.Orders}}
<tr>
<td>{{ .Order.Date }}</td>
<td>{{ $.Prettify.Decimal8 .ClosePrice}} {{$mapKey.Quote}}</td>
<td>{{ .Order.Side }}</td>
<td>{{$.Prettify.Float8 .Order.Price }} {{$mapKey.Quote}}</td>
<td>{{$.Prettify.Float8 .Order.Amount }} {{$mapKey.Base}}</td>
<td>{{$.Prettify.Float8 .Order.Fee }} {{$mapKey.Quote}}</td>
<td>{{ $.Prettify.Decimal8 .CostBasis }} {{.Order.FeeAsset}}</td>
<td>{{ $.Prettify.Decimal8 .SlippageRate }}%</td>
</tr>
{{end}}
{{end}}
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
</div>
@@ -1716,85 +1699,79 @@
<h2 id="events" class="px-4 card-header-title text-light">Events</h2>
</div>
<div class="card-body card-body-cascade ">
{{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}}
{{ range $asset, $unused :=.}}
{{ range $base, $unused := .}}
{{ range $quote, $data := .}}
<div>
<h3>{{$exchange}} {{$asset}} {{ $base }}-{{$quote}}</h3>
</div>
<div >
<table class="table table-hover table-bordered table-striped">
<tr>
<th>Date</th>
<th>Price</th>
<th>Action</th>
<th>Event Details</th>
{{if $asset.IsFutures}}
<th>Holdings</th>
<th>Position Direction</th>
<th>Unrealised PNL</th>
<th>Realised PNL</th>
{{ else }}
<th>{{$base}} Funds</th>
<th>{{$quote}} Funds</th>
<th>Total value in {{$quote}}</th>
<th>Committed funds in {{$quote}}</th>
{{ end }}
{{ range $mapKey, $val := .Statistics.ExchangeAssetPairStatistics}}
<div>
<h3>{{$mapKey.Exchange}} {{$mapKey.Asset}} {{ $mapKey.Base }}-{{$mapKey.Quote}}</h3>
</div>
<div >
<table class="table table-hover table-bordered table-striped">
<tr>
<th>Date</th>
<th>Price</th>
<th>Action</th>
<th>Event Details</th>
{{if $mapKey.Asset.IsFutures}}
<th>Holdings</th>
<th>Position Direction</th>
<th>Unrealised PNL</th>
<th>Realised PNL</th>
{{ else }}
<th>{{$mapKey.Base}} Funds</th>
<th>{{$mapKey.Quote}} Funds</th>
<th>Total value in {{$mapKey.Quote}}</th>
<th>Committed funds in {{$mapKey.Quote}}</th>
{{ end }}
</tr>
<tbody >
{{range $ev := $data.Events}}
<tr>
{{ if ne $ev.FillEvent nil }}
<td><b>{{$ev.FillEvent.GetTime}}</b></td>
<td>{{ $.Prettify.Decimal8 $ev.FillEvent.GetClosePrice}} {{if $asset.IsFutures}}{{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{else}}{{$quote}}{{end}}</td>
<td>{{$ev.FillEvent.GetDirection}}</td>
<td>
<ul>
{{ range $ev.FillEvent.GetReasons }}
<li>{{.}}</li>
{{end}}
</ul>
</td>
{{ else if ne $ev.SignalEvent nil}}
<td>{{$ev.SignalEvent.GetTime}}</td>
<td>{{ $.Prettify.Decimal8 $ev.SignalEvent.GetClosePrice}} {{if $asset.IsFutures}}{{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{else}}{{$quote}}{{end}}</td>
<td>{{$ev.SignalEvent.GetDirection}}</td>
<td>
<ul>
{{ range $ev.SignalEvent.GetReasons }}
<li>{{.}}</li>
{{end}}
</ul>
</td>
{{ end }}
{{if $asset.IsFutures}}
{{if ne $ev.PNL nil }}
<td>{{ $.Prettify.Decimal8 $ev.PNL.GetExposure}} {{$base}}-{{$quote}}</td>
<td>{{$ev.PNL.GetDirection}}</td>
<td>{{$.Prettify.Decimal8 $ev.PNL.GetUnrealisedPNL.PNL}} {{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}</td>
<td>{{$.Prettify.Decimal8 $ev.PNL.GetRealisedPNL.PNL}} {{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}</td>
{{else}}
<td>0 {{$base}}-{{$quote}}</td>
<td>N/A</td>
<td>0</td>
<td>0</td>
{{end}}
{{else }}
<td>{{ $.Prettify.Decimal8 $ev.Holdings.BaseSize}} {{$base}}</td>
<td>{{ $.Prettify.Decimal8 $ev.Holdings.QuoteSize}} {{$quote}}</td>
<td>{{ $.Prettify.Decimal8 $ev.Holdings.TotalValue}} {{$quote}}</td>
<td>{{ $.Prettify.Decimal8 $ev.Holdings.CommittedFunds}} {{$quote}}</td>
</tr>
<tbody >
{{range $ev := $val.Events}}
<tr>
{{ if ne $ev.FillEvent nil }}
<td><b>{{$ev.FillEvent.GetTime}}</b></td>
<td>{{ $.Prettify.Decimal8 $ev.FillEvent.GetClosePrice}} {{if $mapKey.Asset.IsFutures}}{{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{else}}{{$mapKey.Quote}}{{end}}</td>
<td>{{$ev.FillEvent.GetDirection}}</td>
<td>
<ul>
{{ range $ev.FillEvent.GetReasons }}
<li>{{.}}</li>
{{end}}
</tr>
</ul>
</td>
{{ else if ne $ev.SignalEvent nil}}
<td>{{$ev.SignalEvent.GetTime}}</td>
<td>{{ $.Prettify.Decimal8 $ev.SignalEvent.GetClosePrice}} {{if $mapKey.Asset.IsFutures}}{{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{else}}{{$mapKey.Quote}}{{end}}</td>
<td>{{$ev.SignalEvent.GetDirection}}</td>
<td>
<ul>
{{ range $ev.SignalEvent.GetReasons }}
<li>{{.}}</li>
{{end}}
</ul>
</td>
{{ end }}
{{if $mapKey.Asset.IsFutures}}
{{if ne $ev.PNL nil }}
<td>{{ $.Prettify.Decimal8 $ev.PNL.GetExposure}} {{$mapKey.Base}}-{{$mapKey.Quote}}</td>
<td>{{$ev.PNL.GetDirection}}</td>
<td>{{$.Prettify.Decimal8 $ev.PNL.GetUnrealisedPNL.PNL}} {{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}</td>
<td>{{$.Prettify.Decimal8 $ev.PNL.GetRealisedPNL.PNL}} {{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}</td>
{{else}}
<td>0 {{$mapKey.Base}}-{{$mapKey.Quote}}</td>
<td>N/A</td>
<td>0</td>
<td>0</td>
{{end}}
</tbody>
</table>
</div>
{{else }}
<td>{{ $.Prettify.Decimal8 $ev.Holdings.BaseSize}} {{$mapKey.Base}}</td>
<td>{{ $.Prettify.Decimal8 $ev.Holdings.QuoteSize}} {{$mapKey.Quote}}</td>
<td>{{ $.Prettify.Decimal8 $ev.Holdings.TotalValue}} {{$mapKey.Quote}}</td>
<td>{{ $.Prettify.Decimal8 $ev.Holdings.CommittedFunds}} {{$mapKey.Quote}}</td>
{{end}}
</tr>
{{end}}
{{end}}
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
</div>

47
common/key/key.go Normal file
View File

@@ -0,0 +1,47 @@
package key
import (
"strings"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
// ExchangePairAsset is a unique map key signature for exchange, currency pair and asset
type ExchangePairAsset struct {
Exchange string
Base *currency.Item
Quote *currency.Item
Asset asset.Item
}
// PairAsset is a unique map key signature for currency pair and asset
type PairAsset struct {
Base *currency.Item
Quote *currency.Item
Asset asset.Item
}
// SubAccountCurrencyAsset is a unique map key signature for subaccount, currency code and asset
type SubAccountCurrencyAsset struct {
SubAccount string
Currency *currency.Item
Asset asset.Item
}
// MatchesExchangeAsset checks if the key matches the exchange and asset
func (k *ExchangePairAsset) MatchesExchangeAsset(exch string, item asset.Item) bool {
return strings.EqualFold(k.Exchange, exch) && k.Asset == item
}
// MatchesPairAsset checks if the key matches the pair and asset
func (k *ExchangePairAsset) MatchesPairAsset(pair currency.Pair, item asset.Item) bool {
return k.Base == pair.Base.Item &&
k.Quote == pair.Quote.Item &&
k.Asset == item
}
// MatchesExchange checks if the exchange matches
func (k *ExchangePairAsset) MatchesExchange(exch string) bool {
return strings.EqualFold(k.Exchange, exch)
}

72
common/key/key_test.go Normal file
View File

@@ -0,0 +1,72 @@
package key
import (
"testing"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
func TestMatchesExchangeAsset(t *testing.T) {
t.Parallel()
cp := currency.NewPair(currency.BTC, currency.USD)
k := ExchangePairAsset{
Exchange: "test",
Base: cp.Base.Item,
Quote: cp.Quote.Item,
Asset: asset.Spot,
}
if !k.MatchesExchangeAsset("test", asset.Spot) {
t.Error("expected true")
}
if k.MatchesExchangeAsset("TEST", asset.Futures) {
t.Error("expected false")
}
if k.MatchesExchangeAsset("test", asset.Futures) {
t.Error("expected false")
}
if !k.MatchesExchangeAsset("TEST", asset.Spot) {
t.Error("expected true")
}
}
func TestMatchesPairAsset(t *testing.T) {
t.Parallel()
cp := currency.NewPair(currency.BTC, currency.USD)
k := ExchangePairAsset{
Base: cp.Base.Item,
Quote: cp.Quote.Item,
Asset: asset.Spot,
}
if !k.MatchesPairAsset(cp, asset.Spot) {
t.Error("expected true")
}
if k.MatchesPairAsset(cp, asset.Futures) {
t.Error("expected false")
}
if k.MatchesPairAsset(currency.EMPTYPAIR, asset.Futures) {
t.Error("expected false")
}
if k.MatchesPairAsset(currency.NewPair(currency.BTC, currency.USDT), asset.Spot) {
t.Error("expected false")
}
}
func TestMatchesExchange(t *testing.T) {
t.Parallel()
k := ExchangePairAsset{
Exchange: "test",
}
if !k.MatchesExchange("test") {
t.Error("expected true")
}
if !k.MatchesExchange("TEST") {
t.Error("expected true")
}
if k.MatchesExchange("tèst") {
t.Error("expected false")
}
if k.MatchesExchange("") {
t.Error("expected false")
}
}

View File

@@ -356,7 +356,7 @@ func TestSettingsPrint(t *testing.T) {
var unsupportedDefaultConfigExchanges = []string{
"itbit", // due to unsupported API
"poloniex", // outdated API // TODO remove once updated
"poloniex", // poloniex has dropped support for the API GCT has implemented //TODO: drop this when supported
}
func TestGetDefaultConfigurations(t *testing.T) {

View File

@@ -299,7 +299,7 @@ func TestCheckEventCondition(t *testing.T) {
}
m.m.Lock()
err = m.checkEventCondition(&m.events[0])
if err != nil && !strings.Contains(err.Error(), "no tickers for") {
if err != nil && !strings.Contains(err.Error(), "no tickers associated") {
t.Error(err)
} else if err == nil {
t.Error("expected error")

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -95,7 +96,6 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
return Holdings{}, fmt.Errorf("%s %s %w", exch, assetType, errExchangeHoldingsNotFound)
}
var accountsHoldings []SubAccount
subAccountHoldings, ok := accounts.SubAccounts[*creds]
if !ok {
return Holdings{}, fmt.Errorf("%s %s %s %w",
@@ -105,44 +105,39 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
errNoCredentialBalances)
}
for subAccount, assetHoldings := range subAccountHoldings {
for ai, currencyHoldings := range assetHoldings {
if ai != assetType {
continue
}
var currencyBalances = make([]Balance, len(currencyHoldings))
target := 0
for item, balance := range currencyHoldings {
balance.m.Lock()
currencyBalances[target] = Balance{
Currency: currency.Code{Item: item, UpperCase: true},
Total: balance.total,
Hold: balance.hold,
Free: balance.free,
AvailableWithoutBorrow: balance.availableWithoutBorrow,
Borrowed: balance.borrowed,
}
balance.m.Unlock()
target++
}
if len(currencyBalances) == 0 {
continue
}
cpy := *creds
if cpy.SubAccount == "" {
cpy.SubAccount = subAccount
}
accountsHoldings = append(accountsHoldings, SubAccount{
Credentials: Protected{creds: cpy},
ID: subAccount,
AssetType: ai,
Currencies: currencyBalances,
})
break
var currencyBalances = make([]Balance, 0, len(subAccountHoldings))
accountsHoldings := make([]SubAccount, 0, len(subAccountHoldings))
for mapKey, assetHoldings := range subAccountHoldings {
if mapKey.Asset != assetType {
continue
}
assetHoldings.m.Lock()
currencyBalances = append(currencyBalances, Balance{
Currency: currency.Code{Item: mapKey.Currency, UpperCase: true},
Total: assetHoldings.total,
Hold: assetHoldings.hold,
Free: assetHoldings.free,
AvailableWithoutBorrow: assetHoldings.availableWithoutBorrow,
Borrowed: assetHoldings.borrowed,
})
assetHoldings.m.Unlock()
if len(currencyBalances) == 0 {
continue
}
cpy := *creds
if cpy.SubAccount == "" {
cpy.SubAccount = mapKey.SubAccount
}
accountsHoldings = append(accountsHoldings, SubAccount{
Credentials: Protected{creds: cpy},
ID: mapKey.SubAccount,
AssetType: mapKey.Asset,
Currencies: currencyBalances,
})
break
}
if len(accountsHoldings) == 0 {
@@ -187,22 +182,14 @@ func GetBalance(exch, subAccount string, creds *Credentials, ai asset.Item, c cu
exch, creds, errNoCredentialBalances)
}
assetBalances, ok := subAccounts[subAccount]
if !ok {
return nil, fmt.Errorf("%s %s %w",
exch, subAccount, errNoExchangeSubAccountBalances)
}
currencyBalances, ok := assetBalances[ai]
if !ok {
return nil, fmt.Errorf("%s %s %s %w",
exch, subAccount, ai, errAssetHoldingsNotFound)
}
bal, ok := currencyBalances[c.Item]
bal, ok := subAccounts[key.SubAccountCurrencyAsset{
SubAccount: subAccount,
Currency: c.Item,
Asset: ai,
}]
if !ok {
return nil, fmt.Errorf("%s %s %s %s %w",
exch, subAccount, ai, c, errNoBalanceFound)
exch, subAccount, ai, c, errNoExchangeSubAccountBalances)
}
return bal, nil
}
@@ -232,7 +219,7 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error {
}
accounts = &Accounts{
ID: id,
SubAccounts: make(map[Credentials]map[string]map[asset.Item]map[*currency.Item]*ProtectedBalance),
SubAccounts: make(map[Credentials]map[key.SubAccountCurrencyAsset]*ProtectedBalance),
}
s.exchangeAccounts[exch] = accounts
}
@@ -257,34 +244,28 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error {
}
incoming.Accounts[x].Credentials.creds = cpy
var subAccounts map[string]map[asset.Item]map[*currency.Item]*ProtectedBalance
var subAccounts map[key.SubAccountCurrencyAsset]*ProtectedBalance
subAccounts, ok = accounts.SubAccounts[*creds]
if !ok {
subAccounts = make(map[string]map[asset.Item]map[*currency.Item]*ProtectedBalance)
subAccounts = make(map[key.SubAccountCurrencyAsset]*ProtectedBalance)
accounts.SubAccounts[*creds] = subAccounts
}
var accountAssets map[asset.Item]map[*currency.Item]*ProtectedBalance
accountAssets, ok = subAccounts[incoming.Accounts[x].ID]
if !ok {
accountAssets = make(map[asset.Item]map[*currency.Item]*ProtectedBalance)
for y := range incoming.Accounts[x].Currencies {
// Note: Sub accounts are case sensitive and an account "name" is
// different to account "naMe".
subAccounts[incoming.Accounts[x].ID] = accountAssets
}
var currencyBalances map[*currency.Item]*ProtectedBalance
currencyBalances, ok = accountAssets[incoming.Accounts[x].AssetType]
if !ok {
currencyBalances = make(map[*currency.Item]*ProtectedBalance)
accountAssets[incoming.Accounts[x].AssetType] = currencyBalances
}
for y := range incoming.Accounts[x].Currencies {
bal := currencyBalances[incoming.Accounts[x].Currencies[y].Currency.Item]
if bal == nil {
bal, ok := subAccounts[key.SubAccountCurrencyAsset{
SubAccount: incoming.Accounts[x].ID,
Currency: incoming.Accounts[x].Currencies[y].Currency.Item,
Asset: incoming.Accounts[x].AssetType,
}]
if !ok || bal == nil {
bal = &ProtectedBalance{}
currencyBalances[incoming.Accounts[x].Currencies[y].Currency.Item] = bal
subAccounts[key.SubAccountCurrencyAsset{
SubAccount: incoming.Accounts[x].ID,
Currency: incoming.Accounts[x].Currencies[y].Currency.Item,
Asset: incoming.Accounts[x].AssetType,
}] = bal
}
bal.load(incoming.Accounts[x].Currencies[y])
}

View File

@@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -292,13 +293,8 @@ func TestGetBalance(t *testing.T) {
}
_, err = GetBalance("bruh", "1337", happyCredentials, asset.Futures, currency.BTC)
if !errors.Is(err, errAssetHoldingsNotFound) {
t.Fatalf("received: '%v' but expected: '%v'", err, errAssetHoldingsNotFound)
}
_, err = GetBalance("bruh", "1337", happyCredentials, asset.Spot, currency.BTC)
if !errors.Is(err, errNoBalanceFound) {
t.Fatalf("received: '%v' but expected: '%v'", err, errNoBalanceFound)
if !errors.Is(err, errNoExchangeSubAccountBalances) {
t.Fatalf("received: '%v' but expected: '%v'", err, errNoExchangeSubAccountBalances)
}
err = Process(&Holdings{
@@ -473,7 +469,11 @@ func TestUpdate(t *testing.T) {
t.Fatal("account should be loaded")
}
b, ok := acc.SubAccounts[Credentials{Key: "AAAAA"}]["1337"][asset.Spot][currency.BTC.Item]
b, ok := acc.SubAccounts[Credentials{Key: "AAAAA"}][key.SubAccountCurrencyAsset{
SubAccount: "1337",
Currency: currency.BTC.Item,
Asset: asset.Spot,
}]
if !ok {
t.Fatal("account should be loaded")
}

View File

@@ -5,6 +5,7 @@ import (
"sync"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/alert"
@@ -32,7 +33,7 @@ type Accounts struct {
// TODO: Credential tracker to match to keys that are managed and return
// pointer.
// TODO: Have different cred struct for centralized verse DEFI exchanges.
SubAccounts map[Credentials]map[string]map[asset.Item]map[*currency.Item]*ProtectedBalance
SubAccounts map[Credentials]map[key.SubAccountCurrencyAsset]*ProtectedBalance
}
// Holdings is a generic type to hold each exchange's holdings for all enabled

View File

@@ -12,6 +12,8 @@ var (
ErrNotSupported = errors.New("unsupported asset type")
// ErrNotEnabled is an error for an asset not enabled
ErrNotEnabled = errors.New("asset type not enabled")
// ErrInvalidAsset is returned when the assist isn't valid
ErrInvalidAsset = errors.New("asset is invalid")
)
// Item stores the asset type

View File

@@ -619,7 +619,7 @@ func TestGetHistoricCandles(t *testing.T) {
if err != nil {
t.Fatal(err)
}
startTime := time.Now().AddDate(0, -2, 0)
startTime := time.Now().AddDate(0, -1, 0)
_, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, startTime, time.Now())
if err != nil {
t.Fatal(err)

View File

@@ -46,6 +46,9 @@ const (
)
var (
// ErrExchangeNameIsEmpty is returned when the exchange name is empty
ErrExchangeNameIsEmpty = errors.New("exchange name is empty")
errEndpointStringNotFound = errors.New("endpoint string not found")
errConfigPairFormatRequiresDelimiter = errors.New("config pair format requires delimiter")
errSymbolCannotBeMatched = errors.New("symbol cannot be matched")

View File

@@ -10,6 +10,7 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
@@ -20,7 +21,7 @@ import (
// to track futures orders
func SetupPositionController() PositionController {
return PositionController{
multiPositionTrackers: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker),
multiPositionTrackers: make(map[key.ExchangePairAsset]*MultiPositionTracker),
}
}
@@ -41,24 +42,14 @@ func (c *PositionController) TrackNewOrder(d *order.Detail) error {
}
c.m.Lock()
defer c.m.Unlock()
exchMap, ok := c.multiPositionTrackers[d.Exchange]
exchMap, ok := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: d.Exchange,
Base: d.Pair.Base.Item,
Quote: d.Pair.Quote.Item,
Asset: d.AssetType,
}]
if !ok {
exchMap = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[d.Exchange] = exchMap
}
itemMap, ok := exchMap[d.AssetType]
if !ok {
itemMap = make(map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
exchMap[d.AssetType] = itemMap
}
baseMap, ok := itemMap[d.Pair.Base.Item]
if !ok {
baseMap = make(map[*currency.Item]*MultiPositionTracker)
itemMap[d.Pair.Base.Item] = baseMap
}
quoteMap, ok := baseMap[d.Pair.Quote.Item]
if !ok {
quoteMap, err = SetupMultiPositionTracker(&MultiPositionTrackerSetup{
exchMap, err = SetupMultiPositionTracker(&MultiPositionTrackerSetup{
Exchange: d.Exchange,
Asset: d.AssetType,
Pair: d.Pair,
@@ -67,9 +58,14 @@ func (c *PositionController) TrackNewOrder(d *order.Detail) error {
if err != nil {
return err
}
baseMap[d.Pair.Quote.Item] = quoteMap
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: d.Exchange,
Base: d.Pair.Base.Item,
Quote: d.Pair.Quote.Item,
Asset: d.AssetType,
}] = exchMap
}
err = quoteMap.TrackNewOrder(d)
err = exchMap.TrackNewOrder(d)
if err != nil {
return err
}
@@ -91,7 +87,12 @@ func (c *PositionController) SetCollateralCurrency(exch string, item asset.Item,
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return fmt.Errorf("%w no open position for %v %v %v", ErrPositionNotFound, exch, item, pair)
}
@@ -119,7 +120,12 @@ func (c *PositionController) GetPositionsForExchange(exch string, item asset.Ite
}
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return nil, fmt.Errorf("%w no open position for %v %v %v", ErrPositionNotFound, exch, item, pair)
}
@@ -142,7 +148,12 @@ func (c *PositionController) TrackFundingDetails(d *fundingrate.Rates) error {
}
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[d.Exchange][d.Asset][d.Pair.Base.Item][d.Pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: d.Exchange,
Base: d.Pair.Base.Item,
Quote: d.Pair.Quote.Item,
Asset: d.Asset,
}]
if tracker == nil {
return fmt.Errorf("%w no open position for %v %v %v", ErrPositionNotFound, d.Exchange, d.Asset, d.Pair)
}
@@ -177,7 +188,12 @@ func (c *PositionController) GetOpenPosition(exch string, item asset.Item, pair
}
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return nil, fmt.Errorf("%w no open position for %v %v %v", ErrPositionNotFound, exch, item, pair)
}
@@ -200,19 +216,13 @@ func (c *PositionController) GetAllOpenPositions() ([]Position, error) {
c.m.Lock()
defer c.m.Unlock()
var openPositions []Position
for _, exchMap := range c.multiPositionTrackers {
for _, itemMap := range exchMap {
for _, baseMap := range itemMap {
for _, multiPositionTracker := range baseMap {
positions := multiPositionTracker.GetPositions()
for i := range positions {
if positions[i].Status.IsInactive() {
continue
}
openPositions = append(openPositions, positions[i])
}
}
for _, multiPositionTracker := range c.multiPositionTrackers {
positions := multiPositionTracker.GetPositions()
for i := range positions {
if positions[i].Status.IsInactive() {
continue
}
openPositions = append(openPositions, positions[i])
}
}
if len(openPositions) == 0 {
@@ -235,7 +245,12 @@ func (c *PositionController) UpdateOpenPositionUnrealisedPNL(exch string, item a
}
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return decimal.Zero, fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionNotFound)
}
@@ -327,7 +342,12 @@ func (c *PositionController) ClearPositionsForExchange(exch string, item asset.I
c.m.Lock()
defer c.m.Unlock()
tracker := c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item]
tracker := c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}]
if tracker == nil {
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionNotFound)
}
@@ -345,7 +365,12 @@ func (c *PositionController) ClearPositionsForExchange(exch string, item asset.I
if err != nil {
return err
}
c.multiPositionTrackers[exch][item][pair.Base.Item][pair.Quote.Item] = newMPT
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: exch,
Base: pair.Base.Item,
Quote: pair.Quote.Item,
Asset: item,
}] = newMPT
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
@@ -601,14 +602,23 @@ func TestGetPositionsForExchange(t *testing.T) {
if len(pos) != 0 {
t.Error("expected zero")
}
c.multiPositionTrackers = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange] = nil
c.multiPositionTrackers = make(map[key.ExchangePairAsset]*MultiPositionTracker)
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = nil
_, err = c.GetPositionsForExchange(testExchange, asset.Futures, p)
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
}
c.multiPositionTrackers[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures] = nil
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = nil
_, err = c.GetPositionsForExchange(testExchange, asset.Futures, p)
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
@@ -618,9 +628,12 @@ func TestGetPositionsForExchange(t *testing.T) {
t.Errorf("received '%v' expected '%v", err, ErrNotFuturesAsset)
}
c.multiPositionTrackers[testExchange][asset.Futures] = make(map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item] = make(map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = &MultiPositionTracker{
exchange: testExchange,
}
@@ -631,7 +644,12 @@ func TestGetPositionsForExchange(t *testing.T) {
if len(pos) != 0 {
t.Fatal("expected zero")
}
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = &MultiPositionTracker{
exchange: testExchange,
positions: []*PositionTracker{
{
@@ -669,29 +687,23 @@ func TestClearPositionsForExchange(t *testing.T) {
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
}
c.multiPositionTrackers = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange] = nil
err = c.ClearPositionsForExchange(testExchange, asset.Futures, p)
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
}
c.multiPositionTrackers[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures] = nil
c.multiPositionTrackers = make(map[key.ExchangePairAsset]*MultiPositionTracker)
err = c.ClearPositionsForExchange(testExchange, asset.Futures, p)
if !errors.Is(err, ErrPositionNotFound) {
t.Errorf("received '%v' expected '%v", err, ErrPositionNotFound)
}
err = c.ClearPositionsForExchange(testExchange, asset.Spot, p)
if !errors.Is(err, ErrNotFuturesAsset) {
t.Errorf("received '%v' expected '%v", err, ErrNotFuturesAsset)
}
c.multiPositionTrackers[testExchange][asset.Futures] = make(map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item] = make(map[*currency.Item]*MultiPositionTracker)
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
exchange: testExchange,
}
c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}] = &MultiPositionTracker{
exchange: testExchange,
underlying: currency.DOGE,
positions: []*PositionTracker{
@@ -704,7 +716,12 @@ func TestClearPositionsForExchange(t *testing.T) {
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v", err, nil)
}
if len(c.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item].positions) != 0 {
if len(c.multiPositionTrackers[key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}].positions) != 0 {
t.Fatal("expected 0")
}
c = nil
@@ -940,46 +957,36 @@ func TestUpdateOpenPositionUnrealisedPNL(t *testing.T) {
func TestSetCollateralCurrency(t *testing.T) {
t.Parallel()
var expectedError = errExchangeNameEmpty
pc := SetupPositionController()
err := pc.SetCollateralCurrency("", asset.Spot, currency.EMPTYPAIR, currency.Code{})
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, errExchangeNameEmpty) {
t.Errorf("received '%v' expected '%v", err, errExchangeNameEmpty)
}
expectedError = ErrNotFuturesAsset
err = pc.SetCollateralCurrency("hi", asset.Spot, currency.EMPTYPAIR, currency.Code{})
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, ErrNotFuturesAsset) {
t.Errorf("received '%v' expected '%v", err, ErrNotFuturesAsset)
}
p := currency.NewPair(currency.BTC, currency.USDT)
pc.multiPositionTrackers = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
pc.multiPositionTrackers = make(map[key.ExchangePairAsset]*MultiPositionTracker)
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
expectedError = ErrPositionNotFound
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
}
pc.multiPositionTrackers["hi"] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, ErrPositionNotFound) {
t.Fatalf("received '%v' expected '%v", err, ErrPositionNotFound)
}
pc.multiPositionTrackers["hi"][asset.Futures] = make(map[*currency.Item]map[*currency.Item]*MultiPositionTracker)
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, ErrPositionNotFound) {
t.Fatalf("received '%v' expected '%v", err, ErrPositionNotFound)
}
expectedError = ErrPositionNotFound
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item] = make(map[*currency.Item]*MultiPositionTracker)
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item] = nil
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
mapKey := key.ExchangePairAsset{
Exchange: "hi",
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item] = &MultiPositionTracker{
pc.multiPositionTrackers[mapKey] = &MultiPositionTracker{
exchange: "hi",
asset: asset.Futures,
pair: p,
@@ -1000,34 +1007,30 @@ func TestSetCollateralCurrency(t *testing.T) {
}
err = pc.SetCollateralCurrency("hi", asset.Futures, p, currency.DOGE)
expectedError = nil
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v", err, nil)
}
if !pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].collateralCurrency.Equal(currency.DOGE) {
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].collateralCurrency, currency.DOGE)
if !pc.multiPositionTrackers[mapKey].collateralCurrency.Equal(currency.DOGE) {
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers[mapKey].collateralCurrency, currency.DOGE)
}
if !pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].positions[0].collateralCurrency.Equal(currency.DOGE) {
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].positions[0].collateralCurrency, currency.DOGE)
if !pc.multiPositionTrackers[mapKey].positions[0].collateralCurrency.Equal(currency.DOGE) {
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers[mapKey].positions[0].collateralCurrency, currency.DOGE)
}
var nilPC *PositionController
err = nilPC.SetCollateralCurrency("hi", asset.Spot, currency.EMPTYPAIR, currency.Code{})
expectedError = common.ErrNilPointer
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, common.ErrNilPointer) {
t.Errorf("received '%v' expected '%v", err, common.ErrNilPointer)
}
}
func TestMPTUpdateOpenPositionUnrealisedPNL(t *testing.T) {
t.Parallel()
var err, expectedError error
expectedError = nil
p := currency.NewPair(currency.BTC, currency.USDT)
pc := SetupPositionController()
err = pc.TrackNewOrder(&order.Detail{
err := pc.TrackNewOrder(&order.Detail{
Date: time.Now(),
Exchange: "hi",
Pair: p,
@@ -1037,30 +1040,35 @@ func TestMPTUpdateOpenPositionUnrealisedPNL(t *testing.T) {
Price: 1,
Amount: 1,
})
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v", err, nil)
}
result, err := pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
mapKey := key.ExchangePairAsset{
Exchange: "hi",
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}
result, err := pc.multiPositionTrackers[mapKey].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v", err, nil)
}
if result.Equal(decimal.NewFromInt(1337)) {
t.Error("")
}
expectedError = ErrPositionClosed
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].positions[0].status = order.Closed
_, err = pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
pc.multiPositionTrackers[mapKey].positions[0].status = order.Closed
_, err = pc.multiPositionTrackers[mapKey].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, ErrPositionClosed) {
t.Fatalf("received '%v' expected '%v", err, ErrPositionClosed)
}
expectedError = ErrPositionNotFound
pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].positions = nil
_, err = pc.multiPositionTrackers["hi"][asset.Futures][p.Base.Item][p.Quote.Item].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, expectedError) {
t.Fatalf("received '%v' expected '%v", err, expectedError)
pc.multiPositionTrackers[mapKey].positions = nil
_, err = pc.multiPositionTrackers[mapKey].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
if !errors.Is(err, ErrPositionNotFound) {
t.Fatalf("received '%v' expected '%v", err, ErrPositionNotFound)
}
}
@@ -1305,8 +1313,16 @@ func TestPCTrackFundingDetails(t *testing.T) {
Payment: decimal.NewFromInt(1337),
},
}
pc.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item].orderPositions["lol"].openingDate = tn.Add(-time.Hour)
pc.multiPositionTrackers[testExchange][asset.Futures][p.Base.Item][p.Quote.Item].orderPositions["lol"].lastUpdated = tn
mapKey := key.ExchangePairAsset{
Exchange: testExchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: asset.Futures,
}
pc.multiPositionTrackers[mapKey].orderPositions["lol"].openingDate = tn.Add(-time.Hour)
pc.multiPositionTrackers[mapKey].orderPositions["lol"].lastUpdated = tn
err = pc.TrackFundingDetails(rates)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v", err, nil)

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/collateral"
@@ -85,7 +86,7 @@ type TotalCollateralResponse struct {
// the position controller and its all tracked happily
type PositionController struct {
m sync.Mutex
multiPositionTrackers map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*MultiPositionTracker
multiPositionTrackers map[key.ExchangePairAsset]*MultiPositionTracker
updated time.Time
}

View File

@@ -2009,7 +2009,7 @@ func TestGetQuoteAmountFromNominalSlippage(t *testing.T) {
t.Fatalf("%s received: '%v' but expected: '%v'", tt.Name, err, tt.ExpectedError)
}
if !quote.IsEqual(tt.ExpectedShift) {
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
t.Fatalf("%s quote received: \n'%+v' \nbut expected: \n'%+v'",
tt.Name, quote, tt.ExpectedShift)
}
})

View File

@@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -44,6 +45,12 @@ func SubscribeToExchangeOrderbooks(exchange string) (dispatch.Pipe, error) {
// Update stores orderbook data
func (s *Service) Update(b *Base) error {
name := strings.ToLower(b.Exchange)
mapKey := key.PairAsset{
Base: b.Pair.Base.Item,
Quote: b.Pair.Quote.Item,
Asset: b.Asset,
}
s.mu.Lock()
m1, ok := s.books[name]
if !ok {
@@ -53,29 +60,16 @@ func (s *Service) Update(b *Base) error {
return err
}
m1 = Exchange{
m: make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Depth),
m: make(map[key.PairAsset]*Depth),
ID: id,
}
s.books[name] = m1
}
m2, ok := m1.m[b.Asset]
if !ok {
m2 = make(map[*currency.Item]map[*currency.Item]*Depth)
m1.m[b.Asset] = m2
}
m3, ok := m2[b.Pair.Base.Item]
if !ok {
m3 = make(map[*currency.Item]*Depth)
m2[b.Pair.Base.Item] = m3
}
book, ok := m3[b.Pair.Quote.Item]
book, ok := m1.m[mapKey]
if !ok {
book = NewDepth(m1.ID)
book.AssignOptions(b)
m3[b.Pair.Quote.Item] = book
m1.m[mapKey] = book
}
err := book.LoadSnapshot(b.Bids, b.Asks, b.LastUpdateID, b.LastUpdated, true)
s.mu.Unlock()
@@ -97,6 +91,12 @@ func (s *Service) DeployDepth(exchange string, p currency.Pair, a asset.Item) (*
if !a.IsValid() {
return nil, errAssetTypeNotSet
}
mapKey := key.PairAsset{
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}
s.mu.Lock()
defer s.mu.Unlock()
m1, ok := s.books[strings.ToLower(exchange)]
@@ -106,28 +106,18 @@ func (s *Service) DeployDepth(exchange string, p currency.Pair, a asset.Item) (*
return nil, err
}
m1 = Exchange{
m: make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Depth),
m: make(map[key.PairAsset]*Depth),
ID: id,
}
s.books[strings.ToLower(exchange)] = m1
}
m2, ok := m1.m[a]
if !ok {
m2 = make(map[*currency.Item]map[*currency.Item]*Depth)
m1.m[a] = m2
}
m3, ok := m2[p.Base.Item]
if !ok {
m3 = make(map[*currency.Item]*Depth)
m2[p.Base.Item] = m3
}
book, ok := m3[p.Quote.Item]
book, ok := m1.m[mapKey]
if !ok {
book = NewDepth(m1.ID)
book.exchange = exchange
book.pair = p
book.asset = a
m3[p.Quote.Item] = book
m1.m[mapKey] = book
}
return book, nil
}
@@ -143,21 +133,11 @@ func (s *Service) GetDepth(exchange string, p currency.Pair, a asset.Item) (*Dep
errCannotFindOrderbook, exchange)
}
m2, ok := m1.m[a]
if !ok {
return nil, fmt.Errorf("%w associated with asset type %s",
errCannotFindOrderbook,
a)
}
m3, ok := m2[p.Base.Item]
if !ok {
return nil, fmt.Errorf("%w associated with base currency %s",
errCannotFindOrderbook,
p.Base)
}
book, ok := m3[p.Quote.Item]
book, ok := m1.m[key.PairAsset{
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("%w associated with base currency %s",
errCannotFindOrderbook,
@@ -175,6 +155,7 @@ func (s *Service) Retrieve(exchange string, p currency.Pair, a asset.Item) (*Bas
if !a.IsValid() {
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
s.mu.Lock()
defer s.mu.Unlock()
m1, ok := s.books[strings.ToLower(exchange)]
@@ -183,19 +164,11 @@ func (s *Service) Retrieve(exchange string, p currency.Pair, a asset.Item) (*Bas
errCannotFindOrderbook,
exchange)
}
m2, ok := m1.m[a]
if !ok {
return nil, fmt.Errorf("%w associated with asset type %s",
errCannotFindOrderbook,
a)
}
m3, ok := m2[p.Base.Item]
if !ok {
return nil, fmt.Errorf("%w associated with base currency %s",
errCannotFindOrderbook,
p.Base)
}
book, ok := m3[p.Quote.Item]
book, ok := m1.m[key.PairAsset{
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("%w associated with base currency %s",
errCannotFindOrderbook,

View File

@@ -6,6 +6,7 @@ import (
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -49,7 +50,7 @@ type Service struct {
// Exchange defines a holder for the exchange specific depth items with a
// specific ID associated with that exchange
type Exchange struct {
m map[asset.Item]map[*currency.Item]map[*currency.Item]*Depth
m map[key.PairAsset]*Depth
ID uuid.UUID
}

View File

@@ -6,6 +6,7 @@ import (
"sort"
"time"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -60,7 +61,7 @@ func (w *Orderbook) Setup(exchangeConfig *config.Exchange, c *Config, dataHandle
w.updateEntriesByID = c.UpdateEntriesByID
w.exchangeName = exchangeConfig.Name
w.dataHandler = dataHandler
w.ob = make(map[Key]*orderbookHolder)
w.ob = make(map[key.PairAsset]*orderbookHolder)
w.verbose = exchangeConfig.Verbose
// set default publish period if missing
@@ -93,7 +94,7 @@ func (w *Orderbook) Update(u *orderbook.Update) error {
}
w.mtx.Lock()
defer w.mtx.Unlock()
book, ok := w.ob[Key{Base: u.Pair.Base.Item, Quote: u.Pair.Quote.Item, Asset: u.Asset}]
book, ok := w.ob[key.PairAsset{Base: u.Pair.Base.Item, Quote: u.Pair.Quote.Item, Asset: u.Asset}]
if !ok {
return fmt.Errorf("%w for Exchange %s CurrencyPair: %s AssetType: %s",
errDepthNotFound,
@@ -311,7 +312,7 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error {
w.mtx.Lock()
defer w.mtx.Unlock()
holder, ok := w.ob[Key{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}]
holder, ok := w.ob[key.PairAsset{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}]
if !ok {
// Associate orderbook pointer with local exchange depth map
var depth *orderbook.Depth
@@ -327,7 +328,7 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error {
ticker = time.NewTicker(w.publishPeriod)
}
holder = &orderbookHolder{ob: depth, buffer: &buffer, ticker: ticker}
w.ob[Key{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}] = holder
w.ob[key.PairAsset{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}] = holder
}
holder.updateID = book.LastUpdateID
@@ -364,7 +365,7 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error {
func (w *Orderbook) GetOrderbook(p currency.Pair, a asset.Item) (*orderbook.Base, error) {
w.mtx.Lock()
defer w.mtx.Unlock()
book, ok := w.ob[Key{Base: p.Base.Item, Quote: p.Quote.Item, Asset: a}]
book, ok := w.ob[key.PairAsset{Base: p.Base.Item, Quote: p.Quote.Item, Asset: a}]
if !ok {
return nil, fmt.Errorf("%s %s %s %w", w.exchangeName, p, a, errDepthNotFound)
}
@@ -375,7 +376,7 @@ func (w *Orderbook) GetOrderbook(p currency.Pair, a asset.Item) (*orderbook.Base
// connection is lost and reconnected
func (w *Orderbook) FlushBuffer() {
w.mtx.Lock()
w.ob = make(map[Key]*orderbookHolder)
w.ob = make(map[key.PairAsset]*orderbookHolder)
w.mtx.Unlock()
}
@@ -383,7 +384,7 @@ func (w *Orderbook) FlushBuffer() {
func (w *Orderbook) FlushOrderbook(p currency.Pair, a asset.Item) error {
w.mtx.Lock()
defer w.mtx.Unlock()
book, ok := w.ob[Key{Base: p.Base.Item, Quote: p.Quote.Item, Asset: a}]
book, ok := w.ob[key.PairAsset{Base: p.Base.Item, Quote: p.Quote.Item, Asset: a}]
if !ok {
return fmt.Errorf("cannot flush orderbook %s %s %s %w",
w.exchangeName,

View File

@@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -44,7 +45,7 @@ func createSnapshot() (holder *Orderbook, asks, bids orderbook.Items, err error)
LastUpdated: time.Now(),
}
newBook := make(map[Key]*orderbookHolder)
newBook := make(map[key.PairAsset]*orderbookHolder)
ch := make(chan interface{})
go func(<-chan interface{}) { // reader
@@ -93,7 +94,7 @@ func BenchmarkUpdateBidsByPrice(b *testing.B) {
UpdateTime: time.Now(),
Asset: asset.Spot,
}
holder := ob.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
holder := ob.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
err = holder.updateByPrice(update)
if err != nil {
b.Fatal(err)
@@ -116,7 +117,7 @@ func BenchmarkUpdateAsksByPrice(b *testing.B) {
UpdateTime: time.Now(),
Asset: asset.Spot,
}
holder := ob.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
holder := ob.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
err = holder.updateByPrice(update)
if err != nil {
b.Fatal(err)
@@ -246,7 +247,7 @@ func TestUpdates(t *testing.T) {
t.Error(err)
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
err = book.updateByPrice(&orderbook.Update{
Bids: itemArray[5],
Asks: itemArray[5],
@@ -302,7 +303,7 @@ func TestHittingTheBuffer(t *testing.T) {
}
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
askLen, err := book.ob.GetAskLength()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -350,7 +351,7 @@ func TestInsertWithIDs(t *testing.T) {
}
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
askLen, err := book.ob.GetAskLength()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -393,7 +394,7 @@ func TestSortIDs(t *testing.T) {
t.Fatal(err)
}
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
askLen, err := book.ob.GetAskLength()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -438,7 +439,7 @@ func TestOutOfOrderIDs(t *testing.T) {
t.Fatal(err)
}
}
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
cpy, err := book.ob.Retrieve()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -570,7 +571,7 @@ func TestRunUpdateWithoutAnyUpdates(t *testing.T) {
func TestRunSnapshotWithNoData(t *testing.T) {
t.Parallel()
var obl Orderbook
obl.ob = make(map[Key]*orderbookHolder)
obl.ob = make(map[key.PairAsset]*orderbookHolder)
obl.dataHandler = make(chan interface{}, 1)
var snapShot1 orderbook.Base
snapShot1.Asset = asset.Spot
@@ -589,7 +590,7 @@ func TestLoadSnapshot(t *testing.T) {
t.Parallel()
var obl Orderbook
obl.dataHandler = make(chan interface{}, 100)
obl.ob = make(map[Key]*orderbookHolder)
obl.ob = make(map[key.PairAsset]*orderbookHolder)
var snapShot1 orderbook.Base
snapShot1.Exchange = "SnapshotWithOverride"
asks := []orderbook.Item{
@@ -615,11 +616,11 @@ func TestFlushBuffer(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if obl.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] == nil {
if obl.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] == nil {
t.Error("expected ob to have ask entries")
}
obl.FlushBuffer()
if obl.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] != nil {
if obl.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] != nil {
t.Error("expected ob be flushed")
}
}
@@ -629,7 +630,7 @@ func TestInsertingSnapShots(t *testing.T) {
t.Parallel()
var holder Orderbook
holder.dataHandler = make(chan interface{}, 100)
holder.ob = make(map[Key]*orderbookHolder)
holder.ob = make(map[key.PairAsset]*orderbookHolder)
var snapShot1 orderbook.Base
snapShot1.Exchange = "WSORDERBOOKTEST1"
asks := []orderbook.Item{
@@ -795,7 +796,7 @@ func TestGetOrderbook(t *testing.T) {
if err != nil {
t.Fatal(err)
}
bufferOb := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
bufferOb := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
b, err := bufferOb.ob.Retrieve()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
@@ -888,7 +889,7 @@ func TestEnsureMultipleUpdatesViaPrice(t *testing.T) {
}
asks := bidAskGenerator()
book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
book := holder.ob[key.PairAsset{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}]
err = book.updateByPrice(&orderbook.Update{
Bids: asks,
Asks: asks,

View File

@@ -4,8 +4,7 @@ import (
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
)
@@ -30,7 +29,7 @@ type Config struct {
// Orderbook defines a local cache of orderbooks for amending, appending
// and deleting changes and updates the main store for a stream
type Orderbook struct {
ob map[Key]*orderbookHolder
ob map[key.PairAsset]*orderbookHolder
obBufferLimit int
bufferEnabled bool
sortBuffer bool
@@ -67,10 +66,3 @@ type orderbookHolder struct {
ticker *time.Ticker
updateID int64
}
// Key defines a unique orderbook key for a specific pair and asset
type Key struct {
Base *currency.Item
Quote *currency.Item
Asset asset.Item
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -22,7 +23,7 @@ var (
func init() {
service = new(Service)
service.Tickers = make(map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker)
service.Tickers = make(map[key.ExchangePairAsset]*Ticker)
service.Exchange = make(map[string]uuid.UUID)
service.mux = dispatch.GetNewMux(nil)
}
@@ -33,8 +34,12 @@ func SubscribeTicker(exchange string, p currency.Pair, a asset.Item) (dispatch.P
exchange = strings.ToLower(exchange)
service.mu.Lock()
defer service.mu.Unlock()
tick, ok := service.Tickers[exchange][p.Base.Item][p.Quote.Item][a]
tick, ok := service.Tickers[key.ExchangePairAsset{
Exchange: exchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return dispatch.Pipe{}, fmt.Errorf("ticker item not found for %s %s %s",
exchange,
@@ -72,30 +77,18 @@ func GetTicker(exchange string, p currency.Pair, a asset.Item) (*Price, error) {
exchange = strings.ToLower(exchange)
service.mu.Lock()
defer service.mu.Unlock()
m1, ok := service.Tickers[exchange]
tick, ok := service.Tickers[key.ExchangePairAsset{
Exchange: exchange,
Base: p.Base.Item,
Quote: p.Quote.Item,
Asset: a,
}]
if !ok {
return nil, fmt.Errorf("no tickers for %s exchange", exchange)
return nil, fmt.Errorf("no tickers associated with asset type %s %s %s",
exchange, p, a)
}
m2, ok := m1[p.Base.Item]
if !ok {
return nil, fmt.Errorf("no tickers associated with base currency %s",
p.Base)
}
m3, ok := m2[p.Quote.Item]
if !ok {
return nil, fmt.Errorf("no tickers associated with quote currency %s",
p.Quote)
}
t, ok := m3[a]
if !ok {
return nil, fmt.Errorf("no tickers associated with asset type %s",
a)
}
cpy := t.Price // Don't let external functions have access to underlying
cpy := tick.Price // Don't let external functions have access to underlying
return &cpy, nil
}
@@ -103,20 +96,10 @@ func GetTicker(exchange string, p currency.Pair, a asset.Item) (*Price, error) {
func FindLast(p currency.Pair, a asset.Item) (float64, error) {
service.mu.Lock()
defer service.mu.Unlock()
for _, m1 := range service.Tickers {
m2, ok := m1[p.Base.Item]
if !ok {
for mapKey, t := range service.Tickers {
if !mapKey.MatchesPairAsset(p, a) {
continue
}
m3, ok := m2[p.Quote.Item]
if !ok {
continue
}
t, ok := m3[a]
if !ok {
continue
}
if t.Last == 0 {
return 0, errInvalidTicker
}
@@ -178,27 +161,14 @@ func ProcessTicker(p *Price) error {
// update updates ticker price
func (s *Service) update(p *Price) error {
name := strings.ToLower(p.ExchangeName)
mapKey := key.ExchangePairAsset{
Exchange: name,
Base: p.Pair.Base.Item,
Quote: p.Pair.Quote.Item,
Asset: p.AssetType,
}
s.mu.Lock()
m1, ok := service.Tickers[name]
if !ok {
m1 = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker)
service.Tickers[name] = m1
}
m2, ok := m1[p.Pair.Base.Item]
if !ok {
m2 = make(map[*currency.Item]map[asset.Item]*Ticker)
m1[p.Pair.Base.Item] = m2
}
m3, ok := m2[p.Pair.Quote.Item]
if !ok {
m3 = make(map[asset.Item]*Ticker)
m2[p.Pair.Quote.Item] = m3
}
t, ok := m3[p.AssetType]
t, ok := service.Tickers[mapKey]
if !ok || t == nil {
newTicker := &Ticker{}
err := s.setItemID(newTicker, p, name)
@@ -206,7 +176,7 @@ func (s *Service) update(p *Price) error {
s.mu.Unlock()
return err
}
m3[p.AssetType] = newTicker
service.Tickers[mapKey] = newTicker
s.mu.Unlock()
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -25,7 +26,7 @@ var (
// Service holds ticker information for each individual exchange
type Service struct {
Tickers map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker
Tickers map[key.ExchangePairAsset]*Ticker
Exchange map[string]uuid.UUID
mux *dispatch.Mux
mu sync.Mutex