From 527184464ffdff943f0cd67186c4aa1a62313b5a Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Fri, 11 Oct 2024 16:10:56 +0530 Subject: [PATCH 1/6] feat(#111): mark visibility as public of some of component libraries --- geniusyield-orderbot.cabal | 2 ++ 1 file changed, 2 insertions(+) diff --git a/geniusyield-orderbot.cabal b/geniusyield-orderbot.cabal index deedf0c..d409668 100644 --- a/geniusyield-orderbot.cabal +++ b/geniusyield-orderbot.cabal @@ -111,6 +111,7 @@ library datasource-providers , geniusyield-orderbot-framework:common , geniusyield-dex-api exposed-modules: GeniusYield.OrderBot.DataSource.Providers + visibility: public library orderbook-list import: common-lang @@ -123,6 +124,7 @@ library orderbook-list , geniusyield-dex-api exposed-modules: GeniusYield.OrderBot.OrderBook.List + visibility: public library geniusyield-strategies import: common-lang From 4a3b385fd294c440362f9e70f691016da972bcb8 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Sun, 13 Oct 2024 21:04:17 +0530 Subject: [PATCH 2/6] feat(#111): add signature for strategies and give it's implementation --- .../geniusyield-orderbot-framework.cabal | 19 +++ .../lib-common/GeniusYield/OrderBot/Types.hs | 54 ++++++- .../GeniusYield/OrderBot/OrderBook.hsig | 30 +++- .../GeniusYield/OrderBot/OrderBook/Extra.hs | 24 ++++ .../GeniusYield/OrderBot/Strategies.hsig | 56 ++++++++ .../GeniusYield/OrderBot/MatchingStrategy.hs | 72 +--------- geniusyield-orderbot.cabal | 32 +++++ .../GeniusYield/OrderBot/OrderBook/List.hs | 29 +++- .../GeniusYield/OrderBot/Strategies/Impl.hs | 133 ++++++++++++++++++ 9 files changed, 371 insertions(+), 78 deletions(-) create mode 100644 geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook/Extra.hs create mode 100644 geniusyield-orderbot-framework/lib-strategies/GeniusYield/OrderBot/Strategies.hsig create mode 100644 impl/strategies-impl/GeniusYield/OrderBot/Strategies/Impl.hs diff --git a/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal b/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal index e986ba1..62bb7ab 100644 --- a/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal +++ b/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal @@ -136,6 +136,24 @@ library orderbook , geniusyield-dex-api signatures: GeniusYield.OrderBot.OrderBook + exposed-modules: + GeniusYield.OrderBot.OrderBook.Extra + +library strategies + import: common-lang + import: common-ghc-opts + visibility: public + hs-source-dirs: lib-strategies + build-depends: + , aeson + , atlas-cardano + , base + , envy + , geniusyield-orderbot-framework:common + , geniusyield-orderbot-framework:orderbook + , geniusyield-dex-api + signatures: + GeniusYield.OrderBot.Strategies -- Indefinite library exposing the OrderBot orchestration types and functions. library @@ -147,6 +165,7 @@ library , geniusyield-orderbot-framework:common , geniusyield-orderbot-framework:datasource , geniusyield-orderbot-framework:orderbook + , geniusyield-orderbot-framework:strategies , geniusyield-dex-api exposed-modules: GeniusYield.OrderBot diff --git a/geniusyield-orderbot-framework/lib-common/GeniusYield/OrderBot/Types.hs b/geniusyield-orderbot-framework/lib-common/GeniusYield/OrderBot/Types.hs index f333e7a..3fd8a58 100644 --- a/geniusyield-orderbot-framework/lib-common/GeniusYield/OrderBot/Types.hs +++ b/geniusyield-orderbot-framework/lib-common/GeniusYield/OrderBot/Types.hs @@ -20,15 +20,20 @@ module GeniusYield.OrderBot.Types , mkOrderAssetPair , equivalentAssetPair , mkEquivalentAssetPair + , FillType (..) + , MatchExecutionInfo (..) + , completeFill + , partialFill ) where import Data.Aeson (ToJSON, (.=)) import qualified Data.Aeson as Aeson import Data.Kind (Type) import Data.Ratio (denominator, numerator, (%)) +import Data.Text (Text) import Numeric.Natural (Natural) -import GeniusYield.Types.TxOutRef (GYTxOutRef) +import GeniusYield.Types.TxOutRef (GYTxOutRef, showTxOutRef) import GeniusYield.Types.Value (GYAssetClass (..)) import GeniusYield.Api.Dex.PartialOrder (PartialOrderInfo (..)) @@ -237,3 +242,50 @@ mkOrderType mkOrderType asked oap | commodityAsset oap == asked = BuyOrder | otherwise = SellOrder + +{- | "Fill" refers to the _volume_ of the order filled. Therefore, its unit is always the 'commodityAsset'. + +Of course, 'CompleteFill' just means the whole order is filled, whether it's buy or sell. + +'PartialFill' means slightly different things for the two order types. But the 'Natural' field within +always designates the 'commodityAsset'. + +For sell orders, `PartialFill n` indicates that n amount of commodity tokens will be sold from the order, +and the respective payment will be made in the currency asset. + +For buy orders, `PartialFill n` indicates that n amount of +commodity tokens should be bought, and the corresponding price (orderPrice * n), _floored_ if necessary, +must be paid by the order. + +**NOTE**: The 'n' in 'PartialFill n' must not be the max volume of the order. Use 'CompleteFill' in those scenarios. +-} +data FillType = CompleteFill | PartialFill Natural deriving stock (Eq, Show) + +data MatchExecutionInfo + = forall t. OrderExecutionInfo !FillType {-# UNPACK #-} !(OrderInfo t) + +instance ToJSON MatchExecutionInfo where + toJSON (OrderExecutionInfo fillT OrderInfo { orderRef, orderType, assetInfo + , volume + , price = Price {getPrice = x} + }) = + Aeson.object + [ "utxoRef" .= showTxOutRef orderRef + , "volumeMin" .= volumeMin volume + , "volumeMax" .= volumeMax volume + , "price" .= x + , "commodity" .= commodityAsset assetInfo + , "currency" .= currencyAsset assetInfo + , "type" .= prettySOrderType orderType + , "fillType" .= show fillT + ] + where + prettySOrderType :: SOrderType t -> Text + prettySOrderType SBuyOrder = "Buy" + prettySOrderType SSellOrder = "Sell" + +completeFill :: OrderInfo t -> MatchExecutionInfo +completeFill = OrderExecutionInfo CompleteFill + +partialFill :: OrderInfo t -> Natural -> MatchExecutionInfo +partialFill o n = OrderExecutionInfo (PartialFill n) o diff --git a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig index 5fe3ad4..ad90af6 100644 --- a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig +++ b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig @@ -27,12 +27,16 @@ signature GeniusYield.OrderBot.OrderBook ( -- * Order book construction populateOrderBook, buildOrderBookList, + emptyOrders, + unconsOrders, + insertOrder, -- * Order book queries lowestSell, highestBuy, withoutTip, foldlOrders, foldrOrders, + foldlMOrders, ordersLTPrice, ordersLTEPrice, ordersGTPrice, @@ -41,11 +45,12 @@ signature GeniusYield.OrderBot.OrderBook ( volumeLTEPrice, volumeGTPrice, volumeGTEPrice, + nullOrders, -- * MultiAssetOrderBook reading utilities withEachAsset ) where -import Prelude (IO) +import Prelude (Bool, IO, Maybe, Monad) import Data.Aeson (ToJSON) import Data.Kind (Type) @@ -129,6 +134,15 @@ buildOrderBookList -- Components +-- | An empty 'Orders' data structure. +emptyOrders :: Orders t + +-- | If the 'Orders' data structure is empty, return 'Nothing', else return the tip and the rest. +unconsOrders :: Orders t -> Maybe (OrderInfo t, Orders t) + +-- | Insert an order into the 'Orders' data structure. +insertOrder :: OrderInfo t -> Orders t -> Orders t + buyOrders :: OrderBook -> Orders 'BuyOrder sellOrders :: OrderBook -> Orders 'SellOrder @@ -145,13 +159,14 @@ withoutTip :: Orders t -> Orders t -- Folds +-- TODO: Document that it should be strict in accumulator. {- | Left associative fold over the 'Orders' data structure. The order in which each 'OrderInfo' is passed onto the function, depends on the type of 'Orders'. -For sell orders, it should act like a 'foldr' on a list with _ascending_ orders based on price. -For buy orders, it should act like a 'foldr' on a list with _descending_ orders based on price. +For sell orders, it should act like a 'foldl' on a list with _ascending_ orders based on price. +For buy orders, it should act like a 'foldl' on a list with _descending_ orders based on price. -} foldlOrders :: forall a t. (a -> OrderInfo t -> a) -> a -> Orders t -> a @@ -160,11 +175,14 @@ foldlOrders :: forall a t. (a -> OrderInfo t -> a) -> a -> Orders t -> a The order in which each 'OrderInfo' is passed onto the function, depends on the type of 'Orders'. -For sell orders, it should act like a 'foldl' on a list with _ascending_ orders based on price. -For buy orders, it should act like a 'foldl' on a list with _descending_ orders based on price. +For sell orders, it should act like a 'foldr' on a list with _ascending_ orders based on price. +For buy orders, it should act like a 'foldr' on a list with _descending_ orders based on price. -} foldrOrders :: forall a t. (OrderInfo t -> a -> a) -> a -> Orders t -> a +-- | @foldlM@ variant for 'Orders', you should almost always be using @foldlMOrders'@ instead. +foldlMOrders :: forall a t m. Monad m => (a -> OrderInfo t -> m a) -> a -> Orders t -> m a + -- Price queries ordersLTPrice :: Price -> Orders t -> Orders t @@ -185,6 +203,8 @@ volumeGTPrice :: Price -> Orders t -> Volume volumeGTEPrice :: Price -> Orders t -> Volume +nullOrders :: Orders t -> Bool + ------------------------------------------------------------------------------- -- MultiAssetOrderBook reading utilities ------------------------------------------------------------------------------- diff --git a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook/Extra.hs b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook/Extra.hs new file mode 100644 index 0000000..e4ac678 --- /dev/null +++ b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook/Extra.hs @@ -0,0 +1,24 @@ +{-| +Module : GeniusYield.OrderBot.OrderBook.Extra +Synopsis : Extra utilities when working with order books. +Copyright : (c) 2023 GYELD GMBH +License : Apache 2.0 +Maintainer : support@geniusyield.co +Stability : develop +-} +module GeniusYield.OrderBot.OrderBook.Extra ( + foldlMOrders', + mapMOrders_, +) where + +import Prelude (Monad, (*>), pure) +import GeniusYield.OrderBot.Types (OrderInfo) +import GeniusYield.OrderBot.OrderBook + +-- | @foldlM'@ variant for 'Orders' which is strict in accumulator. +foldlMOrders' :: forall a t m. Monad m => (a -> OrderInfo t -> m a) -> a -> Orders t -> m a +foldlMOrders' f = foldlMOrders (\(!acc) -> f acc) + +-- | @mapM_@ variant for 'Orders'. +mapMOrders_ :: forall a t m. Monad m => (OrderInfo t -> m a) -> Orders t -> m () +mapMOrders_ f os = foldlMOrders' (\_ oi -> f oi *> pure ()) () os \ No newline at end of file diff --git a/geniusyield-orderbot-framework/lib-strategies/GeniusYield/OrderBot/Strategies.hsig b/geniusyield-orderbot-framework/lib-strategies/GeniusYield/OrderBot/Strategies.hsig new file mode 100644 index 0000000..8432d63 --- /dev/null +++ b/geniusyield-orderbot-framework/lib-strategies/GeniusYield/OrderBot/Strategies.hsig @@ -0,0 +1,56 @@ +{-| +Module : GeniusYield.OrderBot.Strategies +Synopsis : The strategies for matching orders on a DEX. +Copyright : (c) 2023 GYELD GMBH +License : Apache 2.0 +Maintainer : support@geniusyield.co +Stability : develop + +-} +signature GeniusYield.OrderBot.Strategies ( + BotStrategy, + allStrategies, + MatchResult, + IndependentStrategy, + mkIndependentStrategy, +) where + +import Data.Aeson (ToJSON, FromJSON) +import System.Envy (Var) +import GeniusYield.OrderBot.Types (OrderAssetPair, MatchExecutionInfo) +import GeniusYield.OrderBot.OrderBook (OrderBook) +import GHC.Natural (Natural) + +-- | Every bot strategy must be named here. +data BotStrategy + +instance ToJSON BotStrategy +instance FromJSON BotStrategy +instance Var BotStrategy + +{- | A list containing all implemented strategies. This list is used for the + tests and for the error message during env variable parsing. +-} +allStrategies :: [BotStrategy] + +{- | The result of order matching - should contain information to perform execute order and LP transactions. + +Essentially, all orders (and pool swaps) in a list of 'MatchExecutionInfo's are matched with each other. + +All of their tokens are put into one big transaction bucket, which is then auto balanced to pay each other. +Any extra tokens are returned to the bot wallet - this is known as arbitrage profit. +-} +type MatchResult = [MatchExecutionInfo] + +{- | A matching strategy has access to the 'OrderBook' for a single asset pair, +alongside all its relevant query functions. It must produce a 'MatchResult' which +has information on how to execute the order matching transaction. +-} +type IndependentStrategy = (OrderAssetPair -> OrderBook -> [MatchResult]) + +{- | Given a bot strategy and a max amount of orders per transaction, creates + an independent strategy. +-} +mkIndependentStrategy :: BotStrategy -> Natural -> IndependentStrategy + + diff --git a/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/MatchingStrategy.hs b/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/MatchingStrategy.hs index c53eece..4e2c03a 100644 --- a/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/MatchingStrategy.hs +++ b/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/MatchingStrategy.hs @@ -17,84 +17,18 @@ module GeniusYield.OrderBot.MatchingStrategy , matchExecutionInfoUtxoRef ) where -import Data.Aeson (ToJSON (toJSON), (.=)) -import qualified Data.Aeson as Aeson import Data.Maybe (fromJust) -import Data.Text (Text) -import Numeric.Natural (Natural) - import GeniusYield.Api.Dex.PartialOrder (PORefs, PartialOrderInfo (poiOfferedAmount), fillMultiplePartialOrders') import GeniusYield.Api.Dex.Types (GYDexApiMonad) -import GeniusYield.OrderBot.OrderBook (OrderBook) +import GeniusYield.OrderBot.Strategies (IndependentStrategy, + MatchResult) import GeniusYield.OrderBot.Types import GeniusYield.TxBuilder (GYTxSkeleton) import GeniusYield.Types.PlutusVersion (PlutusVersion (PlutusV2)) -import GeniusYield.Types.TxOutRef (GYTxOutRef, showTxOutRef) - -{- | A matching strategy has access to the 'OrderBook' for a single asset pair, -alongside all its relevant query functions. It must produce a 'MatchResult' which -has information on how to execute the order matching transaction. --} -type IndependentStrategy = (OrderAssetPair -> OrderBook -> [MatchResult]) - -data MatchExecutionInfo - = forall t. OrderExecutionInfo !FillType {-# UNPACK #-} !(OrderInfo t) - --- Smart Constructors -completeFill :: OrderInfo t -> MatchExecutionInfo -completeFill = OrderExecutionInfo CompleteFill - -partialFill :: OrderInfo t -> Natural -> MatchExecutionInfo -partialFill o n = OrderExecutionInfo (PartialFill n) o - -instance ToJSON MatchExecutionInfo where - toJSON (OrderExecutionInfo fillT OrderInfo { orderRef, orderType, assetInfo - , volume - , price = Price {getPrice = x} - }) = - Aeson.object - [ "utxoRef" .= showTxOutRef orderRef - , "volumeMin" .= volumeMin volume - , "volumeMax" .= volumeMax volume - , "price" .= x - , "commodity" .= commodityAsset assetInfo - , "currency" .= currencyAsset assetInfo - , "type" .= prettySOrderType orderType - , "fillType" .= show fillT - ] - where - prettySOrderType :: SOrderType t -> Text - prettySOrderType SBuyOrder = "Buy" - prettySOrderType SSellOrder = "Sell" - -{- | The result of order matching - should contain information to perform execute order and LP transactions. - -Essentially, all orders (and pool swaps) in a list of 'MatchExecutionInfo's are matched with each other. +import GeniusYield.Types.TxOutRef (GYTxOutRef) -All of their tokens are put into one big transaction bucket, which is then auto balanced to pay each other. -Any extra tokens are returned to the bot wallet - this is known as arbitrage profit. --} -type MatchResult = [MatchExecutionInfo] - -{- | "Fill" refers to the _volume_ of the order filled. Therefore, its unit is always the 'commodityAsset'. - -Of course, 'CompleteFill' just means the whole order is filled, whether it's buy or sell. - -'PartialFill' means slightly different things for the two order types. But the 'Natural' field within -always designates the 'commodityAsset'. - -For sell orders, `PartialFill n` indicates that n amount of commodity tokens will be sold from the order, -and the respective payment will be made in the currency asset. - -For buy orders, `PartialFill n` indicates that n amount of -commodity tokens should be bought, and the corresponding price (orderPrice * n), _floored_ if necessary, -must be paid by the order. - -**NOTE**: The 'n' in 'PartialFill n' must not be the max volume of the order. Use 'CompleteFill' in those scenarios. --} -data FillType = CompleteFill | PartialFill Natural deriving stock (Eq, Show) executionSkeleton :: GYDexApiMonad m a diff --git a/geniusyield-orderbot.cabal b/geniusyield-orderbot.cabal index d409668..ad2004c 100644 --- a/geniusyield-orderbot.cabal +++ b/geniusyield-orderbot.cabal @@ -126,6 +126,20 @@ library orderbook-list GeniusYield.OrderBot.OrderBook.List visibility: public +library strategies-impl + import: common-lang + import: common-deps + import: common-ghc-opts + hs-source-dirs: impl/strategies-impl + build-depends: + , envy + , geniusyield-orderbot-framework:common + , geniusyield-orderbot-framework:orderbook + , geniusyield-dex-api + exposed-modules: + GeniusYield.OrderBot.Strategies.Impl + visibility: public + library geniusyield-strategies import: common-lang import: common-deps @@ -136,6 +150,7 @@ library geniusyield-strategies , geniusyield-orderbot-framework:common , geniusyield-orderbot:datasource-providers , geniusyield-orderbot:orderbook-list + , geniusyield-orderbot:strategies-impl , geniusyield-dex-api , envy , cardano-api @@ -145,6 +160,11 @@ library geniusyield-strategies , geniusyield-orderbot-framework requires ( GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers , GeniusYield.OrderBot.OrderBook as GeniusYield.OrderBot.OrderBook.List + , GeniusYield.OrderBot.Strategies as GeniusYield.OrderBot.Strategies.Impl + ) + , geniusyield-orderbot:strategies-impl requires + ( GeniusYield.OrderBot.OrderBook as GeniusYield.OrderBot.OrderBook.List + , GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers ) exposed-modules: Strategies @@ -170,6 +190,7 @@ executable geniusyield-orderbot-exe , geniusyield-orderbot-framework:common , geniusyield-orderbot:datasource-providers , geniusyield-orderbot:orderbook-list + , geniusyield-orderbot:strategies-impl , geniusyield-orderbot:geniusyield-strategies , geniusyield-dex-api , plutus-ledger-api @@ -180,6 +201,11 @@ executable geniusyield-orderbot-exe , geniusyield-orderbot-framework requires ( GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers , GeniusYield.OrderBot.OrderBook as GeniusYield.OrderBot.OrderBook.List + , GeniusYield.OrderBot.Strategies as GeniusYield.OrderBot.Strategies.Impl + ) + , geniusyield-orderbot:strategies-impl requires + ( GeniusYield.OrderBot.OrderBook as GeniusYield.OrderBot.OrderBook.List + , GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers ) ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N @@ -202,6 +228,7 @@ test-suite strategies-tests , geniusyield-orderbot-framework:common , geniusyield-orderbot:datasource-providers , geniusyield-orderbot:orderbook-list + , geniusyield-orderbot:strategies-impl , geniusyield-orderbot:geniusyield-strategies , geniusyield-dex-api , QuickCheck @@ -213,4 +240,9 @@ test-suite strategies-tests , geniusyield-orderbot-framework requires ( GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers , GeniusYield.OrderBot.OrderBook as GeniusYield.OrderBot.OrderBook.List + , GeniusYield.OrderBot.Strategies as GeniusYield.OrderBot.Strategies.Impl + ) + , geniusyield-orderbot:strategies-impl requires + ( GeniusYield.OrderBot.OrderBook as GeniusYield.OrderBot.OrderBook.List + , GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers ) diff --git a/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs b/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs index c09e4a1..bc0d573 100644 --- a/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs +++ b/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs @@ -19,6 +19,9 @@ module GeniusYield.OrderBot.OrderBook.List ( -- * Order book construction populateOrderBook, buildOrderBookList, + emptyOrders, + unconsOrders, + insertOrder, -- * Order book queries lowestSell, @@ -26,6 +29,7 @@ module GeniusYield.OrderBot.OrderBook.List ( withoutTip, foldlOrders, foldrOrders, + foldlMOrders, ordersLTPrice, ordersLTEPrice, ordersGTPrice, @@ -34,14 +38,14 @@ module GeniusYield.OrderBot.OrderBook.List ( volumeLTEPrice, volumeGTPrice, volumeGTEPrice, - + nullOrders, -- * MultiAssetOrderBook reading utilities withEachAsset, ) where import Data.Aeson (ToJSON, object, toJSON) -import Data.Foldable (foldl') -import Data.List (sortOn) +import Data.Foldable (foldl', foldlM) +import Data.List (insertBy, sortOn) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Ord (Down (Down)) @@ -96,6 +100,19 @@ buildOrderBookList acc (# oap, buyOrders, sellOrders #) = (oap, OrderBook (Orders $ sortOn price sellOrders) (Orders $ sortOn (Down . price) buyOrders)) : acc +emptyOrders :: Orders t +emptyOrders = Orders [] + +unconsOrders :: Orders t -> Maybe (OrderInfo t, Orders t) +unconsOrders (Orders []) = Nothing +unconsOrders (Orders (x : xs)) = Just (x, Orders xs) + +insertOrder :: OrderInfo t -> Orders t -> Orders t +insertOrder oi (Orders os) = Orders $ + case orderType oi of + SBuyOrder -> insertBy (\oadd opresent -> compare (price opresent) (price oadd)) oi os + SSellOrder -> insertBy (\oadd opresent -> compare (price oadd) (price opresent)) oi os + lowestSell :: Orders 'SellOrder -> OrderInfo 'SellOrder lowestSell = head . unOrders @@ -108,9 +125,15 @@ withoutTip = Orders . drop 1 . unOrders foldlOrders :: forall a t. (a -> OrderInfo t -> a) -> a -> Orders t -> a foldlOrders f e = foldl' f e . unOrders +foldlMOrders :: forall a t m. Monad m => (a -> OrderInfo t -> m a) -> a -> Orders t -> m a +foldlMOrders f e = foldlM f e . unOrders + foldrOrders :: forall a t. (OrderInfo t -> a -> a) -> a -> Orders t -> a foldrOrders f e = foldr f e . unOrders +nullOrders :: Orders t -> Bool +nullOrders = null . unOrders + ordersLTPrice :: Price -> Orders t -> Orders t ordersLTPrice maxPrice = Orders . filter (\oi -> price oi < maxPrice) . unOrders diff --git a/impl/strategies-impl/GeniusYield/OrderBot/Strategies/Impl.hs b/impl/strategies-impl/GeniusYield/OrderBot/Strategies/Impl.hs new file mode 100644 index 0000000..add4880 --- /dev/null +++ b/impl/strategies-impl/GeniusYield/OrderBot/Strategies/Impl.hs @@ -0,0 +1,133 @@ +{-# LANGUAGE MultiWayIf #-} +{-| +Module : GeniusYield.OrderBot.Strategies.Impl +Copyright : (c) 2023 GYELD GMBH +License : Apache 2.0 +Maintainer : support@geniusyield.co +Stability : develop + +-} +module GeniusYield.OrderBot.Strategies.Impl ( + BotStrategy (..), + allStrategies, + MatchResult, + IndependentStrategy, + mkIndependentStrategy, +) where + +import Control.Monad.State.Strict (State, execState, modify') +import Data.Text (Text) +import Data.Aeson.Types (Parser) +import Data.Aeson +import GeniusYield.OrderBot.Types +import GeniusYield.OrderBot.OrderBook +import GeniusYield.OrderBot.OrderBook.Extra +import Data.Data (Typeable) +import GHC.Generics (Generic) +import GHC.Natural (Natural) +import System.Envy (Var (..)) + +data BotStrategy = OneSellToManyBuy + deriving stock (Show, Eq, Generic) + deriving anyclass (ToJSON, Typeable) + +instance FromJSON BotStrategy where + parseJSON = withText "BotStrategy" parse + where + parse :: Text -> Parser BotStrategy + parse "OneSellToManyBuy" = return OneSellToManyBuy + parse _ = fail "Undefined strategy name" + +instance Var BotStrategy where + fromVar s = case s of + "OneSellToManyBuy" -> Just OneSellToManyBuy + _ -> Nothing + toVar = show + +allStrategies :: [BotStrategy] +allStrategies = [OneSellToManyBuy] + +type MatchResult = [MatchExecutionInfo] + +type IndependentStrategy = (OrderAssetPair -> OrderBook -> [MatchResult]) + +mkIndependentStrategy :: BotStrategy -> Natural -> IndependentStrategy +mkIndependentStrategy bs maxOrders _ bk = + case bs of + OneSellToManyBuy -> oneSellToManyBuy maxOrders bk + +-- | Strategy state containing the matchings found and the remaining buy orders. +data StrategyState = StrategyState + { matchResults :: ![MatchResult] + , remainingOrders :: !(Orders 'BuyOrder) + } + +-- | Utility function for updating the state, after one run of the strategy. +updateStrategyState + :: MatchResult + -> Orders 'BuyOrder + -> StrategyState + -> StrategyState +updateStrategyState [] bos' ss = ss { remainingOrders = bos' } +updateStrategyState mr' bos' StrategyState { matchResults = mr } = + StrategyState { matchResults = mr ++ [mr'] + , remainingOrders = bos' + } + +{- | Strategy matching: Picking one sell order and matching it with many (up to + `maxOrders`) buy orders. +-} +oneSellToManyBuy :: Natural -> OrderBook -> [MatchResult] +oneSellToManyBuy maxOrders ob = + matchResults + $ execState (mapMOrders_ go $ sellOrders ob) + $ StrategyState {matchResults = [], remainingOrders = buyOrders ob} + where + go :: OrderInfo 'SellOrder + -> State StrategyState () + go order = modify' $ + \st -> uncurry updateStrategyState + (multiFill (maxOrders - 1) (<=) order (remainingOrders st)) st + +-- | General matching orders function. +multiFill + :: forall b b' + . Natural + -> (Price -> Price -> Bool) + -> OrderInfo b + -> Orders b' + -> (MatchResult, Orders b') +multiFill maxOrders checkPrices order = go (maxOrders - 1) vh + where + (Volume vl vh) = volume order + checkPrice = checkPrices $ price order + + go :: Natural -> Natural -> Orders b' -> (MatchResult, Orders b') + go _ 0 os = ([completeFill order], os) + go 0 v os + | (vh - v) >= vl = ([partialFill order (vh - v)], os) + | otherwise = ([], os) + go limitO remVol os' = + case unconsOrders os' of + Nothing -> + if | (vh - remVol) > vl -> ([partialFill order (vh - remVol)], emptyOrders) + | otherwise -> ([], emptyOrders) + Just (o, os) -> + if | remVol == maxFillX && checkPrice xP -> + let !b = completeFill o + in ([completeFill order, b], os) + | remVol > maxFillX && remVol >= minFillX && checkPrice xP -> + case go (limitO - 1) (remVol - maxFillX) os of + ([], _) -> updateRemaining o $ go limitO remVol os + (bs, s) -> (completeFill o : bs, s) + | remVol < maxFillX + && remVol >= minFillX + && checkPrice xP -> + ([completeFill order, partialFill o remVol], os) + | otherwise -> updateRemaining o $ go limitO remVol os + where + xP = price o + (Volume minFillX maxFillX) = volume o + + updateRemaining x (a, b) = (a, insertOrder x b) + From a7a29caa34afe762f7f268af9c4b9627dd990f1a Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Mon, 14 Oct 2024 12:45:08 +0530 Subject: [PATCH 3/6] feat(#111): move logic for order bot config, executable in framework --- README.md | 16 +- .../geniusyield-orderbot-framework.cabal | 5 + .../GeniusYield/OrderBot/Strategies.hsig | 2 + .../GeniusYield/OrderBot}/OrderBotConfig.hs | 6 +- .../src/GeniusYield/OrderBot/Run.hs | 49 ++++++ geniusyield-orderbot.cabal | 36 ----- geniusyield-orderbot/src/Main.hs | 38 +---- geniusyield-orderbot/src/Strategies.hs | 139 ------------------ geniusyield-orderbot/test/Main.hs | 3 +- 9 files changed, 70 insertions(+), 224 deletions(-) rename {geniusyield-orderbot/src => geniusyield-orderbot-framework/src/GeniusYield/OrderBot}/OrderBotConfig.hs (97%) create mode 100644 geniusyield-orderbot-framework/src/GeniusYield/OrderBot/Run.hs delete mode 100644 geniusyield-orderbot/src/Strategies.hs diff --git a/README.md b/README.md index 66d1528..1242dfe 100644 --- a/README.md +++ b/README.md @@ -423,8 +423,8 @@ For running the tests we can just simply execute `make orderbot-tests`. The SOR is organized into 5 main folders: - [`geniusyield-orderbot-framework`](./geniusyield-orderbot-framework), implement the main abstract tools for the SOR. -- [`geniusyield-orderbot`](./geniusyield-orderbot), the executable is implemented here, together with the strategies. -- [`impl`](./impl), specific implementations of the orderbook and data-provider. +- [`impl`](./impl), specific implementations of the orderbook, data-provider and strategies. +- [`geniusyield-orderbot`](./geniusyield-orderbot), simply runs the executable. ### Backpack @@ -439,8 +439,8 @@ To get started with Backpack, please see the following example: [A really small ## Strategies -On the [`Strategies`](./geniusyield-orderbot/src/Strategies.hs) module, you can find all the strategies -implemented by the SOR. Currently, there is only one called [`OneSellToManyBuy`](./geniusyield-orderbot/src/Strategies.hs#L36C20-L36C36), +On the [`GeniusYield.OrderBot.Strategies.Impl`](./impl/strategies-impl/GeniusYield/OrderBot/Strategies/Impl.hs) module, you can find all the strategies +implemented by the SOR. Currently, there is only one called `OneSellToManyBuy`, which basically takes the best sell order (the one with the lowest price) and searches for many buy orders (starting from the one with the highest price), ideally buying the total amount of offered tokens, or until it reaches the maxOrderMatches. @@ -464,7 +464,7 @@ data BotStrategy = OneSellToManyBuy ``` We must adjust some straightforward instances with the new constructor: `FromJSON` and `Var`. -As is the case with [`mkIndependentStrategy`](./geniusyield-orderbot/src/Strategies.hs#L56-L59), +As is the case with `mkIndependentStrategy`, adding a new particular case for `OneBuyToManySell` ```haskell @@ -484,7 +484,7 @@ oneBuyToManySell :: Natural -> OrderBook -> [MatchResult] oneBuyToManySell _ _ = [] ``` -Even more! We can add the new constructor `OneBuyToManySell` to the `allStrategies` [list](https://github.com/geniusyield/smart-order-router/blob/75aeeb733ea2c747595e2b231460601d80ed2866/geniusyield-orderbot/src/Strategies.hs#L58) +Even more! We can add the new constructor `OneBuyToManySell` to the `allStrategies` list and this should be enough to start testing with our custom strategy by running the tests. ```haskell @@ -497,8 +497,8 @@ Finishing the dummy implementation of `oneBuyToManySell` with the actual logic i
Hint -> Checking [`multiFill`](./geniusyield-orderbot/src/Strategies.hs#L95-L132), - can help to realize that it's enough to use [`oneSellToManyBuy`](./geniusyield-orderbot/src/Strategies.hs#L82-L92) +> Checking `multiFill`, + can help to realize that it's enough to use [`oneSellToManyBuy`] as inspiration and "flip" something.
diff --git a/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal b/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal index 62bb7ab..7adee9a 100644 --- a/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal +++ b/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal @@ -162,13 +162,18 @@ library import: common-ghc-opts hs-source-dirs: src build-depends: + , cardano-api + , envy , geniusyield-orderbot-framework:common , geniusyield-orderbot-framework:datasource , geniusyield-orderbot-framework:orderbook , geniusyield-orderbot-framework:strategies , geniusyield-dex-api + , vector exposed-modules: GeniusYield.OrderBot GeniusYield.OrderBot.MatchingStrategy + GeniusYield.OrderBot.OrderBotConfig + GeniusYield.OrderBot.Run ghc-options: -O2 diff --git a/geniusyield-orderbot-framework/lib-strategies/GeniusYield/OrderBot/Strategies.hsig b/geniusyield-orderbot-framework/lib-strategies/GeniusYield/OrderBot/Strategies.hsig index 8432d63..27a37a2 100644 --- a/geniusyield-orderbot-framework/lib-strategies/GeniusYield/OrderBot/Strategies.hsig +++ b/geniusyield-orderbot-framework/lib-strategies/GeniusYield/OrderBot/Strategies.hsig @@ -27,6 +27,8 @@ data BotStrategy instance ToJSON BotStrategy instance FromJSON BotStrategy instance Var BotStrategy +instance Show BotStrategy +instance Eq BotStrategy {- | A list containing all implemented strategies. This list is used for the tests and for the error message during env variable parsing. diff --git a/geniusyield-orderbot/src/OrderBotConfig.hs b/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/OrderBotConfig.hs similarity index 97% rename from geniusyield-orderbot/src/OrderBotConfig.hs rename to geniusyield-orderbot-framework/src/GeniusYield/OrderBot/OrderBotConfig.hs index b7b4abd..fafc5c0 100644 --- a/geniusyield-orderbot/src/OrderBotConfig.hs +++ b/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/OrderBotConfig.hs @@ -1,12 +1,12 @@ {-| -Module : OrderBotConfig +Module : GeniusYield.OrderBot.OrderBotConfig Copyright : (c) 2023 GYELD GMBH License : Apache 2.0 Maintainer : support@geniusyield.co Stability : develop -} -module OrderBotConfig where +module GeniusYield.OrderBot.OrderBotConfig where import Control.Exception ( throwIO ) import Control.Monad ( (<=<) ) @@ -41,7 +41,7 @@ import Cardano.Api ( AsType (AsSigningKey, AsPaymentKey) , deserialiseFromTextEnvelope ) -import Strategies ( BotStrategy(..), allStrategies, mkIndependentStrategy ) +import GeniusYield.OrderBot.Strategies ( BotStrategy, allStrategies, mkIndependentStrategy ) -- | Order bot vanilla config. data OrderBotConfig = diff --git a/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/Run.hs b/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/Run.hs new file mode 100644 index 0000000..61ff445 --- /dev/null +++ b/geniusyield-orderbot-framework/src/GeniusYield/OrderBot/Run.hs @@ -0,0 +1,49 @@ +{-| +Module : GeniusYield.OrderBot.Run +Copyright : (c) 2023 GYELD GMBH +License : Apache 2.0 +Maintainer : support@geniusyield.co +Stability : develop + +-} +module GeniusYield.OrderBot.Run ( run ) where + +import Control.Exception (throwIO) +import GeniusYield.Api.Dex.Constants (dexInfoDefaultMainnet, + dexInfoDefaultPreprod) +import GeniusYield.GYConfig +import GeniusYield.OrderBot (runOrderBot) +import GeniusYield.Types (GYNetworkId (..)) +import GeniusYield.OrderBot.OrderBotConfig (buildOrderBot, readBotConfig) +import System.Environment (getArgs) + +parseArgs :: IO (String, FilePath, Maybe FilePath) +parseArgs = do + args <- getArgs + case args of + [action, providerConfigFile, botConfigFile] -> return ( action + , providerConfigFile + , Just botConfigFile + ) + [action, providerConfigFile] -> return (action, providerConfigFile, Nothing) + _ -> throwIO . userError $ unlines + [ "Expected two or three command line arguments, in order:" + , "\t1. Action to execute: 'run'" + , "\t2. Path to the Atlas provider configuration file" + , "\t3. Path to the OrderBot config-file (only when reading config from file)" + ] + +run :: IO () +run = do + (action, pConfFile,obConfFile) <- parseArgs + obc <- readBotConfig obConfFile + cfg <- coreConfigIO pConfFile + di <- + case cfgNetworkId cfg of + GYTestnetPreprod -> pure dexInfoDefaultPreprod + GYMainnet -> pure dexInfoDefaultMainnet + _ -> throwIO $ userError "Only Preprod and Mainnet are supported." + ob <- buildOrderBot obc + case action of + "run" -> runOrderBot cfg di ob + _ -> throwIO . userError $ unwords ["Action: ", show action, " not supported."] diff --git a/geniusyield-orderbot.cabal b/geniusyield-orderbot.cabal index ad2004c..8cf172d 100644 --- a/geniusyield-orderbot.cabal +++ b/geniusyield-orderbot.cabal @@ -140,37 +140,6 @@ library strategies-impl GeniusYield.OrderBot.Strategies.Impl visibility: public -library geniusyield-strategies - import: common-lang - import: common-deps - import: common-ghc-opts - hs-source-dirs: geniusyield-orderbot/src - build-depends: - , geniusyield-orderbot-framework - , geniusyield-orderbot-framework:common - , geniusyield-orderbot:datasource-providers - , geniusyield-orderbot:orderbook-list - , geniusyield-orderbot:strategies-impl - , geniusyield-dex-api - , envy - , cardano-api - mixins: - , geniusyield-orderbot:orderbook-list requires - ( GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers ) - , geniusyield-orderbot-framework requires - ( GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers - , GeniusYield.OrderBot.OrderBook as GeniusYield.OrderBot.OrderBook.List - , GeniusYield.OrderBot.Strategies as GeniusYield.OrderBot.Strategies.Impl - ) - , geniusyield-orderbot:strategies-impl requires - ( GeniusYield.OrderBot.OrderBook as GeniusYield.OrderBot.OrderBook.List - , GeniusYield.OrderBot.DataSource as GeniusYield.OrderBot.DataSource.Providers - ) - exposed-modules: - Strategies - ghc-options: - -O2 - -- The primary orderbot executable - this must be instantiated with the signature -- implementations. @@ -180,9 +149,6 @@ executable geniusyield-orderbot-exe import: common-ghc-opts hs-source-dirs: geniusyield-orderbot/src main-is: Main.hs - other-modules: - OrderBotConfig - Strategies build-depends: , cardano-api , envy @@ -191,7 +157,6 @@ executable geniusyield-orderbot-exe , geniusyield-orderbot:datasource-providers , geniusyield-orderbot:orderbook-list , geniusyield-orderbot:strategies-impl - , geniusyield-orderbot:geniusyield-strategies , geniusyield-dex-api , plutus-ledger-api , ply-core @@ -229,7 +194,6 @@ test-suite strategies-tests , geniusyield-orderbot:datasource-providers , geniusyield-orderbot:orderbook-list , geniusyield-orderbot:strategies-impl - , geniusyield-orderbot:geniusyield-strategies , geniusyield-dex-api , QuickCheck , tasty diff --git a/geniusyield-orderbot/src/Main.hs b/geniusyield-orderbot/src/Main.hs index 91b04b2..92a178d 100644 --- a/geniusyield-orderbot/src/Main.hs +++ b/geniusyield-orderbot/src/Main.hs @@ -8,42 +8,8 @@ Stability : develop -} module Main ( main ) where -import Control.Exception (throwIO) -import GeniusYield.Api.Dex.Constants (dexInfoDefaultMainnet, - dexInfoDefaultPreprod) -import GeniusYield.GYConfig -import GeniusYield.OrderBot (runOrderBot) -import GeniusYield.Types (GYNetworkId (..)) -import OrderBotConfig (buildOrderBot, readBotConfig) -import System.Environment (getArgs) +import GeniusYield.OrderBot.Run (run) -parseArgs :: IO (String, FilePath, Maybe FilePath) -parseArgs = do - args <- getArgs - case args of - [action, providerConfigFile, botConfigFile] -> return ( action - , providerConfigFile - , Just botConfigFile - ) - [action, providerConfigFile] -> return (action, providerConfigFile, Nothing) - _ -> throwIO . userError $ unlines - [ "Expected two or three command line arguments, in order:" - , "\t1. Action to execute: 'run'" - , "\t2. Path to the Atlas provider configuration file" - , "\t3. Path to the OrderBot config-file (only when reading config from file)" - ] main :: IO () -main = do - (action, pConfFile,obConfFile) <- parseArgs - obc <- readBotConfig obConfFile - cfg <- coreConfigIO pConfFile - di <- - case cfgNetworkId cfg of - GYTestnetPreprod -> pure dexInfoDefaultPreprod - GYMainnet -> pure dexInfoDefaultMainnet - _ -> throwIO $ userError "Only Preprod and Mainnet are supported." - ob <- buildOrderBot obc - case action of - "run" -> runOrderBot cfg di ob - _ -> throwIO . userError $ unwords ["Action: ", show action, " not supported."] +main = run diff --git a/geniusyield-orderbot/src/Strategies.hs b/geniusyield-orderbot/src/Strategies.hs deleted file mode 100644 index e880ae0..0000000 --- a/geniusyield-orderbot/src/Strategies.hs +++ /dev/null @@ -1,139 +0,0 @@ -{-| -Module : Strategies -Copyright : (c) 2023 GYELD GMBH -License : Apache 2.0 -Maintainer : support@geniusyield.co -Stability : develop - --} -module Strategies - ( BotStrategy(..) - , allStrategies - , mkIndependentStrategy - ) where - -import Control.Monad.State.Strict (State, execState, modify') -import Data.Aeson (FromJSON, ToJSON, parseJSON, withText) -import Data.Aeson.Types (Parser) -import Data.Text (Text) -import Data.Data (Typeable) -import GHC.Generics (Generic) -import GHC.Natural (Natural) -import System.Envy (Var (..)) - -import GeniusYield.OrderBot.MatchingStrategy ( IndependentStrategy - , MatchResult - , completeFill - , partialFill - ) -import GeniusYield.OrderBot.OrderBook.List (OrderBook(..), unOrders) -import GeniusYield.OrderBot.Types ( OrderInfo (..) - , OrderType (BuyOrder, SellOrder) - , Volume (..) - , Price - ) - --- | Every bot strategy must be named here. -data BotStrategy = OneSellToManyBuy - deriving stock (Show, Eq, Generic) - deriving anyclass (ToJSON, Typeable) - -instance FromJSON BotStrategy where - parseJSON = withText "BotStrategy" parse - where - parse :: Text -> Parser BotStrategy - parse "OneSellToManyBuy" = return OneSellToManyBuy - parse _ = fail "Undefined strategy name" - -instance Var BotStrategy where - fromVar s = case s of - "OneSellToManyBuy" -> Just OneSellToManyBuy - _ -> Nothing - toVar = show - -{- | A list containing all implemented strategies. This list is used for the - tests and for the error message during env variable parsing. --} -allStrategies :: [BotStrategy] -allStrategies = [OneSellToManyBuy] - -{- | Given a bot strategy and a max amount of orders per transaction, creates - an independent strategy. --} -mkIndependentStrategy :: BotStrategy -> Natural -> IndependentStrategy -mkIndependentStrategy bs maxOrders _ bk = - case bs of - OneSellToManyBuy -> oneSellToManyBuy maxOrders bk - --- | Strategy state containing the matchings found and the remaining buy orders. -data StrategyState = StrategyState - { matchResults :: ![MatchResult] - , remainingOrders :: ![OrderInfo 'BuyOrder] - } - --- | Utility function for updating the state, after one run of the strategy. -updateStrategyState - :: MatchResult - -> [OrderInfo 'BuyOrder] - -> StrategyState - -> StrategyState -updateStrategyState [] bos' ss = ss { remainingOrders = bos' } -updateStrategyState mr' bos' StrategyState { matchResults = mr } = - StrategyState { matchResults = mr ++ [mr'] - , remainingOrders = bos' - } - -{- | Strategy matching: Picking one sell order and matching it with many (up to - `maxOrders`) buy orders. --} -oneSellToManyBuy :: Natural -> OrderBook -> [MatchResult] -oneSellToManyBuy maxOrders OrderBook{sellOrders, buyOrders} = - matchResults - $ execState (mapM_ go $ unOrders sellOrders) - $ StrategyState {matchResults = [], remainingOrders = unOrders buyOrders} - where - go :: OrderInfo 'SellOrder - -> State StrategyState () - go order = modify' $ - \st -> uncurry updateStrategyState - (multiFill (maxOrders - 1) (<=) order (remainingOrders st)) st - --- | General matching orders function. -multiFill - :: forall b b' - . Natural - -> (Price -> Price -> Bool) - -> OrderInfo b - -> [OrderInfo b'] - -> (MatchResult, [OrderInfo b']) -multiFill maxOrders checkPrices order = go (maxOrders - 1) vh - where - (Volume vl vh) = volume order - checkPrice = checkPrices $ price order - - go :: Natural -> Natural -> [OrderInfo b'] -> (MatchResult, [OrderInfo b']) - go _ 0 os = ([completeFill order], os) - go 0 v os - | (vh - v) >= vl = ([partialFill order (vh - v)], os) - | otherwise = ([], os) - go _ v [] - | (vh - v) > vl = ([partialFill order (vh - v)], []) - | otherwise = ([], []) - go limitO remVol (o : os) - | remVol == maxFillX && checkPrice xP = - let !b = completeFill o - in ([completeFill order, b], os) - | remVol > maxFillX && remVol >= minFillX && checkPrice xP = - case go (limitO - 1) (remVol - maxFillX) os of - ([], _) -> updateRemaining o $ go limitO remVol os - (bs, s) -> (completeFill o : bs, s) - | remVol < maxFillX - && remVol >= minFillX - && checkPrice xP = - ([completeFill order, partialFill o remVol], os) - | otherwise = updateRemaining o $ go limitO remVol os - where - xP = price o - (Volume minFillX maxFillX) = volume o - - updateRemaining x (a, b) = (a, x : b) diff --git a/geniusyield-orderbot/test/Main.hs b/geniusyield-orderbot/test/Main.hs index 574388c..1808fbe 100644 --- a/geniusyield-orderbot/test/Main.hs +++ b/geniusyield-orderbot/test/Main.hs @@ -2,8 +2,7 @@ module Main where import Test.Tasty (defaultMain, testGroup, TestTree) import Test.Tasty.QuickCheck (testProperty) - -import Strategies +import GeniusYield.OrderBot.Strategies.Impl import Tests.Prop.Strategies import Tests.Prop.Orderbook From dfa32ea310375fab77adb5c76b5efb72f31e27be Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Mon, 14 Oct 2024 16:14:42 +0530 Subject: [PATCH 4/6] feat(#111): add more orderbook utitlities --- .../lib-common/GeniusYield/OrderBot/Types.hs | 16 +++++++++--- .../GeniusYield/OrderBot/OrderBook.hsig | 25 ++++++++++++++++++- .../GeniusYield/OrderBot/OrderBook/List.hs | 25 ++++++++++++++++++- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/geniusyield-orderbot-framework/lib-common/GeniusYield/OrderBot/Types.hs b/geniusyield-orderbot-framework/lib-common/GeniusYield/OrderBot/Types.hs index 3fd8a58..9639baa 100644 --- a/geniusyield-orderbot-framework/lib-common/GeniusYield/OrderBot/Types.hs +++ b/geniusyield-orderbot-framework/lib-common/GeniusYield/OrderBot/Types.hs @@ -12,6 +12,7 @@ module GeniusYield.OrderBot.Types , OrderAssetPair (OAssetPair, currencyAsset, commodityAsset) , OrderType (..) , SOrderType (..) + , SOrderTypeI (..) , Volume (..) , Price (..) , mkOrderInfo @@ -140,13 +141,22 @@ isBuyOrder _ = False data OrderType = BuyOrder | SellOrder deriving stock (Eq, Show) -data SOrderType t where - SBuyOrder :: SOrderType BuyOrder - SSellOrder :: SOrderType SellOrder +data SOrderType (t :: OrderType) where + SBuyOrder :: SOrderType 'BuyOrder + SSellOrder :: SOrderType 'SellOrder deriving stock instance Eq (SOrderType t) deriving stock instance Show (SOrderType t) +class SOrderTypeI (t :: OrderType) where + sOrderType :: SOrderType t + +instance SOrderTypeI 'BuyOrder where + sOrderType = SBuyOrder + +instance SOrderTypeI 'SellOrder where + sOrderType = SSellOrder + ------------------------------------------------------------------------------- -- Order components ------------------------------------------------------------------------------- diff --git a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig index ad90af6..f719749 100644 --- a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig +++ b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig @@ -30,13 +30,18 @@ signature GeniusYield.OrderBot.OrderBook ( emptyOrders, unconsOrders, insertOrder, + deleteOrder, -- * Order book queries lowestSell, + lowestSellMaybe, highestBuy, + highestBuyMaybe, + lookupBest, withoutTip, foldlOrders, foldrOrders, foldlMOrders, + filterOrders, ordersLTPrice, ordersLTEPrice, ordersGTPrice, @@ -57,7 +62,8 @@ import Data.Kind (Type) import GeniusYield.OrderBot.Types ( OrderAssetPair(..) , OrderType (BuyOrder, SellOrder) - , OrderInfo, Price, Volume + , OrderInfo, Price, Volume + , SOrderTypeI ) import GeniusYield.OrderBot.DataSource ( Connection ) @@ -143,16 +149,30 @@ unconsOrders :: Orders t -> Maybe (OrderInfo t, Orders t) -- | Insert an order into the 'Orders' data structure. insertOrder :: OrderInfo t -> Orders t -> Orders t +-- | Delete an order from the 'Orders' data structure. +deleteOrder :: OrderInfo t -> Orders t -> Orders t + buyOrders :: OrderBook -> Orders 'BuyOrder sellOrders :: OrderBook -> Orders 'SellOrder -- Minima & Maxima +-- | The lowest sell order in the 'Orders' data structure. Fails if the 'Orders' data structure is empty. lowestSell :: Orders 'SellOrder -> OrderInfo 'SellOrder +-- | The lowest sell order in the 'Orders' data structure. Returns 'Nothing' if the 'Orders' data structure is empty. +lowestSellMaybe :: Orders 'SellOrder -> Maybe (OrderInfo 'SellOrder) + +-- | The highest buy order in the 'Orders' data structure. Fails if the 'Orders' data structure is empty. highestBuy :: Orders 'BuyOrder -> OrderInfo 'BuyOrder +-- | The highest buy order in the 'Orders' data structure. Returns 'Nothing' if the 'Orders' data structure is empty. +highestBuyMaybe :: Orders 'BuyOrder -> Maybe (OrderInfo 'BuyOrder) + +-- | In case we have buy orders, return the best buy order (highest price). And in case we have sell orders, return the best sell order (lowest price). +lookupBest :: forall (t :: OrderType). SOrderTypeI t => Orders t -> Maybe (OrderInfo t) + -- Slicing withoutTip :: Orders t -> Orders t @@ -183,6 +203,9 @@ foldrOrders :: forall a t. (OrderInfo t -> a -> a) -> a -> Orders t -> a -- | @foldlM@ variant for 'Orders', you should almost always be using @foldlMOrders'@ instead. foldlMOrders :: forall a t m. Monad m => (a -> OrderInfo t -> m a) -> a -> Orders t -> m a +-- | Filter orders based on a predicate. +filterOrders :: (OrderInfo t -> Bool) -> Orders t -> Orders t + -- Price queries ordersLTPrice :: Price -> Orders t -> Orders t diff --git a/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs b/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs index bc0d573..545b767 100644 --- a/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs +++ b/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs @@ -22,14 +22,19 @@ module GeniusYield.OrderBot.OrderBook.List ( emptyOrders, unconsOrders, insertOrder, + deleteOrder, -- * Order book queries lowestSell, + lowestSellMaybe, highestBuy, + highestBuyMaybe, + lookupBest, withoutTip, foldlOrders, foldrOrders, foldlMOrders, + filterOrders, ordersLTPrice, ordersLTEPrice, ordersGTPrice, @@ -45,11 +50,12 @@ module GeniusYield.OrderBot.OrderBook.List ( import Data.Aeson (ToJSON, object, toJSON) import Data.Foldable (foldl', foldlM) -import Data.List (insertBy, sortOn) +import Data.List (delete, insertBy, sortOn) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Ord (Down (Down)) +import Data.Maybe (listToMaybe) import GeniusYield.Api.Dex.Constants (DEXInfo) import GeniusYield.OrderBot.DataSource (Connection, withEachAssetOrders) @@ -113,12 +119,26 @@ insertOrder oi (Orders os) = Orders $ SBuyOrder -> insertBy (\oadd opresent -> compare (price opresent) (price oadd)) oi os SSellOrder -> insertBy (\oadd opresent -> compare (price oadd) (price opresent)) oi os +deleteOrder :: OrderInfo t -> Orders t -> Orders t +deleteOrder oi (Orders os) = Orders $ delete oi os + lowestSell :: Orders 'SellOrder -> OrderInfo 'SellOrder lowestSell = head . unOrders +lowestSellMaybe :: Orders 'SellOrder -> Maybe (OrderInfo 'SellOrder) +lowestSellMaybe = listToMaybe . unOrders + highestBuy :: Orders 'BuyOrder -> OrderInfo 'BuyOrder highestBuy = head . unOrders +highestBuyMaybe :: Orders 'BuyOrder -> Maybe (OrderInfo 'BuyOrder) +highestBuyMaybe = listToMaybe . unOrders + +lookupBest :: forall (t :: OrderType). SOrderTypeI t => Orders t -> Maybe (OrderInfo t) +lookupBest os = case (sOrderType @t) of + SBuyOrder -> highestBuyMaybe os + SSellOrder -> lowestSellMaybe os + withoutTip :: Orders t -> Orders t withoutTip = Orders . drop 1 . unOrders @@ -131,6 +151,9 @@ foldlMOrders f e = foldlM f e . unOrders foldrOrders :: forall a t. (OrderInfo t -> a -> a) -> a -> Orders t -> a foldrOrders f e = foldr f e . unOrders +filterOrders :: (OrderInfo t -> Bool) -> Orders t -> Orders t +filterOrders f = Orders . filter f . unOrders + nullOrders :: Orders t -> Bool nullOrders = null . unOrders From 761c5977c598b7fbf7a743876bcca72f30a0e79c Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Mon, 14 Oct 2024 16:22:54 +0530 Subject: [PATCH 5/6] feat(#111): `lookupBest` implementation is given as part of framework --- .../GeniusYield/OrderBot/OrderBook.hsig | 5 ----- .../GeniusYield/OrderBot/OrderBook/Extra.hs | 13 ++++++++++--- .../GeniusYield/OrderBot/OrderBook/List.hs | 6 ------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig index f719749..fbcff0c 100644 --- a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig +++ b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook.hsig @@ -36,7 +36,6 @@ signature GeniusYield.OrderBot.OrderBook ( lowestSellMaybe, highestBuy, highestBuyMaybe, - lookupBest, withoutTip, foldlOrders, foldrOrders, @@ -63,7 +62,6 @@ import Data.Kind (Type) import GeniusYield.OrderBot.Types ( OrderAssetPair(..) , OrderType (BuyOrder, SellOrder) , OrderInfo, Price, Volume - , SOrderTypeI ) import GeniusYield.OrderBot.DataSource ( Connection ) @@ -170,9 +168,6 @@ highestBuy :: Orders 'BuyOrder -> OrderInfo 'BuyOrder -- | The highest buy order in the 'Orders' data structure. Returns 'Nothing' if the 'Orders' data structure is empty. highestBuyMaybe :: Orders 'BuyOrder -> Maybe (OrderInfo 'BuyOrder) --- | In case we have buy orders, return the best buy order (highest price). And in case we have sell orders, return the best sell order (lowest price). -lookupBest :: forall (t :: OrderType). SOrderTypeI t => Orders t -> Maybe (OrderInfo t) - -- Slicing withoutTip :: Orders t -> Orders t diff --git a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook/Extra.hs b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook/Extra.hs index e4ac678..9f70b75 100644 --- a/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook/Extra.hs +++ b/geniusyield-orderbot-framework/lib-orderbook/GeniusYield/OrderBot/OrderBook/Extra.hs @@ -9,10 +9,11 @@ Stability : develop module GeniusYield.OrderBot.OrderBook.Extra ( foldlMOrders', mapMOrders_, + lookupBest, ) where -import Prelude (Monad, (*>), pure) -import GeniusYield.OrderBot.Types (OrderInfo) +import Prelude (Maybe, Monad, (*>), pure) +import GeniusYield.OrderBot.Types (OrderInfo, SOrderTypeI (..), SOrderType (..), OrderType) import GeniusYield.OrderBot.OrderBook -- | @foldlM'@ variant for 'Orders' which is strict in accumulator. @@ -21,4 +22,10 @@ foldlMOrders' f = foldlMOrders (\(!acc) -> f acc) -- | @mapM_@ variant for 'Orders'. mapMOrders_ :: forall a t m. Monad m => (OrderInfo t -> m a) -> Orders t -> m () -mapMOrders_ f os = foldlMOrders' (\_ oi -> f oi *> pure ()) () os \ No newline at end of file +mapMOrders_ f os = foldlMOrders' (\_ oi -> f oi *> pure ()) () os + +-- | In case we have buy orders, return the best buy order (highest price). And in case we have sell orders, return the best sell order (lowest price). +lookupBest :: forall (t :: OrderType). SOrderTypeI t => Orders t -> Maybe (OrderInfo t) +lookupBest os = case (sOrderType @t) of + SBuyOrder -> highestBuyMaybe os + SSellOrder -> lowestSellMaybe os \ No newline at end of file diff --git a/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs b/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs index 545b767..b08efd4 100644 --- a/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs +++ b/impl/orderbook-list/GeniusYield/OrderBot/OrderBook/List.hs @@ -29,7 +29,6 @@ module GeniusYield.OrderBot.OrderBook.List ( lowestSellMaybe, highestBuy, highestBuyMaybe, - lookupBest, withoutTip, foldlOrders, foldrOrders, @@ -134,11 +133,6 @@ highestBuy = head . unOrders highestBuyMaybe :: Orders 'BuyOrder -> Maybe (OrderInfo 'BuyOrder) highestBuyMaybe = listToMaybe . unOrders -lookupBest :: forall (t :: OrderType). SOrderTypeI t => Orders t -> Maybe (OrderInfo t) -lookupBest os = case (sOrderType @t) of - SBuyOrder -> highestBuyMaybe os - SSellOrder -> lowestSellMaybe os - withoutTip :: Orders t -> Orders t withoutTip = Orders . drop 1 . unOrders From fc1951f37bf2e0ec6dd5d2a5e6f26d17d24919e4 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Mon, 14 Oct 2024 19:09:25 +0530 Subject: [PATCH 6/6] feat(#111): adds versioning & changelog --- CHANGELOG.md | 5 +++++ geniusyield-orderbot-framework/CHANGELOG.md | 4 ++++ .../geniusyield-orderbot-framework.cabal | 2 +- geniusyield-orderbot.cabal | 3 ++- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..08d5211 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for geniusyield-orderbot + +## 0.2.0 + +Uses revamped geniusyield-orderbot-framework, strategies is moved into a signature with corresponding implementation. \ No newline at end of file diff --git a/geniusyield-orderbot-framework/CHANGELOG.md b/geniusyield-orderbot-framework/CHANGELOG.md index 078f451..b3b3bae 100644 --- a/geniusyield-orderbot-framework/CHANGELOG.md +++ b/geniusyield-orderbot-framework/CHANGELOG.md @@ -1,5 +1,9 @@ # Revision history for geniusyield-orderbot-framework +## 0.5.0 + +Adds strategy signature, utilities to different orderbook, etc. signatures and more modules related to order bot configuration and command line parsing. + ## 0.4.0 Conway era support. Note that this update is not compatible with Babbage era and so must be employed on Mainnet after Chang HF. diff --git a/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal b/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal index 7adee9a..1850d00 100644 --- a/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal +++ b/geniusyield-orderbot-framework/geniusyield-orderbot-framework.cabal @@ -1,7 +1,7 @@ cabal-version: 3.4 name: geniusyield-orderbot-framework synopsis: Smart Order Router framework -version: 0.4.0 +version: 0.5.0 build-type: Simple license: Apache-2.0 copyright: (c) 2023 GYELD GMBH diff --git a/geniusyield-orderbot.cabal b/geniusyield-orderbot.cabal index 8cf172d..b0517bb 100644 --- a/geniusyield-orderbot.cabal +++ b/geniusyield-orderbot.cabal @@ -1,6 +1,6 @@ cabal-version: 3.4 name: geniusyield-orderbot -version: 0.1.0.0 +version: 0.2.0 synopsis: Smart Order Router description: Open-source Smart Order Router framework to connect liquidity from the GeniusYield DEX to empowers users to deploy their own arbitrage @@ -16,6 +16,7 @@ category: Blockchain, Cardano, Framework homepage: https://github.com/geniusyield/smart-order-router#readme bug-reports: https://github.com/geniusyield/smart-order-router/issues extra-source-files: README.md +extra-doc-files: CHANGELOG.md -- Common sections