mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, "", " ")
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
47
common/key/key.go
Normal 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
72
common/key/key_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user