Skip to content
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

implemented issue #27 #28

Merged
merged 1 commit into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ cabal.project.local~
.DS_Store
*.skey
var/
.envrc
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ Market maker bot for [GeniusYield](https://www.geniusyield.co/) DEX which implem
> [!NOTE]
> **Order classification and price**
>
> We call non-ADA tokens as _commodity_ and ADA as _currency_. Order offering currency in exchange of commodity is called as _buy order_ whereas order offering commodity in exchange of currency is called as _sell order_.
> In the following, we call any non-ADA token _commodity_, and we call ADA _currency_. An order offering currency in exchange for commodity is called a _buy order_, whereas an order offering commodity in exchange for currency is called a _sell order_.
>
> _Price_ is described in display unit[^1] of currency token per display unit of commodity token.

Given a market price `M` and a variable `δ` defined as _spread_, bot would place following orders where exact number and volume is determined by configuration:
Given a market price `M` and a variable `δ` defined as _spread_, the bot would place the following orders, where exact numbers and volumes are determined by the configuration:

* Buy orders at price:
* `M * (1 - δ)`
Expand All @@ -25,9 +25,9 @@ Given a market price `M` and a variable `δ` defined as _spread_, bot would plac
* `M * (1 + δ)`
* `M * (1 + δ + δ / 2)`
* `M * (1 + δ + δ / 2 + δ / 2)`
* And so on, where `n`th sell order's price is given by `M * (1 + δ + (n - 1) * δ / 2)`.
* And so on, where the `n`th sell order's price is given by `M * (1 + δ + (n - 1) * δ / 2)`.

If market price has drifted way higher (_"way higher"_ as directed by configuration) than the price at which buy orders were placed, buy orders would be canceled. Likewise, if the price has drifted way lower than the price at which sell orders were placed, they would be canceled.
If the market price has drifted way higher (_"way higher"_ as directed by the configuration) than the price at which buy orders were placed, buy orders would be canceled. Likewise, if the price has drifted way lower than the price at which sell orders were placed, those sell orders would be canceled.

## Running the market maker bot: Using docker compose (simple)

Expand Down Expand Up @@ -56,7 +56,13 @@ As in the example above; the following environment variables must be specified b

The configuration values used for these environment variables in the example above are just placeholders. These must be replaced by your own
configuration values. A MAINNET Maestro API key is needed, a payment signing key must be generated and a collateral UTxO must be provided after
sending funds to the address controlled by the payment signing key.
sending funds to the address given by the payment signing key and the (optional) stake address.

In order to determine this address, you could use `cardano-cli address build`, but you can also just run the market maker - the address will be printed to the console in the first line of output:

```
Genius Yield Market Maker: <MARKET MAKER ADDRESS>
```

Maestro API keys are available after registration via the following link:
- https://docs.gomaestro.org/Getting-started/Sign-up-login
Expand Down
51 changes: 28 additions & 23 deletions geniusyield-market-maker/Main.hs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
module Main (main) where

import Control.Exception (throwIO)
import GeniusYield.GYConfig
import GeniusYield.MarketMaker.MakerBot
import GeniusYield.MarketMaker.MakerBotConfig
import GeniusYield.MarketMaker.Prices
import GeniusYield.MarketMaker.Strategies
import GeniusYield.OrderBot.DataSource.Providers (connectDB)
import System.Environment
import Control.Exception (throwIO)
import GeniusYield.GYConfig
import GeniusYield.MarketMaker.MakerBot
import GeniusYield.MarketMaker.MakerBotConfig
import GeniusYield.MarketMaker.Prices
import GeniusYield.MarketMaker.Strategies
import GeniusYield.MarketMaker.Utils (addrUser)
import GeniusYield.OrderBot.DataSource.Providers (connectDB)
import GeniusYield.Types (addressToText)
import System.Environment

-----------------------------------------------------------------------
----------------------------- MAIN ------------------------------------
Expand All @@ -16,9 +18,9 @@ parseArgs ∷ IO (String, FilePath, Maybe FilePath)
parseArgs = do
args ← getArgs
case args of
[action, frameworkCfgPath, mBotConfigFile] return (action, frameworkCfgPath, Just mBotConfigFile)
[action, frameworkCfgPath] return (action, frameworkCfgPath, Nothing)
_
[action, frameworkCfgPath, mBotConfigFile] -> return (action, frameworkCfgPath, Just mBotConfigFile)
[action, frameworkCfgPath] -> return (action, frameworkCfgPath, Nothing)
_ ->
throwIO
. userError
$ unlines
Expand All @@ -28,21 +30,24 @@ parseArgs = do
" 3. Path to the maker bot config file (optional). If not provided, required information is fetched from environment variables."
]

main IO ()
main :: IO ()
main = do
(action, frameworkCfgPath, mBotConfigFile) parseArgs
(action, frameworkCfgPath, mBotConfigFile) <- parseArgs

coreCfg ← coreConfigIO frameworkCfgPath
mbc ← readMBotConfig mBotConfigFile
mb ← buildMakerBot mbc
di ← getDexInfo mbc
coreCfg <- coreConfigIO frameworkCfgPath
mbc <- readMBotConfig mBotConfigFile
mb <- buildMakerBot mbc
di <- getDexInfo mbc

putStrLn $ "Genius Yield Market Maker: "
++ show (addressToText $ addrUser (cfgNetworkId coreCfg) $ mbUser mb)

let netId = cfgNetworkId coreCfg
withCfgProviders coreCfg "" $ \providers
withCfgProviders coreCfg "" $ \providers ->
case action of
"Run" do
c connectDB netId providers
pp buildPP c di (mbcPriceConfig mbc)
"Run" -> do
c <- connectDB netId providers
pp <- buildPP c di (mbcPriceConfig mbc)
executeStrategy (fixedSpreadVsMarketPriceStrategy (mbcStrategyConfig mbc)) mb netId providers pp di
"Cancel" cancelAllOrders mb netId providers di
_ throwIO . userError $ "Action '" ++ action ++ "' not supported. Check cli arguments."
"Cancel" -> cancelAllOrders mb netId providers di
_ -> throwIO . userError $ "Action '" ++ action ++ "' not supported. Check cli arguments."
100 changes: 48 additions & 52 deletions geniusyield-market-maker/src/GeniusYield/MarketMaker/MakerBot.hs
Original file line number Diff line number Diff line change
@@ -1,56 +1,52 @@
module GeniusYield.MarketMaker.MakerBot where

import Control.Concurrent (threadDelay)
import Control.Exception (Exception (displayException), Handler (Handler), catches)
import Control.Monad (forM_, forever, when)
import Control.Monad.Reader (runReaderT)
import Data.List.Split (chunksOf)
import qualified Data.Map.Strict as M
import GeniusYield.Api.Dex.PartialOrder (
PartialOrderInfo (poiOwnerKey),
cancelMultiplePartialOrders,
partialOrders,
placePartialOrder,
)
import GeniusYield.Imports (printf)
import GeniusYield.MarketMaker.Constants (awaitTxParams, logNS)
import GeniusYield.MarketMaker.Prices
import GeniusYield.MarketMaker.Strategies
import GeniusYield.MarketMaker.User
import GeniusYield.MarketMaker.Utils (
DEXInfo (dexPORefs),
addrFromSkey,
pkhFromSkey,
)
import GeniusYield.Providers.Common (SubmitTxException)
import GeniusYield.Transaction (BuildTxException)
import GeniusYield.TxBuilder
import GeniusYield.Types
import System.Exit
import Control.Concurrent (threadDelay)
import Control.Exception (Exception (displayException),
Handler (Handler), catches)
import Control.Monad (forM_, forever, when)
import Control.Monad.Reader (runReaderT)
import Data.List.Split (chunksOf)
import qualified Data.Map.Strict as M
import GeniusYield.Api.Dex.PartialOrder (PartialOrderInfo (poiOwnerKey),
cancelMultiplePartialOrders,
partialOrders,
placePartialOrder)
import GeniusYield.Imports (printf)
import GeniusYield.MarketMaker.Constants (awaitTxParams, logNS)
import GeniusYield.MarketMaker.Prices
import GeniusYield.MarketMaker.Strategies
import GeniusYield.MarketMaker.User
import GeniusYield.MarketMaker.Utils (DEXInfo (dexPORefs),
addrUser, pkhUser)
import GeniusYield.Providers.Common (SubmitTxException)
import GeniusYield.Transaction (BuildTxException)
import GeniusYield.TxBuilder
import GeniusYield.Types
import System.Exit

data MakerBot = MakerBot
{ -- | User.
mbUser User,
mbUser :: User,
-- | Delay in microseconds between each iteration of execution strategy loop.
mbDelay Int,
mbDelay :: Int,
-- | Non-ADA token as other pair of the token is assumed to be ADA.
mbToken MMToken
mbToken :: MMToken
}

-----------------------------------------------------------------------
---------------------------- ACTIONS ----------------------------------

-- | Scan the chain for existing orders and cancel all of them in batches of 6.
cancelAllOrders MakerBot GYNetworkId GYProviders DEXInfo IO ()
cancelAllOrders :: MakerBot -> GYNetworkId -> GYProviders -> DEXInfo -> IO ()
cancelAllOrders MakerBot {mbUser} netId providers di = do
let go [PartialOrderInfo] IO ()
let go :: [PartialOrderInfo] -> IO ()
go partialOrderInfos = do
gyLogInfo providers logNS $ "---------- " ++ show (length partialOrderInfos) ++ " orders to cancel! -----------"
when (null partialOrderInfos) $ do
gyLogInfo providers logNS "---------- No more orders to cancel! -----------"
exitSuccess
let (batch, rest) = splitAt 6 partialOrderInfos
userAddr = addrFromSkey netId $ uSKey mbUser
userAddr = addrUser netId mbUser
txBody ←
runGYTxMonadNode netId providers [userAddr] userAddr (uColl mbUser)
$ runReaderT (cancelMultiplePartialOrders (dexPORefs di) batch) di
Expand All @@ -61,23 +57,23 @@ cancelAllOrders MakerBot {mbUser} netId providers di = do
gyLogInfo providers logNS "---------- Done for the block! -----------"
gyAwaitTxConfirmed providers awaitTxParams tid
go rest
partialOrderInfos runGYTxQueryMonadNode netId providers $ runReaderT (partialOrders (dexPORefs di)) di
let userPkh = pkhFromSkey . uSKey $ mbUser
userPOIs = filter (\o poiOwnerKey o == userPkh) $ M.elems partialOrderInfos
partialOrderInfos <- runGYTxQueryMonadNode netId providers $ runReaderT (partialOrders (dexPORefs di)) di
let userPkh = pkhUser mbUser
userPOIs = filter (\o -> poiOwnerKey o == userPkh) $ M.elems partialOrderInfos
go userPOIs

buildAndSubmitActions User GYProviders GYNetworkId UserActions DEXInfo IO ()
buildAndSubmitActions User {uSKey, uColl, uStakeAddress} providers netId ua di = flip catches handlers $ do
let userAddr = addrFromSkey netId uSKey
buildAndSubmitActions :: User -> GYProviders -> GYNetworkId -> UserActions -> DEXInfo -> IO ()
buildAndSubmitActions user@User {uSKey, uColl, uStakeAddress} providers netId ua di = flip catches handlers $ do
let userAddr = addrUser netId user
placeActions = uaPlaces ua
cancelActions = uaCancels ua

forM_ (chunksOf 6 cancelActions) $ \cancelChunk do
forM_ (chunksOf 6 cancelActions) $ \cancelChunk -> do
logInfo $ "Building for cancel action(s): " <> show cancelChunk
txBody ← runGYTxMonadNode netId providers [userAddr] userAddr uColl $ flip runReaderT di $ cancelMultiplePartialOrders (dexPORefs di) (map coaPoi cancelChunk)
buildCommon txBody

forM_ placeActions $ \pa@PlaceOrderAction {..} do
forM_ placeActions $ \pa@PlaceOrderAction {..} -> do
logInfo $ "Building for place action: " <> show pa
txBody ←
runGYTxMonadNode netId providers [userAddr] userAddr uColl
Expand All @@ -97,16 +93,16 @@ buildAndSubmitActions User {uSKey, uColl, uStakeAddress} providers netId ua di =
logInfo = gyLogInfo providers logNS

handlers =
let handlerCommon Exception e e IO ()
let handlerCommon :: Exception e => e -> IO ()
handlerCommon = logWarn . displayException

be BuildTxException IO ()
be :: BuildTxException -> IO ()
be = handlerCommon

se SubmitTxException IO ()
se :: SubmitTxException -> IO ()
se = handlerCommon

me GYTxMonadException IO ()
me :: GYTxMonadException -> IO ()
me = handlerCommon
in [Handler be, Handler se, Handler me]

Expand All @@ -120,13 +116,13 @@ buildAndSubmitActions User {uSKey, uColl, uStakeAddress} providers netId ua di =
logInfo $ printf "Tx successfully seen on chain with %d confirmation(s)" numConfirms

executeStrategy
Strategy
MakerBot
GYNetworkId
GYProviders
PricesProviders
DEXInfo
IO ()
:: Strategy
-> MakerBot
-> GYNetworkId
-> GYProviders
-> PricesProviders
-> DEXInfo
-> IO ()
executeStrategy runStrategy MakerBot {mbUser, mbDelay, mbToken} netId providers pp di =
forever $ do
newActions ← runStrategy pp mbUser mbToken
Expand Down
Loading
Loading