mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-31 07:26:44 +00:00
backtester: Futures handling & FTX Cash and Carry example strategy (#930)
* implements futures functions and GRPC functions on new branch * lint and test fixes * Fix uneven split pnl. Adds collateral weight test. docs. New clear func * Test protection if someone has zero collateral * Uses string instead of double for accuracy * Fixes old code panic * context, match, docs * Addresses Shazniterinos, var names, expanded tests * Returns subaccount name, provides USD values when offlinecalc * Fixes oopsie * Fixes cool bug which allowed made up subaccount results * Subaccount override on FTX, subaccount results for collateral * Strenghten collateral account info checks. Improve FTX test * English is my first language * Fixes oopsies * Adds some conceptual futures order details to track PNL * Initial design of future order processing in the backtester * Introduces futures concept for collateral and spot/futures config diffs * Fixes most tests * Simple designs for collateral funding pair concept * Expands interface use so much it hurts * Implements more collateral interfaces * Adds liquidation, adds strategy, struggles with Binance * Attempts at getting FTX to work * Adds calculatePNL as a wrapper function and adds an `IsFutures` asset check * Successfully loads backtester with collateral currency * Fails to really get much going for supporting futures * Merges master changes * Fleshes out how FTX processes collateral * Further FTX collateral workings * hooks up more ftx collateral and pnl calculations * more funcs to flesh out handling * Adds more links, just can't fit the pieces together :( * Greatly expands futures order processing * Fleshes out position tracker to also handle asset and exchange +testing * RM linkedOrderID. rn positioncontroller, unexport * Successfully tracks futures order positions * Fails to calculate PNL * Calculates pnl from orders accurately with exception to flipping orders * Calculates PNL from orders * Adds another controller layer to make it ez from orderstore * Backtester now compiles. Adds test coverage * labels things add scaling collateral test * Calculates pnl in line with fees * Mostly accurate PNL, with exception to appending with diff prices * Adds locks, adds rpc function * grpc implementations * Gracefully handles rpc function * beautiful tests! * rejiggles tests to polish * Finishes FTX testing, adds comments * Exposes collateral calculations to rpc * Adds commands and testing for rpcserver.go functions * Increase testing and fix up backtester code * Returns cool changes to original branch * end of day fixes * Fixing some tests * Fixing tests 🎉 * Fixes all the tests * Splits the backtester setup and running into different files * Merge, minor fixes * Messing with some strategy updates * Failed understanding at collateral usage * Begins the creation of cash and carry strategy * Adds underlying pair, adds filldependentevent for futures * Completes fill prerequsite event implementation. Can't short though * Some bug fixes * investigating funds * CAN NOW CREATE A SHORT ORDER * Minor change in short size * Fixes for unrealised PNL & collateral rendering * Fixes lint and tests * Adds some verbosity * Updates to pnl calc * Tracks pnl for short orders, minor update to strategy * Close and open event based on conditions * Adds pnl data for currency statistics * Working through PNL calculation automatically. Now panics * Adds tracking, is blocked from design * Work to flesh out closing a position * vain attempts at tracking zeroing out bugs * woww, super fun new subloggers 🎉 * Begins attempt at automatically handling contracts and collateral based on direction * Merge master + fixes * Investigating issues with pnl and holdings * Minor pnl fixes * Fixes future position sizing, needs contract sizing * Can render pnl results, focussing on funding statistics * tracking candles for futures, but why not btc * Improves funding statistics * Colours and stats * Fixes collateral and snapshot bugs * Completes test * Fixes totals bug * Fix double buy, expand stats, fixes usd totals, introduce interface * Begins report formatting and calculations * Appends pnl to receiving curr. Fixes map[time]. accurate USD * Improves report output rendering * PNL stats in report. New tests for futures * Fixes existing tests before adding new coverage * Test coverage * Completes portfolio coverage * Increase coverage exchange, portfolio. fix size bug. NEW CHART * WHAT IS GOING ON WITH PNL * Fixes PNL calculation. Adds ability to skip om futures tracking * minor commit before merge * Adds basic liquidation to backtester * Changes liquidation to order based * Liquidationnnnnn * Further fleshes out liquidations * Completes liquidations in a honorable manner. Adds AppendReasonf * Beginnings of spot futures gap chart. Needs to link currencies to render difference * Removes fake liquidation. Adds cool new chart * Fixes somet tests,allows for zero fee value v nil distinction,New tests * Some annoying test fixes that took too long * portfolio coverage * holding coverage, privatisation funding * Testwork * boring tests * engine coverage * More backtesting coverage * Funding, strategy, report test coverage * Completes coverage of report package * Documentation, fixes some assumptions on asset errors * Changes before master merge * Lint and Tests * defaults to non-coloured rendering * Chart rendering * Fixes surprise non-local-lints * Niterinos to the extremeos * Fixes merge problems * The linter splintered across the glinting plinths * Many nits addressed. Now sells spot position on final candle * Adds forgotten coverage * Adds ability to size futures contracts to match spot positions. * fixes order sell sizing * Adds tests to sizing. Fixes charting issue * clint splintered the linters with flint * Improves stats, stat rendering * minifix * Fixes tests and fee bug * Merge fixeroos * Microfixes * Updates orderPNL on first Correctly utilises fees. Adds committed funds * New base funcs. New order summary * Fun test updates * Fix logo colouring * Fixes niteroonies * Fix report * BAD COMMIT * Fixes funding issues.Updates default fee rates.Combines cashcarry case * doc regen * Now returns err * Fixes sizing bug issue introduced in PR * Fixes fun fee/total US value bug * Fix chart bug. Show log charts with disclaimer * sellside fee * fixes fee and slippage view * Fixed slippage price issue * Fixes calculation and removes rendering * Fixes stats and some rendering * Merge fix * Fixes merge issues * go mod tidy, lint updates * New linter attempt * Version bump in appveyor and makefile * Regex filename, config fixes, template h2 fixes * Removes bad stats. * neatens config builder. Moves filename generator * Fixes issue where linter wants to fix my spelling * Fixes pointers and starts
This commit is contained in:
@@ -18,7 +18,7 @@ import (
|
||||
// to track futures orders
|
||||
func SetupPositionController() *PositionController {
|
||||
return &PositionController{
|
||||
positionTrackerControllers: make(map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker),
|
||||
multiPositionTrackers: make(map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ func (c *PositionController) TrackNewOrder(d *Detail) error {
|
||||
}
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
exchM, ok := c.positionTrackerControllers[strings.ToLower(d.Exchange)]
|
||||
exchM, ok := c.multiPositionTrackers[strings.ToLower(d.Exchange)]
|
||||
if !ok {
|
||||
exchM = make(map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.positionTrackerControllers[strings.ToLower(d.Exchange)] = exchM
|
||||
c.multiPositionTrackers[strings.ToLower(d.Exchange)] = exchM
|
||||
}
|
||||
itemM, ok := exchM[d.AssetType]
|
||||
if !ok {
|
||||
@@ -65,6 +65,44 @@ func (c *PositionController) TrackNewOrder(d *Detail) error {
|
||||
return multiPositionTracker.TrackNewOrder(d)
|
||||
}
|
||||
|
||||
// SetCollateralCurrency allows the setting of a collateral currency to all child trackers
|
||||
// when using position controller for futures orders tracking
|
||||
func (c *PositionController) SetCollateralCurrency(exch string, item asset.Item, pair currency.Pair, collateralCurrency currency.Code) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("position controller %w", common.ErrNilPointer)
|
||||
}
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
if !item.IsFutures() {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrNotFuturesAsset)
|
||||
}
|
||||
|
||||
exchM, ok := c.multiPositionTrackers[strings.ToLower(exch)]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set collateral %v for %v %v %v %w", collateralCurrency, exch, item, pair, ErrPositionsNotLoadedForExchange)
|
||||
}
|
||||
itemM, ok := exchM[item]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set collateral %v for %v %v %v %w", collateralCurrency, exch, item, pair, ErrPositionsNotLoadedForAsset)
|
||||
}
|
||||
multiPositionTracker, ok := itemM[pair]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set collateral %v for %v %v %v %w", collateralCurrency, exch, item, pair, ErrPositionsNotLoadedForPair)
|
||||
}
|
||||
if multiPositionTracker == nil {
|
||||
return fmt.Errorf("cannot set collateral %v for %v %v %v %w", collateralCurrency, exch, item, pair, common.ErrNilPointer)
|
||||
}
|
||||
multiPositionTracker.m.Lock()
|
||||
multiPositionTracker.collateralCurrency = collateralCurrency
|
||||
for i := range multiPositionTracker.positions {
|
||||
multiPositionTracker.positions[i].m.Lock()
|
||||
multiPositionTracker.positions[i].collateralCurrency = collateralCurrency
|
||||
multiPositionTracker.positions[i].m.Unlock()
|
||||
}
|
||||
multiPositionTracker.m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPositionsForExchange returns all positions for an
|
||||
// exchange, asset pair that is stored in the position controller
|
||||
func (c *PositionController) GetPositionsForExchange(exch string, item asset.Item, pair currency.Pair) ([]PositionStats, error) {
|
||||
@@ -76,7 +114,7 @@ func (c *PositionController) GetPositionsForExchange(exch string, item asset.Ite
|
||||
if !item.IsFutures() {
|
||||
return nil, fmt.Errorf("%v %v %v %w", exch, item, pair, ErrNotFuturesAsset)
|
||||
}
|
||||
exchM, ok := c.positionTrackerControllers[strings.ToLower(exch)]
|
||||
exchM, ok := c.multiPositionTrackers[strings.ToLower(exch)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionsNotLoadedForExchange)
|
||||
}
|
||||
@@ -105,7 +143,7 @@ func (c *PositionController) UpdateOpenPositionUnrealisedPNL(exch string, item a
|
||||
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
exchM, ok := c.positionTrackerControllers[strings.ToLower(exch)]
|
||||
exchM, ok := c.multiPositionTrackers[strings.ToLower(exch)]
|
||||
if !ok {
|
||||
return decimal.Zero, fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionsNotLoadedForExchange)
|
||||
}
|
||||
@@ -137,45 +175,6 @@ func (c *PositionController) UpdateOpenPositionUnrealisedPNL(exch string, item a
|
||||
return latestPos.unrealisedPNL, nil
|
||||
}
|
||||
|
||||
// ClearPositionsForExchange resets positions for an
|
||||
// exchange, asset, pair that has been stored
|
||||
func (c *PositionController) ClearPositionsForExchange(exch string, item asset.Item, pair currency.Pair) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("position controller %w", common.ErrNilPointer)
|
||||
}
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
if !item.IsFutures() {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrNotFuturesAsset)
|
||||
}
|
||||
exchM, ok := c.positionTrackerControllers[strings.ToLower(exch)]
|
||||
if !ok {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionsNotLoadedForExchange)
|
||||
}
|
||||
itemM, ok := exchM[item]
|
||||
if !ok {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionsNotLoadedForAsset)
|
||||
}
|
||||
multiPositionTracker, ok := itemM[pair]
|
||||
if !ok {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionsNotLoadedForPair)
|
||||
}
|
||||
newMPT, err := SetupMultiPositionTracker(&MultiPositionTrackerSetup{
|
||||
Exchange: exch,
|
||||
Asset: item,
|
||||
Pair: pair,
|
||||
Underlying: multiPositionTracker.underlying,
|
||||
OfflineCalculation: multiPositionTracker.offlinePNLCalculation,
|
||||
UseExchangePNLCalculation: multiPositionTracker.useExchangePNLCalculations,
|
||||
ExchangePNLCalculation: multiPositionTracker.exchangePNLCalculation,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemM[pair] = newMPT
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupMultiPositionTracker creates a futures order tracker for a specific exchange
|
||||
func SetupMultiPositionTracker(setup *MultiPositionTrackerSetup) (*MultiPositionTracker, error) {
|
||||
if setup == nil {
|
||||
@@ -205,85 +204,17 @@ func SetupMultiPositionTracker(setup *MultiPositionTrackerSetup) (*MultiPosition
|
||||
orderPositions: make(map[string]*PositionTracker),
|
||||
useExchangePNLCalculations: setup.UseExchangePNLCalculation,
|
||||
exchangePNLCalculation: setup.ExchangePNLCalculation,
|
||||
collateralCurrency: setup.CollateralCurrency,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPositions returns all positions
|
||||
func (e *MultiPositionTracker) GetPositions() []PositionStats {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
e.m.Lock()
|
||||
defer e.m.Unlock()
|
||||
resp := make([]PositionStats, len(e.positions))
|
||||
for i := range e.positions {
|
||||
resp[i] = e.positions[i].GetStats()
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// TrackNewOrder upserts an order to the tracker and updates position
|
||||
// status and exposure. PNL is calculated separately as it requires mark prices
|
||||
func (e *MultiPositionTracker) TrackNewOrder(d *Detail) error {
|
||||
if e == nil {
|
||||
return fmt.Errorf("multi-position tracker %w", common.ErrNilPointer)
|
||||
}
|
||||
if d == nil {
|
||||
return ErrSubmissionIsNil
|
||||
}
|
||||
e.m.Lock()
|
||||
defer e.m.Unlock()
|
||||
if d.AssetType != e.asset {
|
||||
return errAssetMismatch
|
||||
}
|
||||
if tracker, ok := e.orderPositions[d.OrderID]; ok {
|
||||
// this has already been associated
|
||||
// update the tracker
|
||||
return tracker.TrackNewOrder(d)
|
||||
}
|
||||
if len(e.positions) > 0 {
|
||||
for i := range e.positions {
|
||||
if e.positions[i].status == Open && i != len(e.positions)-1 {
|
||||
return fmt.Errorf("%w %v at position %v/%v", errPositionDiscrepancy, e.positions[i], i, len(e.positions)-1)
|
||||
}
|
||||
}
|
||||
if e.positions[len(e.positions)-1].status == Open {
|
||||
err := e.positions[len(e.positions)-1].TrackNewOrder(d)
|
||||
if err != nil && !errors.Is(err, ErrPositionClosed) {
|
||||
return err
|
||||
}
|
||||
e.orderPositions[d.OrderID] = e.positions[len(e.positions)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
setup := &PositionTrackerSetup{
|
||||
Pair: d.Pair,
|
||||
EntryPrice: decimal.NewFromFloat(d.Price),
|
||||
Underlying: d.Pair.Base,
|
||||
Asset: d.AssetType,
|
||||
Side: d.Side,
|
||||
UseExchangePNLCalculation: e.useExchangePNLCalculations,
|
||||
}
|
||||
tracker, err := e.SetupPositionTracker(setup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.positions = append(e.positions, tracker)
|
||||
err = tracker.TrackNewOrder(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.orderPositions[d.OrderID] = tracker
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupPositionTracker creates a new position tracker to track n futures orders
|
||||
// until the position(s) are closed
|
||||
func (e *MultiPositionTracker) SetupPositionTracker(setup *PositionTrackerSetup) (*PositionTracker, error) {
|
||||
if e == nil {
|
||||
func (m *MultiPositionTracker) SetupPositionTracker(setup *PositionTrackerSetup) (*PositionTracker, error) {
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("multi-position tracker %w", common.ErrNilPointer)
|
||||
}
|
||||
if e.exchange == "" {
|
||||
if m.exchange == "" {
|
||||
return nil, errExchangeNameEmpty
|
||||
}
|
||||
if setup == nil {
|
||||
@@ -297,7 +228,7 @@ func (e *MultiPositionTracker) SetupPositionTracker(setup *PositionTrackerSetup)
|
||||
}
|
||||
|
||||
resp := &PositionTracker{
|
||||
exchange: strings.ToLower(e.exchange),
|
||||
exchange: strings.ToLower(m.exchange),
|
||||
asset: setup.Asset,
|
||||
contractPair: setup.Pair,
|
||||
underlyingAsset: setup.Underlying,
|
||||
@@ -306,20 +237,167 @@ func (e *MultiPositionTracker) SetupPositionTracker(setup *PositionTrackerSetup)
|
||||
currentDirection: setup.Side,
|
||||
openingDirection: setup.Side,
|
||||
useExchangePNLCalculation: setup.UseExchangePNLCalculation,
|
||||
offlinePNLCalculation: e.offlinePNLCalculation,
|
||||
collateralCurrency: setup.CollateralCurrency,
|
||||
offlinePNLCalculation: m.offlinePNLCalculation,
|
||||
}
|
||||
if !setup.UseExchangePNLCalculation {
|
||||
// use position tracker's pnl calculation by default
|
||||
resp.PNLCalculation = &PNLCalculator{}
|
||||
} else {
|
||||
if e.exchangePNLCalculation == nil {
|
||||
if m.exchangePNLCalculation == nil {
|
||||
return nil, ErrNilPNLCalculator
|
||||
}
|
||||
resp.PNLCalculation = e.exchangePNLCalculation
|
||||
resp.PNLCalculation = m.exchangePNLCalculation
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UpdateOpenPositionUnrealisedPNL updates the pnl for the latest open position
|
||||
// based on the last price and the time
|
||||
func (m *MultiPositionTracker) UpdateOpenPositionUnrealisedPNL(last float64, updated time.Time) (decimal.Decimal, error) {
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
pos := m.positions
|
||||
if len(pos) == 0 {
|
||||
return decimal.Zero, fmt.Errorf("%v %v %v %w", m.exchange, m.asset, m.pair, ErrPositionsNotLoadedForPair)
|
||||
}
|
||||
latestPos := pos[len(pos)-1]
|
||||
if latestPos.status.IsInactive() {
|
||||
return decimal.Zero, fmt.Errorf("%v %v %v %w", m.exchange, m.asset, m.pair, ErrPositionClosed)
|
||||
}
|
||||
err := latestPos.TrackPNLByTime(updated, last)
|
||||
if err != nil {
|
||||
return decimal.Zero, fmt.Errorf("%w for position %v %v %v", err, m.exchange, m.asset, m.pair)
|
||||
}
|
||||
latestPos.m.Lock()
|
||||
defer latestPos.m.Unlock()
|
||||
return latestPos.unrealisedPNL, nil
|
||||
}
|
||||
|
||||
// ClearPositionsForExchange resets positions for an
|
||||
// exchange, asset, pair that has been stored
|
||||
func (c *PositionController) ClearPositionsForExchange(exch string, item asset.Item, pair currency.Pair) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("position controller %w", common.ErrNilPointer)
|
||||
}
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
if !item.IsFutures() {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrNotFuturesAsset)
|
||||
}
|
||||
exchM, ok := c.multiPositionTrackers[strings.ToLower(exch)]
|
||||
if !ok {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionsNotLoadedForExchange)
|
||||
}
|
||||
itemM, ok := exchM[item]
|
||||
if !ok {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionsNotLoadedForAsset)
|
||||
}
|
||||
multiPositionTracker, ok := itemM[pair]
|
||||
if !ok {
|
||||
return fmt.Errorf("%v %v %v %w", exch, item, pair, ErrPositionsNotLoadedForPair)
|
||||
}
|
||||
newMPT, err := SetupMultiPositionTracker(&MultiPositionTrackerSetup{
|
||||
Exchange: exch,
|
||||
Asset: item,
|
||||
Pair: pair,
|
||||
Underlying: multiPositionTracker.underlying,
|
||||
OfflineCalculation: multiPositionTracker.offlinePNLCalculation,
|
||||
UseExchangePNLCalculation: multiPositionTracker.useExchangePNLCalculations,
|
||||
ExchangePNLCalculation: multiPositionTracker.exchangePNLCalculation,
|
||||
CollateralCurrency: multiPositionTracker.collateralCurrency,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemM[pair] = newMPT
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPositions returns all positions
|
||||
func (m *MultiPositionTracker) GetPositions() []PositionStats {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
resp := make([]PositionStats, len(m.positions))
|
||||
for i := range m.positions {
|
||||
resp[i] = m.positions[i].GetStats()
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// TrackNewOrder upserts an order to the tracker and updates position
|
||||
// status and exposure. PNL is calculated separately as it requires mark prices
|
||||
func (m *MultiPositionTracker) TrackNewOrder(d *Detail) error {
|
||||
if m == nil {
|
||||
return fmt.Errorf("multi-position tracker %w", common.ErrNilPointer)
|
||||
}
|
||||
if d == nil {
|
||||
return ErrSubmissionIsNil
|
||||
}
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
if d.AssetType != m.asset {
|
||||
return errAssetMismatch
|
||||
}
|
||||
if tracker, ok := m.orderPositions[d.OrderID]; ok {
|
||||
// this has already been associated
|
||||
// update the tracker
|
||||
return tracker.TrackNewOrder(d, false)
|
||||
}
|
||||
if len(m.positions) > 0 {
|
||||
for i := range m.positions {
|
||||
if m.positions[i].status == Open && i != len(m.positions)-1 {
|
||||
return fmt.Errorf("%w %v at position %v/%v", errPositionDiscrepancy, m.positions[i], i, len(m.positions)-1)
|
||||
}
|
||||
}
|
||||
if m.positions[len(m.positions)-1].status == Open {
|
||||
err := m.positions[len(m.positions)-1].TrackNewOrder(d, false)
|
||||
if err != nil && !errors.Is(err, ErrPositionClosed) {
|
||||
return err
|
||||
}
|
||||
m.orderPositions[d.OrderID] = m.positions[len(m.positions)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
setup := &PositionTrackerSetup{
|
||||
Pair: d.Pair,
|
||||
EntryPrice: decimal.NewFromFloat(d.Price),
|
||||
Underlying: d.Pair.Base,
|
||||
Asset: d.AssetType,
|
||||
Side: d.Side,
|
||||
UseExchangePNLCalculation: m.useExchangePNLCalculations,
|
||||
CollateralCurrency: m.collateralCurrency,
|
||||
}
|
||||
tracker, err := m.SetupPositionTracker(setup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.positions = append(m.positions, tracker)
|
||||
err = tracker.TrackNewOrder(d, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.orderPositions[d.OrderID] = tracker
|
||||
return nil
|
||||
}
|
||||
|
||||
// Liquidate will update the latest open position's
|
||||
// to reflect its liquidated status
|
||||
func (m *MultiPositionTracker) Liquidate(price decimal.Decimal, t time.Time) error {
|
||||
if m == nil {
|
||||
return fmt.Errorf("multi-position tracker %w", common.ErrNilPointer)
|
||||
}
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
if len(m.positions) == 0 {
|
||||
return fmt.Errorf("%v %v %v %w", m.exchange, m.asset, m.pair, ErrPositionsNotLoadedForPair)
|
||||
}
|
||||
return m.positions[len(m.positions)-1].Liquidate(price, t)
|
||||
}
|
||||
|
||||
// GetStats returns a summary of a future position
|
||||
func (p *PositionTracker) GetStats() PositionStats {
|
||||
if p == nil {
|
||||
@@ -330,20 +408,23 @@ func (p *PositionTracker) GetStats() PositionStats {
|
||||
var orders []Detail
|
||||
orders = append(orders, p.longPositions...)
|
||||
orders = append(orders, p.shortPositions...)
|
||||
|
||||
return PositionStats{
|
||||
Exchange: p.exchange,
|
||||
Asset: p.asset,
|
||||
Pair: p.contractPair,
|
||||
Underlying: p.underlyingAsset,
|
||||
Status: p.status,
|
||||
Orders: orders,
|
||||
RealisedPNL: p.realisedPNL,
|
||||
UnrealisedPNL: p.unrealisedPNL,
|
||||
LatestDirection: p.currentDirection,
|
||||
OpeningDirection: p.openingDirection,
|
||||
OpeningPrice: p.entryPrice,
|
||||
LatestPrice: p.latestPrice,
|
||||
PNLHistory: p.pnlHistory,
|
||||
Exchange: p.exchange,
|
||||
Asset: p.asset,
|
||||
Pair: p.contractPair,
|
||||
Underlying: p.underlyingAsset,
|
||||
CollateralCurrency: p.collateralCurrency,
|
||||
Status: p.status,
|
||||
Orders: orders,
|
||||
RealisedPNL: p.realisedPNL,
|
||||
UnrealisedPNL: p.unrealisedPNL,
|
||||
LatestDirection: p.currentDirection,
|
||||
OpeningDirection: p.openingDirection,
|
||||
OpeningPrice: p.entryPrice,
|
||||
LatestPrice: p.latestPrice,
|
||||
PNLHistory: p.pnlHistory,
|
||||
Exposure: p.exposure,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,8 +441,9 @@ func (p *PositionTracker) TrackPNLByTime(t time.Time, currentPrice float64) erro
|
||||
}()
|
||||
price := decimal.NewFromFloat(currentPrice)
|
||||
result := &PNLResult{
|
||||
Time: t,
|
||||
Price: price,
|
||||
Time: t,
|
||||
Price: price,
|
||||
Status: p.status,
|
||||
}
|
||||
if p.currentDirection.IsLong() {
|
||||
diff := price.Sub(p.entryPrice)
|
||||
@@ -371,8 +453,12 @@ func (p *PositionTracker) TrackPNLByTime(t time.Time, currentPrice float64) erro
|
||||
result.UnrealisedPNL = p.exposure.Mul(diff)
|
||||
}
|
||||
if len(p.pnlHistory) > 0 {
|
||||
result.RealisedPNLBeforeFees = p.pnlHistory[len(p.pnlHistory)-1].RealisedPNLBeforeFees
|
||||
result.Exposure = p.pnlHistory[len(p.pnlHistory)-1].Exposure
|
||||
latest := p.pnlHistory[len(p.pnlHistory)-1]
|
||||
result.RealisedPNLBeforeFees = latest.RealisedPNLBeforeFees
|
||||
result.Exposure = latest.Exposure
|
||||
result.Direction = latest.Direction
|
||||
result.RealisedPNL = latest.RealisedPNL
|
||||
result.IsLiquidated = latest.IsLiquidated
|
||||
}
|
||||
var err error
|
||||
p.pnlHistory, err = upsertPNLEntry(p.pnlHistory, result)
|
||||
@@ -391,6 +477,37 @@ func (p *PositionTracker) GetRealisedPNL() decimal.Decimal {
|
||||
return calculateRealisedPNL(p.pnlHistory)
|
||||
}
|
||||
|
||||
// Liquidate will update the positions stats to reflect its liquidation
|
||||
func (p *PositionTracker) Liquidate(price decimal.Decimal, t time.Time) error {
|
||||
if p == nil {
|
||||
return fmt.Errorf("position tracker %w", common.ErrNilPointer)
|
||||
}
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
latest, err := p.GetLatestPNLSnapshot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !latest.Time.Equal(t) {
|
||||
return fmt.Errorf("%w cannot liquidate from a different time. PNL snapshot %v. Liquidation request on %v Status: %v", errCannotLiquidate, latest.Time, t, p.status)
|
||||
}
|
||||
p.status = Liquidated
|
||||
p.currentDirection = ClosePosition
|
||||
p.exposure = decimal.Zero
|
||||
p.realisedPNL = decimal.Zero
|
||||
p.unrealisedPNL = decimal.Zero
|
||||
_, err = upsertPNLEntry(p.pnlHistory, &PNLResult{
|
||||
Time: t,
|
||||
Price: price,
|
||||
Direction: ClosePosition,
|
||||
IsLiquidated: true,
|
||||
IsOrder: true,
|
||||
Status: p.status,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetLatestPNLSnapshot takes the latest pnl history value
|
||||
// and returns it
|
||||
func (p *PositionTracker) GetLatestPNLSnapshot() (PNLResult, error) {
|
||||
@@ -402,13 +519,16 @@ func (p *PositionTracker) GetLatestPNLSnapshot() (PNLResult, error) {
|
||||
|
||||
// TrackNewOrder knows how things are going for a given
|
||||
// futures contract
|
||||
func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
func (p *PositionTracker) TrackNewOrder(d *Detail, isInitialOrder bool) error {
|
||||
if p == nil {
|
||||
return fmt.Errorf("position tracker %w", common.ErrNilPointer)
|
||||
}
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
if p.status == Closed {
|
||||
if isInitialOrder && len(p.pnlHistory) > 0 {
|
||||
return fmt.Errorf("%w received isInitialOrder = true with existing position", errCannotTrackInvalidParams)
|
||||
}
|
||||
if p.status.IsInactive() {
|
||||
return ErrPositionClosed
|
||||
}
|
||||
if d == nil {
|
||||
@@ -426,6 +546,7 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
return fmt.Errorf("%w asset '%v' received: '%v'",
|
||||
errOrderNotEqualToTracker, d.AssetType, p.asset)
|
||||
}
|
||||
|
||||
if d.Side == UnknownSide {
|
||||
return ErrSideIsInvalid
|
||||
}
|
||||
@@ -484,7 +605,8 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
longSide = longSide.Add(decimal.NewFromFloat(p.longPositions[i].Amount))
|
||||
}
|
||||
|
||||
if p.currentDirection == UnknownSide {
|
||||
if isInitialOrder {
|
||||
p.openingDirection = d.Side
|
||||
p.currentDirection = d.Side
|
||||
}
|
||||
|
||||
@@ -514,8 +636,18 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
if len(p.pnlHistory) != 0 {
|
||||
cal.PreviousPrice = p.pnlHistory[len(p.pnlHistory)-1].Price
|
||||
}
|
||||
if (cal.OrderDirection.IsShort() && cal.CurrentDirection.IsLong() || cal.OrderDirection.IsLong() && cal.CurrentDirection.IsShort()) &&
|
||||
cal.Exposure.LessThan(amount) {
|
||||
switch {
|
||||
case isInitialOrder:
|
||||
result = &PNLResult{
|
||||
IsOrder: true,
|
||||
Time: cal.Time,
|
||||
Price: cal.CurrentPrice,
|
||||
Exposure: cal.Amount,
|
||||
Fee: cal.Fee,
|
||||
Direction: cal.OpeningDirection,
|
||||
UnrealisedPNL: cal.Fee.Neg(),
|
||||
}
|
||||
case (cal.OrderDirection.IsShort() && cal.CurrentDirection.IsLong() || cal.OrderDirection.IsLong() && cal.CurrentDirection.IsShort()) && cal.Exposure.LessThan(amount):
|
||||
// latest order swaps directions!
|
||||
// split the order to calculate PNL from each direction
|
||||
first := cal.Exposure
|
||||
@@ -527,6 +659,7 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Status = p.status
|
||||
p.pnlHistory, err = upsertPNLEntry(cal.PNLHistory, result)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -548,7 +681,7 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
cal.Time = cal.Time.Add(1)
|
||||
cal.PNLHistory = p.pnlHistory
|
||||
result, err = p.PNLCalculation.CalculatePNL(context.TODO(), cal)
|
||||
} else {
|
||||
default:
|
||||
result, err = p.PNLCalculation.CalculatePNL(context.TODO(), cal)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -559,6 +692,7 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
result.RealisedPNLBeforeFees = decimal.Zero
|
||||
p.status = Closed
|
||||
}
|
||||
result.Status = p.status
|
||||
p.pnlHistory, err = upsertPNLEntry(p.pnlHistory, result)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -571,7 +705,7 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
case shortSide.GreaterThan(longSide):
|
||||
p.currentDirection = Short
|
||||
default:
|
||||
p.currentDirection = UnknownSide
|
||||
p.currentDirection = ClosePosition
|
||||
}
|
||||
|
||||
if p.currentDirection.IsLong() {
|
||||
@@ -585,6 +719,9 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
p.closingPrice = decimal.NewFromFloat(d.Price)
|
||||
p.realisedPNL = calculateRealisedPNL(p.pnlHistory)
|
||||
p.unrealisedPNL = decimal.Zero
|
||||
p.pnlHistory[len(p.pnlHistory)-1].RealisedPNL = p.realisedPNL
|
||||
p.pnlHistory[len(p.pnlHistory)-1].UnrealisedPNL = p.unrealisedPNL
|
||||
p.pnlHistory[len(p.pnlHistory)-1].Direction = p.currentDirection
|
||||
} else if p.exposure.IsNegative() {
|
||||
if p.currentDirection.IsLong() {
|
||||
p.currentDirection = Short
|
||||
@@ -596,6 +733,12 @@ func (p *PositionTracker) TrackNewOrder(d *Detail) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCurrencyForRealisedPNL is a generic handling of determining the asset
|
||||
// to assign realised PNL into, which is just itself
|
||||
func (p *PNLCalculator) GetCurrencyForRealisedPNL(realisedAsset asset.Item, realisedPair currency.Pair) (currency.Code, asset.Item, error) {
|
||||
return realisedPair.Base, realisedAsset, nil
|
||||
}
|
||||
|
||||
// CalculatePNL this is a localised generic way of calculating open
|
||||
// positions' worth, it is an implementation of the PNLCalculation interface
|
||||
func (p *PNLCalculator) CalculatePNL(_ context.Context, calc *PNLCalculatorRequest) (*PNLResult, error) {
|
||||
@@ -604,7 +747,13 @@ func (p *PNLCalculator) CalculatePNL(_ context.Context, calc *PNLCalculatorReque
|
||||
}
|
||||
var previousPNL *PNLResult
|
||||
if len(calc.PNLHistory) > 0 {
|
||||
previousPNL = &calc.PNLHistory[len(calc.PNLHistory)-1]
|
||||
for i := len(calc.PNLHistory) - 1; i >= 0; i-- {
|
||||
if calc.PNLHistory[i].Time.Equal(calc.Time) || !calc.PNLHistory[i].IsOrder {
|
||||
continue
|
||||
}
|
||||
previousPNL = &calc.PNLHistory[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
var prevExposure decimal.Decimal
|
||||
if previousPNL != nil {
|
||||
@@ -646,13 +795,16 @@ func (p *PNLCalculator) CalculatePNL(_ context.Context, calc *PNLCalculatorReque
|
||||
}
|
||||
|
||||
response := &PNLResult{
|
||||
IsOrder: true,
|
||||
Time: calc.Time,
|
||||
UnrealisedPNL: unrealisedPNL,
|
||||
RealisedPNLBeforeFees: realisedPNL,
|
||||
Price: calc.CurrentPrice,
|
||||
Exposure: currentExposure,
|
||||
Fee: calc.Fee,
|
||||
Direction: calc.CurrentDirection,
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -661,6 +813,9 @@ func (p *PNLCalculator) CalculatePNL(_ context.Context, calc *PNLCalculatorReque
|
||||
func calculateRealisedPNL(pnlHistory []PNLResult) decimal.Decimal {
|
||||
var realisedPNL, totalFees decimal.Decimal
|
||||
for i := range pnlHistory {
|
||||
if !pnlHistory[i].IsOrder {
|
||||
continue
|
||||
}
|
||||
realisedPNL = realisedPNL.Add(pnlHistory[i].RealisedPNLBeforeFees)
|
||||
totalFees = totalFees.Add(pnlHistory[i].Fee)
|
||||
}
|
||||
@@ -674,10 +829,24 @@ func upsertPNLEntry(pnlHistory []PNLResult, entry *PNLResult) ([]PNLResult, erro
|
||||
return nil, errTimeUnset
|
||||
}
|
||||
for i := range pnlHistory {
|
||||
if entry.Time.Equal(pnlHistory[i].Time) {
|
||||
pnlHistory[i] = *entry
|
||||
return pnlHistory, nil
|
||||
if !entry.Time.Equal(pnlHistory[i].Time) {
|
||||
continue
|
||||
}
|
||||
pnlHistory[i].UnrealisedPNL = entry.UnrealisedPNL
|
||||
pnlHistory[i].RealisedPNL = entry.RealisedPNL
|
||||
pnlHistory[i].RealisedPNLBeforeFees = entry.RealisedPNLBeforeFees
|
||||
pnlHistory[i].Exposure = entry.Exposure
|
||||
pnlHistory[i].Direction = entry.Direction
|
||||
pnlHistory[i].Price = entry.Price
|
||||
pnlHistory[i].Status = entry.Status
|
||||
pnlHistory[i].Fee = entry.Fee
|
||||
if entry.IsOrder {
|
||||
pnlHistory[i].IsOrder = true
|
||||
}
|
||||
if entry.IsLiquidated {
|
||||
pnlHistory[i].IsLiquidated = true
|
||||
}
|
||||
return pnlHistory, nil
|
||||
}
|
||||
pnlHistory = append(pnlHistory, *entry)
|
||||
sort.Slice(pnlHistory, func(i, j int) bool {
|
||||
|
||||
@@ -28,10 +28,20 @@ func (f *FakePNL) CalculatePNL(context.Context, *PNLCalculatorRequest) (*PNLResu
|
||||
return f.result, nil
|
||||
}
|
||||
|
||||
// GetCurrencyForRealisedPNL overrides default pnl calculations
|
||||
func (f *FakePNL) GetCurrencyForRealisedPNL(realisedAsset asset.Item, realisedPair currency.Pair) (currency.Code, asset.Item, error) {
|
||||
if f.err != nil {
|
||||
return realisedPair.Base, asset.Empty, f.err
|
||||
}
|
||||
return realisedPair.Base, realisedAsset, nil
|
||||
}
|
||||
|
||||
func TestUpsertPNLEntry(t *testing.T) {
|
||||
t.Parallel()
|
||||
var results []PNLResult
|
||||
result := &PNLResult{}
|
||||
result := &PNLResult{
|
||||
IsOrder: true,
|
||||
}
|
||||
_, err := upsertPNLEntry(results, result)
|
||||
if !errors.Is(err, errTimeUnset) {
|
||||
t.Error(err)
|
||||
@@ -79,11 +89,11 @@ func TestTrackNewOrder(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = f.TrackNewOrder(nil)
|
||||
err = f.TrackNewOrder(nil, false)
|
||||
if !errors.Is(err, ErrSubmissionIsNil) {
|
||||
t.Error(err)
|
||||
}
|
||||
err = f.TrackNewOrder(&Detail{})
|
||||
err = f.TrackNewOrder(&Detail{}, false)
|
||||
if !errors.Is(err, errOrderNotEqualToTracker) {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -95,7 +105,7 @@ func TestTrackNewOrder(t *testing.T) {
|
||||
OrderID: "1",
|
||||
Price: 1337,
|
||||
}
|
||||
err = f.TrackNewOrder(od)
|
||||
err = f.TrackNewOrder(od, false)
|
||||
if !errors.Is(err, ErrSideIsInvalid) {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -103,13 +113,13 @@ func TestTrackNewOrder(t *testing.T) {
|
||||
od.Side = Long
|
||||
od.Amount = 1
|
||||
od.OrderID = "2"
|
||||
err = f.TrackNewOrder(od)
|
||||
err = f.TrackNewOrder(od, false)
|
||||
if !errors.Is(err, errTimeUnset) {
|
||||
t.Error(err)
|
||||
}
|
||||
f.openingDirection = Long
|
||||
od.Date = time.Now()
|
||||
err = f.TrackNewOrder(od)
|
||||
err = f.TrackNewOrder(od, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -130,7 +140,7 @@ func TestTrackNewOrder(t *testing.T) {
|
||||
od.Amount = 0.4
|
||||
od.Side = Short
|
||||
od.OrderID = "3"
|
||||
err = f.TrackNewOrder(od)
|
||||
err = f.TrackNewOrder(od, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -149,7 +159,7 @@ func TestTrackNewOrder(t *testing.T) {
|
||||
od.Side = Short
|
||||
od.OrderID = "4"
|
||||
od.Fee = 0.1
|
||||
err = f.TrackNewOrder(od)
|
||||
err = f.TrackNewOrder(od, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -164,27 +174,41 @@ func TestTrackNewOrder(t *testing.T) {
|
||||
od.OrderID = "5"
|
||||
od.Side = Long
|
||||
od.Amount = 0.2
|
||||
err = f.TrackNewOrder(od)
|
||||
err = f.TrackNewOrder(od, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
if f.currentDirection != UnknownSide {
|
||||
if f.currentDirection != ClosePosition {
|
||||
t.Errorf("expected recognition that its unknown, received '%v'", f.currentDirection)
|
||||
}
|
||||
if f.status != Closed {
|
||||
t.Errorf("expected recognition that its closed, received '%v'", f.status)
|
||||
}
|
||||
|
||||
err = f.TrackNewOrder(od)
|
||||
err = f.TrackNewOrder(od, false)
|
||||
if !errors.Is(err, ErrPositionClosed) {
|
||||
t.Error(err)
|
||||
}
|
||||
if f.currentDirection != UnknownSide {
|
||||
if f.currentDirection != ClosePosition {
|
||||
t.Errorf("expected recognition that its unknown, received '%v'", f.currentDirection)
|
||||
}
|
||||
if f.status != Closed {
|
||||
t.Errorf("expected recognition that its closed, received '%v'", f.status)
|
||||
}
|
||||
|
||||
err = f.TrackNewOrder(od, true)
|
||||
if !errors.Is(err, errCannotTrackInvalidParams) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err = e.SetupPositionTracker(setup)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
err = f.TrackNewOrder(od, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupMultiPositionTracker(t *testing.T) {
|
||||
@@ -359,7 +383,7 @@ func TestExchangeTrackNewOrder(t *testing.T) {
|
||||
func TestSetupPositionControllerReal(t *testing.T) {
|
||||
t.Parallel()
|
||||
pc := SetupPositionController()
|
||||
if pc.positionTrackerControllers == nil {
|
||||
if pc.multiPositionTrackers == nil {
|
||||
t.Error("unexpected nil")
|
||||
}
|
||||
}
|
||||
@@ -489,14 +513,14 @@ func TestGetPositionsForExchange(t *testing.T) {
|
||||
if len(pos) != 0 {
|
||||
t.Error("expected zero")
|
||||
}
|
||||
c.positionTrackerControllers = make(map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.positionTrackerControllers[testExchange] = nil
|
||||
c.multiPositionTrackers = make(map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.multiPositionTrackers[testExchange] = nil
|
||||
_, err = c.GetPositionsForExchange(testExchange, asset.Futures, p)
|
||||
if !errors.Is(err, ErrPositionsNotLoadedForAsset) {
|
||||
t.Errorf("received '%v' expected '%v", err, ErrPositionsNotLoadedForExchange)
|
||||
}
|
||||
c.positionTrackerControllers[testExchange] = make(map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.positionTrackerControllers[testExchange][asset.Futures] = nil
|
||||
c.multiPositionTrackers[testExchange] = make(map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.multiPositionTrackers[testExchange][asset.Futures] = nil
|
||||
_, err = c.GetPositionsForExchange(testExchange, asset.Futures, p)
|
||||
if !errors.Is(err, ErrPositionsNotLoadedForPair) {
|
||||
t.Errorf("received '%v' expected '%v", err, ErrPositionsNotLoadedForPair)
|
||||
@@ -506,8 +530,8 @@ func TestGetPositionsForExchange(t *testing.T) {
|
||||
t.Errorf("received '%v' expected '%v", err, ErrNotFuturesAsset)
|
||||
}
|
||||
|
||||
c.positionTrackerControllers[testExchange][asset.Futures] = make(map[currency.Pair]*MultiPositionTracker)
|
||||
c.positionTrackerControllers[testExchange][asset.Futures][p] = &MultiPositionTracker{
|
||||
c.multiPositionTrackers[testExchange][asset.Futures] = make(map[currency.Pair]*MultiPositionTracker)
|
||||
c.multiPositionTrackers[testExchange][asset.Futures][p] = &MultiPositionTracker{
|
||||
exchange: testExchange,
|
||||
}
|
||||
|
||||
@@ -518,7 +542,7 @@ func TestGetPositionsForExchange(t *testing.T) {
|
||||
if len(pos) != 0 {
|
||||
t.Fatal("expected zero")
|
||||
}
|
||||
c.positionTrackerControllers[testExchange][asset.Futures][p] = &MultiPositionTracker{
|
||||
c.multiPositionTrackers[testExchange][asset.Futures][p] = &MultiPositionTracker{
|
||||
exchange: testExchange,
|
||||
positions: []*PositionTracker{
|
||||
{
|
||||
@@ -551,14 +575,14 @@ func TestClearPositionsForExchange(t *testing.T) {
|
||||
if !errors.Is(err, ErrPositionsNotLoadedForExchange) {
|
||||
t.Errorf("received '%v' expected '%v", err, ErrPositionsNotLoadedForExchange)
|
||||
}
|
||||
c.positionTrackerControllers = make(map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.positionTrackerControllers[testExchange] = nil
|
||||
c.multiPositionTrackers = make(map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.multiPositionTrackers[testExchange] = nil
|
||||
err = c.ClearPositionsForExchange(testExchange, asset.Futures, p)
|
||||
if !errors.Is(err, ErrPositionsNotLoadedForAsset) {
|
||||
t.Errorf("received '%v' expected '%v", err, ErrPositionsNotLoadedForExchange)
|
||||
}
|
||||
c.positionTrackerControllers[testExchange] = make(map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.positionTrackerControllers[testExchange][asset.Futures] = nil
|
||||
c.multiPositionTrackers[testExchange] = make(map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
c.multiPositionTrackers[testExchange][asset.Futures] = nil
|
||||
err = c.ClearPositionsForExchange(testExchange, asset.Futures, p)
|
||||
if !errors.Is(err, ErrPositionsNotLoadedForPair) {
|
||||
t.Errorf("received '%v' expected '%v", err, ErrPositionsNotLoadedForPair)
|
||||
@@ -568,11 +592,11 @@ func TestClearPositionsForExchange(t *testing.T) {
|
||||
t.Errorf("received '%v' expected '%v", err, ErrNotFuturesAsset)
|
||||
}
|
||||
|
||||
c.positionTrackerControllers[testExchange][asset.Futures] = make(map[currency.Pair]*MultiPositionTracker)
|
||||
c.positionTrackerControllers[testExchange][asset.Futures][p] = &MultiPositionTracker{
|
||||
c.multiPositionTrackers[testExchange][asset.Futures] = make(map[currency.Pair]*MultiPositionTracker)
|
||||
c.multiPositionTrackers[testExchange][asset.Futures][p] = &MultiPositionTracker{
|
||||
exchange: testExchange,
|
||||
}
|
||||
c.positionTrackerControllers[testExchange][asset.Futures][p] = &MultiPositionTracker{
|
||||
c.multiPositionTrackers[testExchange][asset.Futures][p] = &MultiPositionTracker{
|
||||
exchange: testExchange,
|
||||
underlying: currency.DOGE,
|
||||
positions: []*PositionTracker{
|
||||
@@ -585,7 +609,7 @@ func TestClearPositionsForExchange(t *testing.T) {
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v' expected '%v", err, nil)
|
||||
}
|
||||
if len(c.positionTrackerControllers[testExchange][asset.Futures][p].positions) != 0 {
|
||||
if len(c.multiPositionTrackers[testExchange][asset.Futures][p].positions) != 0 {
|
||||
t.Fatal("expected 0")
|
||||
}
|
||||
c = nil
|
||||
@@ -599,29 +623,32 @@ func TestCalculateRealisedPNL(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := calculateRealisedPNL(nil)
|
||||
if !result.IsZero() {
|
||||
t.Error("expected zero")
|
||||
t.Errorf("received '%v' expected '0'", result)
|
||||
}
|
||||
result = calculateRealisedPNL([]PNLResult{
|
||||
{
|
||||
IsOrder: true,
|
||||
RealisedPNLBeforeFees: decimal.NewFromInt(1337),
|
||||
},
|
||||
})
|
||||
if !result.Equal(decimal.NewFromInt(1337)) {
|
||||
t.Error("expected 1337")
|
||||
t.Errorf("received '%v' expected '1337'", result)
|
||||
}
|
||||
|
||||
result = calculateRealisedPNL([]PNLResult{
|
||||
{
|
||||
IsOrder: true,
|
||||
RealisedPNLBeforeFees: decimal.NewFromInt(1339),
|
||||
Fee: decimal.NewFromInt(2),
|
||||
},
|
||||
{
|
||||
IsOrder: true,
|
||||
RealisedPNLBeforeFees: decimal.NewFromInt(2),
|
||||
Fee: decimal.NewFromInt(2),
|
||||
},
|
||||
})
|
||||
if !result.Equal(decimal.NewFromInt(1337)) {
|
||||
t.Error("expected 1337")
|
||||
t.Errorf("received '%v' expected '1337'", result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,3 +829,245 @@ func TestUpdateOpenPositionUnrealisedPNL(t *testing.T) {
|
||||
t.Errorf("received '%v' expected '%v", err, common.ErrNilPointer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCollateralCurrency(t *testing.T) {
|
||||
t.Parallel()
|
||||
var expectedError = ErrNotFuturesAsset
|
||||
pc := SetupPositionController()
|
||||
err := pc.SetCollateralCurrency("hi", asset.Spot, currency.Pair{}, currency.Code{})
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Errorf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
pc.multiPositionTrackers = make(map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
err = pc.SetCollateralCurrency("hi", asset.Futures, cp, currency.DOGE)
|
||||
expectedError = ErrPositionsNotLoadedForExchange
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
pc.multiPositionTrackers["hi"] = make(map[asset.Item]map[currency.Pair]*MultiPositionTracker)
|
||||
err = pc.SetCollateralCurrency("hi", asset.Futures, cp, currency.DOGE)
|
||||
expectedError = ErrPositionsNotLoadedForAsset
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
|
||||
pc.multiPositionTrackers["hi"][asset.Futures] = make(map[currency.Pair]*MultiPositionTracker)
|
||||
err = pc.SetCollateralCurrency("hi", asset.Futures, cp, currency.DOGE)
|
||||
expectedError = ErrPositionsNotLoadedForPair
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
|
||||
pc.multiPositionTrackers["hi"][asset.Futures][cp] = nil
|
||||
err = pc.SetCollateralCurrency("hi", asset.Futures, cp, currency.DOGE)
|
||||
expectedError = common.ErrNilPointer
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
|
||||
pc.multiPositionTrackers["hi"][asset.Futures][cp] = &MultiPositionTracker{
|
||||
exchange: "hi",
|
||||
asset: asset.Futures,
|
||||
pair: cp,
|
||||
orderPositions: make(map[string]*PositionTracker),
|
||||
}
|
||||
err = pc.TrackNewOrder(&Detail{
|
||||
Date: time.Now(),
|
||||
Exchange: "hi",
|
||||
Pair: cp,
|
||||
AssetType: asset.Futures,
|
||||
Side: Long,
|
||||
OrderID: "lol",
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received '%v' expected '%v", err, nil)
|
||||
}
|
||||
|
||||
err = pc.SetCollateralCurrency("hi", asset.Futures, cp, currency.DOGE)
|
||||
expectedError = nil
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
|
||||
if !pc.multiPositionTrackers["hi"][asset.Futures][cp].collateralCurrency.Equal(currency.DOGE) {
|
||||
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers["hi"][asset.Futures][cp].collateralCurrency, currency.DOGE)
|
||||
}
|
||||
|
||||
if !pc.multiPositionTrackers["hi"][asset.Futures][cp].positions[0].collateralCurrency.Equal(currency.DOGE) {
|
||||
t.Errorf("received '%v' expected '%v'", pc.multiPositionTrackers["hi"][asset.Futures][cp].positions[0].collateralCurrency, currency.DOGE)
|
||||
}
|
||||
|
||||
pc = nil
|
||||
err = pc.SetCollateralCurrency("hi", asset.Spot, currency.Pair{}, currency.Code{})
|
||||
expectedError = common.ErrNilPointer
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Errorf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMPTUpdateOpenPositionUnrealisedPNL(t *testing.T) {
|
||||
t.Parallel()
|
||||
var err, expectedError error
|
||||
expectedError = nil
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
pc := SetupPositionController()
|
||||
err = pc.TrackNewOrder(&Detail{
|
||||
Date: time.Now(),
|
||||
Exchange: "hi",
|
||||
Pair: cp,
|
||||
AssetType: asset.Futures,
|
||||
Side: Long,
|
||||
OrderID: "lol",
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
})
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
result, err := pc.multiPositionTrackers["hi"][asset.Futures][cp].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
if result.Equal(decimal.NewFromInt(1337)) {
|
||||
t.Error("")
|
||||
}
|
||||
|
||||
expectedError = ErrPositionClosed
|
||||
pc.multiPositionTrackers["hi"][asset.Futures][cp].positions[0].status = Closed
|
||||
_, err = pc.multiPositionTrackers["hi"][asset.Futures][cp].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
|
||||
expectedError = ErrPositionsNotLoadedForPair
|
||||
pc.multiPositionTrackers["hi"][asset.Futures][cp].positions = nil
|
||||
_, err = pc.multiPositionTrackers["hi"][asset.Futures][cp].UpdateOpenPositionUnrealisedPNL(1337, time.Now())
|
||||
if !errors.Is(err, expectedError) {
|
||||
t.Fatalf("received '%v' expected '%v", err, expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMPTLiquidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
item := asset.Futures
|
||||
pair, err := currency.NewPairFromStrings("BTC", "1231")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
e := &MultiPositionTracker{
|
||||
exchange: testExchange,
|
||||
exchangePNLCalculation: &FakePNL{},
|
||||
asset: item,
|
||||
orderPositions: make(map[string]*PositionTracker),
|
||||
}
|
||||
|
||||
err = e.Liquidate(decimal.Zero, time.Time{})
|
||||
if !errors.Is(err, ErrPositionsNotLoadedForPair) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
setup := &PositionTrackerSetup{
|
||||
Pair: pair,
|
||||
Asset: item,
|
||||
}
|
||||
_, err = e.SetupPositionTracker(setup)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
tt := time.Now()
|
||||
err = e.TrackNewOrder(&Detail{
|
||||
Date: tt,
|
||||
Exchange: testExchange,
|
||||
Pair: pair,
|
||||
AssetType: item,
|
||||
Side: Long,
|
||||
OrderID: "lol",
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = e.Liquidate(decimal.Zero, time.Time{})
|
||||
if !errors.Is(err, errCannotLiquidate) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = e.Liquidate(decimal.Zero, tt)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if e.positions[0].status != Liquidated {
|
||||
t.Errorf("received '%v' expected '%v'", e.positions[0].status, Liquidated)
|
||||
}
|
||||
if !e.positions[0].exposure.IsZero() {
|
||||
t.Errorf("received '%v' expected '%v'", e.positions[0].exposure, 0)
|
||||
}
|
||||
|
||||
e = nil
|
||||
err = e.Liquidate(decimal.Zero, tt)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPositionLiquidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
item := asset.Futures
|
||||
pair, err := currency.NewPairFromStrings("BTC", "1231")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
p := &PositionTracker{
|
||||
contractPair: pair,
|
||||
asset: item,
|
||||
exchange: testExchange,
|
||||
PNLCalculation: &PNLCalculator{},
|
||||
status: Open,
|
||||
openingDirection: Long,
|
||||
}
|
||||
|
||||
tt := time.Now()
|
||||
err = p.TrackNewOrder(&Detail{
|
||||
Date: tt,
|
||||
Exchange: testExchange,
|
||||
Pair: pair,
|
||||
AssetType: item,
|
||||
Side: Long,
|
||||
OrderID: "lol",
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
}, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = p.Liquidate(decimal.Zero, time.Time{})
|
||||
if !errors.Is(err, errCannotLiquidate) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = p.Liquidate(decimal.Zero, tt)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if p.status != Liquidated {
|
||||
t.Errorf("received '%v' expected '%v'", p.status, Liquidated)
|
||||
}
|
||||
if !p.exposure.IsZero() {
|
||||
t.Errorf("received '%v' expected '%v'", p.exposure, 0)
|
||||
}
|
||||
|
||||
p = nil
|
||||
err = p.Liquidate(decimal.Zero, tt)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,18 +44,21 @@ var (
|
||||
errNilOrder = errors.New("nil order received")
|
||||
errNoPNLHistory = errors.New("no pnl history")
|
||||
errCannotCalculateUnrealisedPNL = errors.New("cannot calculate unrealised PNL")
|
||||
errCannotTrackInvalidParams = errors.New("parameters set incorrectly, cannot track")
|
||||
)
|
||||
|
||||
// PNLCalculation is an interface to allow multiple
|
||||
// ways of calculating PNL to be used for futures positions
|
||||
type PNLCalculation interface {
|
||||
CalculatePNL(context.Context, *PNLCalculatorRequest) (*PNLResult, error)
|
||||
GetCurrencyForRealisedPNL(realisedAsset asset.Item, realisedPair currency.Pair) (currency.Code, asset.Item, error)
|
||||
}
|
||||
|
||||
// CollateralManagement is an interface that allows
|
||||
// multiple ways of calculating the size of collateral
|
||||
// on an exchange
|
||||
type CollateralManagement interface {
|
||||
GetCollateralCurrencyForContract(asset.Item, currency.Pair) (currency.Code, asset.Item, error)
|
||||
ScaleCollateral(ctx context.Context, calculator *CollateralCalculator) (*CollateralByCurrency, error)
|
||||
CalculateTotalCollateral(context.Context, *TotalCollateralCalculator) (*TotalCollateralResponse, error)
|
||||
}
|
||||
@@ -125,8 +128,8 @@ type UsedCollateralBreakdown struct {
|
||||
// and so all you need to do is send all orders to
|
||||
// the position controller and its all tracked happily
|
||||
type PositionController struct {
|
||||
m sync.Mutex
|
||||
positionTrackerControllers map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker
|
||||
m sync.Mutex
|
||||
multiPositionTrackers map[string]map[asset.Item]map[currency.Pair]*MultiPositionTracker
|
||||
}
|
||||
|
||||
// MultiPositionTracker will track the performance of
|
||||
@@ -134,12 +137,13 @@ type PositionController struct {
|
||||
// is closed, then the position controller will create a new one
|
||||
// to track the current positions
|
||||
type MultiPositionTracker struct {
|
||||
m sync.Mutex
|
||||
exchange string
|
||||
asset asset.Item
|
||||
pair currency.Pair
|
||||
underlying currency.Code
|
||||
positions []*PositionTracker
|
||||
m sync.Mutex
|
||||
exchange string
|
||||
asset asset.Item
|
||||
pair currency.Pair
|
||||
underlying currency.Code
|
||||
collateralCurrency currency.Code
|
||||
positions []*PositionTracker
|
||||
// order positions allows for an easier time knowing which order is
|
||||
// part of which position tracker
|
||||
orderPositions map[string]*PositionTracker
|
||||
@@ -155,6 +159,7 @@ type MultiPositionTrackerSetup struct {
|
||||
Asset asset.Item
|
||||
Pair currency.Pair
|
||||
Underlying currency.Code
|
||||
CollateralCurrency currency.Code
|
||||
OfflineCalculation bool
|
||||
UseExchangePNLCalculation bool
|
||||
ExchangePNLCalculation PNLCalculation
|
||||
@@ -173,6 +178,7 @@ type PositionTracker struct {
|
||||
asset asset.Item
|
||||
contractPair currency.Pair
|
||||
underlyingAsset currency.Code
|
||||
collateralCurrency currency.Code
|
||||
exposure decimal.Decimal
|
||||
currentDirection Side
|
||||
openingDirection Side
|
||||
@@ -196,6 +202,7 @@ type PositionTrackerSetup struct {
|
||||
Pair currency.Pair
|
||||
EntryPrice decimal.Decimal
|
||||
Underlying currency.Code
|
||||
CollateralCurrency currency.Code
|
||||
Asset asset.Item
|
||||
Side Side
|
||||
UseExchangePNLCalculation bool
|
||||
@@ -256,29 +263,36 @@ type PNLCalculatorRequest struct {
|
||||
|
||||
// PNLResult stores a PNL result from a point in time
|
||||
type PNLResult struct {
|
||||
Status Status
|
||||
Time time.Time
|
||||
UnrealisedPNL decimal.Decimal
|
||||
RealisedPNLBeforeFees decimal.Decimal
|
||||
RealisedPNL decimal.Decimal
|
||||
Price decimal.Decimal
|
||||
Exposure decimal.Decimal
|
||||
Direction Side
|
||||
Fee decimal.Decimal
|
||||
IsLiquidated bool
|
||||
// Is event is supposed to show that something has happened and it isnt just tracking in time
|
||||
IsOrder bool
|
||||
}
|
||||
|
||||
// PositionStats is a basic holder
|
||||
// for position information
|
||||
type PositionStats struct {
|
||||
Exchange string
|
||||
Asset asset.Item
|
||||
Pair currency.Pair
|
||||
Underlying currency.Code
|
||||
Orders []Detail
|
||||
RealisedPNL decimal.Decimal
|
||||
UnrealisedPNL decimal.Decimal
|
||||
LatestDirection Side
|
||||
Status Status
|
||||
OpeningDirection Side
|
||||
OpeningPrice decimal.Decimal
|
||||
LatestPrice decimal.Decimal
|
||||
PNLHistory []PNLResult
|
||||
Exchange string
|
||||
Asset asset.Item
|
||||
Pair currency.Pair
|
||||
Underlying currency.Code
|
||||
CollateralCurrency currency.Code
|
||||
Orders []Detail
|
||||
RealisedPNL decimal.Decimal
|
||||
UnrealisedPNL decimal.Decimal
|
||||
Exposure decimal.Decimal
|
||||
LatestDirection Side
|
||||
Status Status
|
||||
OpeningDirection Side
|
||||
OpeningPrice decimal.Decimal
|
||||
LatestPrice decimal.Decimal
|
||||
PNLHistory []PNLResult
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ var (
|
||||
ErrAmountIsInvalid = errors.New("order amount is equal or less than zero")
|
||||
ErrPriceMustBeSetIfLimitOrder = errors.New("order price must be set if limit order type is desired")
|
||||
ErrOrderIDNotSet = errors.New("order id or client order id is not set")
|
||||
errCannotLiquidate = errors.New("cannot liquidate position")
|
||||
)
|
||||
|
||||
// Submit contains all properties of an order that may be required
|
||||
@@ -278,6 +279,7 @@ const (
|
||||
Closed
|
||||
Pending
|
||||
Cancelling
|
||||
Liquidated
|
||||
)
|
||||
|
||||
// Type enforces a standard for order types across the code base
|
||||
@@ -304,7 +306,7 @@ const (
|
||||
)
|
||||
|
||||
// Side enforces a standard for order sides across the code base
|
||||
type Side uint16
|
||||
type Side uint32
|
||||
|
||||
// Order side types
|
||||
const (
|
||||
@@ -316,11 +318,16 @@ const (
|
||||
AnySide
|
||||
Long
|
||||
Short
|
||||
ClosePosition
|
||||
// Backtester signal types
|
||||
DoNothing
|
||||
TransferredFunds
|
||||
CouldNotBuy
|
||||
CouldNotSell
|
||||
CouldNotShort
|
||||
CouldNotLong
|
||||
CouldNotCloseShort
|
||||
CouldNotCloseLong
|
||||
MissingData
|
||||
)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const (
|
||||
orderSubmissionValidSides = Buy | Sell | Bid | Ask | Long | Short
|
||||
shortSide = Short | Sell | Ask
|
||||
longSide = Long | Buy | Bid
|
||||
inactiveStatuses = Filled | Cancelled | InsufficientBalance | MarketUnavailable | Rejected | PartiallyCancelled | Expired | Closed | AnyStatus | Cancelling
|
||||
inactiveStatuses = Filled | Cancelled | InsufficientBalance | MarketUnavailable | Rejected | PartiallyCancelled | Expired | Closed | AnyStatus | Cancelling | Liquidated
|
||||
activeStatuses = Active | Open | PartiallyFilled | New | PendingCancel | Hidden | AutoDeleverage | Pending
|
||||
notPlaced = InsufficientBalance | MarketUnavailable | Rejected
|
||||
)
|
||||
@@ -375,7 +375,13 @@ func (d *Detail) IsActive() bool {
|
||||
func (d *Detail) IsInactive() bool {
|
||||
return d.Amount <= 0 ||
|
||||
d.Amount <= d.ExecutedAmount ||
|
||||
inactiveStatuses&d.Status == d.Status
|
||||
d.Status.IsInactive()
|
||||
}
|
||||
|
||||
// IsInactive returns true if the status indicates it is
|
||||
// currently not available on the exchange
|
||||
func (s Status) IsInactive() bool {
|
||||
return inactiveStatuses&s == s
|
||||
}
|
||||
|
||||
// WasOrderPlaced returns true if an order has a status that indicates that it
|
||||
@@ -636,6 +642,8 @@ func (s Side) String() string {
|
||||
return "SHORT"
|
||||
case AnySide:
|
||||
return "ANY"
|
||||
case ClosePosition:
|
||||
return "CLOSE POSITION"
|
||||
// Backtester signal types below.
|
||||
case DoNothing:
|
||||
return "DO NOTHING"
|
||||
@@ -645,6 +653,14 @@ func (s Side) String() string {
|
||||
return "COULD NOT BUY"
|
||||
case CouldNotSell:
|
||||
return "COULD NOT SELL"
|
||||
case CouldNotShort:
|
||||
return "COULD NOT SHORT"
|
||||
case CouldNotLong:
|
||||
return "COULD NOT LONG"
|
||||
case CouldNotCloseShort:
|
||||
return "COULD NOT CLOSE SHORT"
|
||||
case CouldNotCloseLong:
|
||||
return "COULD NOT CLOSE LONG"
|
||||
case MissingData:
|
||||
return "MISSING DATA"
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user