forked from geniusyield/smart-order-router
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMatchingStrategy.hs
123 lines (102 loc) · 4.94 KB
/
MatchingStrategy.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
{-|
Module : GeniusYield.OrderBot.MatchingStrategy
Copyright : (c) 2023 GYELD GMBH
License : Apache 2.0
Maintainer : [email protected]
Stability : develop
-}
module GeniusYield.OrderBot.MatchingStrategy
( IndependentStrategy
, FillType (..)
, MatchExecutionInfo (..)
, MatchResult
, completeFill
, partialFill
, executionSkeleton
, 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.DEX.Api.PartialOrder (PartialOrderInfo (poiOfferedAmount),
fillMultiplePartialOrders)
import GeniusYield.DEX.Api.Types (GYApiMonad)
import GeniusYield.OrderBot.OrderBook (OrderBook)
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.
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
:: GYApiMonad m
=> MatchResult
-> m (GYTxSkeleton 'PlutusV2)
executionSkeleton mr = fillMultiplePartialOrders $ map f mr
where
f (OrderExecutionInfo ft o) =
(poiSource o
, case ft of
CompleteFill -> poiOfferedAmount $ fromJust $ mPoi o
PartialFill n ->
if isBuyOrder o then
floor $ fromIntegral n * getPrice (price o)
else
n
)
matchExecutionInfoUtxoRef :: MatchExecutionInfo -> GYTxOutRef
matchExecutionInfoUtxoRef (OrderExecutionInfo CompleteFill OrderInfo {orderRef}) = orderRef
matchExecutionInfoUtxoRef (OrderExecutionInfo (PartialFill _) OrderInfo {orderRef}) = orderRef
-- | If the order contains the PartialOrderInfo, return it. If not, return the ref
poiSource :: forall t. OrderInfo t -> Either GYTxOutRef PartialOrderInfo
poiSource OrderInfo {orderRef, mPoi = Nothing} = Left orderRef
poiSource OrderInfo {mPoi = Just poi} = Right poi