diff --git a/currency/pair_methods.go b/currency/pair_methods.go index fe50863c..69ae5d52 100644 --- a/currency/pair_methods.go +++ b/currency/pair_methods.go @@ -2,11 +2,15 @@ package currency import ( "encoding/json" + "errors" + "fmt" ) // EMPTYFORMAT defines an empty pair format var EMPTYFORMAT = PairFormat{} +var errCurrencyNotAssociatedWithPair = errors.New("currency not associated with pair") + // String returns a currency pair string func (p Pair) String() string { return p.Base.String() + p.Delimiter + p.Quote.String() @@ -140,7 +144,82 @@ func (p Pair) Other(c Code) (Code, error) { return EMPTYCODE, ErrCurrencyCodeEmpty } -// IsPopulated returns true if the currency pair have both non-empty values for base and quote. +// IsPopulated returns true if the currency pair have both non-empty values for +// base and quote. func (p Pair) IsPopulated() bool { return !p.Base.IsEmpty() && !p.Quote.IsEmpty() } + +// MarketSellOrderParameters returns order parameters for when you want to sell +// a currency which purchases another currency. This specifically returns what +// liquidity side you will be affecting, what order side you will be placing and +// what currency you will be purchasing. +func (p Pair) MarketSellOrderParameters(wantingToSell Code) (*OrderParameters, error) { + return p.getOrderParameters(wantingToSell, true, true) +} + +// MarketBuyOrderParameters returns order parameters for when you want to sell a +// currency which purchases another currency. This specifically returns what +// liquidity side you will be affecting, what order side you will be placing and +// what currency you will be purchasing. +func (p Pair) MarketBuyOrderParameters(wantingToBuy Code) (*OrderParameters, error) { + return p.getOrderParameters(wantingToBuy, false, true) +} + +// LimitSellOrderParameters returns order parameters for when you want to sell a +// currency which purchases another currency. This specifically returns what +// liquidity side you will be affecting, what order side you will be placing and +// what currency you will be purchasing. +func (p Pair) LimitSellOrderParameters(wantingToSell Code) (*OrderParameters, error) { + return p.getOrderParameters(wantingToSell, true, false) +} + +// LimitBuyOrderParameters returns order parameters for when you want to +// sell a currency which purchases another currency. This specifically returns +// what liquidity side you will be affecting, what order side you will be +// placing and what currency you will be purchasing. +func (p Pair) LimitBuyOrderParameters(wantingToBuy Code) (*OrderParameters, error) { + return p.getOrderParameters(wantingToBuy, false, false) +} + +// getOrderDecisionDetails returns order parameters for the currency pair using +// the provided currency code, whether or not you are selling and whether or not +// you are placing a market order. +func (p Pair) getOrderParameters(c Code, selling, market bool) (*OrderParameters, error) { + if !p.IsPopulated() { + return nil, ErrCurrencyPairEmpty + } + if c.IsEmpty() { + return nil, ErrCurrencyCodeEmpty + } + params := OrderParameters{} + switch { + case p.Base.Equal(c): + if selling { + params.SellingCurrency = p.Base + params.PurchasingCurrency = p.Quote + params.IsBuySide = false + params.IsAskLiquidity = !market + } else { + params.SellingCurrency = p.Quote + params.PurchasingCurrency = p.Base + params.IsBuySide = true + params.IsAskLiquidity = market + } + case p.Quote.Equal(c): + if selling { + params.SellingCurrency = p.Quote + params.PurchasingCurrency = p.Base + params.IsBuySide = true + params.IsAskLiquidity = market + } else { + params.SellingCurrency = p.Base + params.PurchasingCurrency = p.Quote + params.IsBuySide = false + params.IsAskLiquidity = !market + } + default: + return nil, fmt.Errorf("%w %v: %v", errCurrencyNotAssociatedWithPair, c, p) + } + return ¶ms, nil +} diff --git a/currency/pair_test.go b/currency/pair_test.go index fc1af247..bfabfaec 100644 --- a/currency/pair_test.go +++ b/currency/pair_test.go @@ -3,6 +3,7 @@ package currency import ( "encoding/json" "errors" + "fmt" "testing" ) @@ -961,3 +962,77 @@ func TestIsPopulated(t *testing.T) { t.Fatal("unexpected value") } } + +func TestGetOrderParameters(t *testing.T) { + t.Parallel() + + p := NewPair(BTC, USDT) + testCases := []struct { + Pair Pair + currency Code + market bool + selling bool + expectedParams *OrderParameters + expectedError error + }{ + {expectedError: ErrCurrencyPairEmpty}, + {Pair: p, expectedError: ErrCurrencyCodeEmpty}, + {Pair: p, currency: XRP, selling: true, market: true, expectedError: errCurrencyNotAssociatedWithPair}, + + {Pair: p, currency: BTC, selling: true, market: true, expectedParams: &OrderParameters{SellingCurrency: BTC, PurchasingCurrency: USDT, IsBuySide: false, IsAskLiquidity: false}}, + {Pair: p, currency: BTC, selling: false, market: true, expectedParams: &OrderParameters{SellingCurrency: USDT, PurchasingCurrency: BTC, IsBuySide: true, IsAskLiquidity: true}}, + {Pair: p, currency: BTC, selling: true, market: false, expectedParams: &OrderParameters{SellingCurrency: BTC, PurchasingCurrency: USDT, IsBuySide: false, IsAskLiquidity: true}}, + {Pair: p, currency: BTC, selling: false, market: false, expectedParams: &OrderParameters{SellingCurrency: USDT, PurchasingCurrency: BTC, IsBuySide: true, IsAskLiquidity: false}}, + + {Pair: p, currency: USDT, selling: true, market: true, expectedParams: &OrderParameters{SellingCurrency: USDT, PurchasingCurrency: BTC, IsBuySide: true, IsAskLiquidity: true}}, + {Pair: p, currency: USDT, selling: false, market: true, expectedParams: &OrderParameters{SellingCurrency: BTC, PurchasingCurrency: USDT, IsBuySide: false, IsAskLiquidity: false}}, + {Pair: p, currency: USDT, selling: true, market: false, expectedParams: &OrderParameters{SellingCurrency: USDT, PurchasingCurrency: BTC, IsBuySide: true, IsAskLiquidity: false}}, + {Pair: p, currency: USDT, selling: false, market: false, expectedParams: &OrderParameters{SellingCurrency: BTC, PurchasingCurrency: USDT, IsBuySide: false, IsAskLiquidity: true}}, + } + + for i, tc := range testCases { + tc := tc + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + t.Parallel() + var resp *OrderParameters + var err error + switch { + case tc.market && tc.selling: + resp, err = tc.Pair.MarketSellOrderParameters(tc.currency) + case tc.market && !tc.selling: + resp, err = tc.Pair.MarketBuyOrderParameters(tc.currency) + case !tc.market && tc.selling: + resp, err = tc.Pair.LimitSellOrderParameters(tc.currency) + case !tc.market && !tc.selling: + resp, err = tc.Pair.LimitBuyOrderParameters(tc.currency) + } + + if !errors.Is(err, tc.expectedError) { + t.Fatalf("received %v, expected %v", err, tc.expectedError) + } + + if tc.expectedParams == nil { + if resp != nil { + t.Fatalf("received %v, expected nil", resp) + } + return + } + + if resp.SellingCurrency != tc.expectedParams.SellingCurrency { + t.Fatalf("SellingCurrency received %v, expected %v", resp.SellingCurrency, tc.expectedParams.SellingCurrency) + } + + if resp.PurchasingCurrency != tc.expectedParams.PurchasingCurrency { + t.Fatalf("PurchasingCurrency received %v, expected %v", resp.PurchasingCurrency, tc.expectedParams.PurchasingCurrency) + } + + if resp.IsBuySide != tc.expectedParams.IsBuySide { + t.Fatalf("BuySide received %v, expected %v", resp.IsBuySide, tc.expectedParams.IsBuySide) + } + + if resp.IsAskLiquidity != tc.expectedParams.IsAskLiquidity { + t.Fatalf("AskLiquidity received %v, expected %v", resp.IsAskLiquidity, tc.expectedParams.IsAskLiquidity) + } + }) + } +} diff --git a/currency/pair_types.go b/currency/pair_types.go index a880bdf0..ec2c096d 100644 --- a/currency/pair_types.go +++ b/currency/pair_types.go @@ -17,3 +17,18 @@ type PairDifference struct { Remove Pairs FormatDifference bool } + +// OrderParameters is used to determine the order side, liquidity side and the +// selling & purchasing currency derived from the currency pair. +type OrderParameters struct { + // SellingCurrency is the currency that will be sold first + SellingCurrency Code + // Purchasing is the currency that will be purchased last + PurchasingCurrency Code + // IsBuySide is the side of the order that will be placed true for buy/long, + // false for sell/short. + IsBuySide bool + // IsAskLiquidity is the side of the orderbook that will be used, false for + // bid liquidity. + IsAskLiquidity bool +}