-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(#111): allow SOR codebase to be used as library #112
Changes from all commits
5271844
4a3b385
a7a29ca
dfa32ea
761c597
fc1951f
7f15e0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ module GeniusYield.OrderBot.Types | |
, OrderAssetPair (OAssetPair, currencyAsset, commodityAsset) | ||
, OrderType (..) | ||
, SOrderType (..) | ||
, SOrderTypeI (..) | ||
, Volume (..) | ||
, Price (..) | ||
, mkOrderInfo | ||
|
@@ -20,15 +21,20 @@ module GeniusYield.OrderBot.Types | |
, mkOrderAssetPair | ||
, equivalentAssetPair | ||
, mkEquivalentAssetPair | ||
, FillType (..) | ||
, MatchExecutionInfo (..) | ||
, completeFill | ||
, partialFill | ||
Comment on lines
+24
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not new definitions, but rather moved here from another module. |
||
) 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 (..)) | ||
|
@@ -135,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 | ||
------------------------------------------------------------------------------- | ||
|
@@ -237,3 +252,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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,12 +27,20 @@ signature GeniusYield.OrderBot.OrderBook ( | |
-- * Order book construction | ||
populateOrderBook, | ||
buildOrderBookList, | ||
emptyOrders, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New utilities to make strategy implementation agnostic of underlying orderbook implementation. |
||
unconsOrders, | ||
insertOrder, | ||
deleteOrder, | ||
-- * Order book queries | ||
lowestSell, | ||
lowestSellMaybe, | ||
highestBuy, | ||
highestBuyMaybe, | ||
withoutTip, | ||
foldlOrders, | ||
foldrOrders, | ||
foldlMOrders, | ||
filterOrders, | ||
ordersLTPrice, | ||
ordersLTEPrice, | ||
ordersGTPrice, | ||
|
@@ -41,18 +49,19 @@ 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) | ||
|
||
import GeniusYield.OrderBot.Types ( OrderAssetPair(..) | ||
, OrderType (BuyOrder, SellOrder) | ||
, OrderInfo, Price, Volume | ||
, OrderInfo, Price, Volume | ||
) | ||
import GeniusYield.OrderBot.DataSource ( Connection ) | ||
|
||
|
@@ -129,29 +138,50 @@ 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 | ||
|
||
-- | 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) | ||
|
||
-- Slicing | ||
|
||
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 +190,17 @@ 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 | ||
|
||
-- | Filter orders based on a predicate. | ||
filterOrders :: (OrderInfo t -> Bool) -> Orders t -> Orders t | ||
|
||
-- Price queries | ||
|
||
ordersLTPrice :: Price -> Orders t -> Orders t | ||
|
@@ -185,6 +221,8 @@ volumeGTPrice :: Price -> Orders t -> Volume | |
|
||
volumeGTEPrice :: Price -> Orders t -> Volume | ||
|
||
nullOrders :: Orders t -> Bool | ||
|
||
------------------------------------------------------------------------------- | ||
-- MultiAssetOrderBook reading utilities | ||
------------------------------------------------------------------------------- | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{-| | ||
Module : GeniusYield.OrderBot.OrderBook.Extra | ||
Synopsis : Extra utilities when working with order books. | ||
Copyright : (c) 2023 GYELD GMBH | ||
License : Apache 2.0 | ||
Maintainer : [email protected] | ||
Stability : develop | ||
-} | ||
module GeniusYield.OrderBot.OrderBook.Extra ( | ||
foldlMOrders', | ||
mapMOrders_, | ||
lookupBest, | ||
) where | ||
|
||
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. | ||
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 | ||
|
||
-- | 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I defined this class for writing
lookupBest
function insideOrderBook.Extra
module.