Execution layer for algorithmic trading
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

102 lines
3.6 KiB

{-# LANGUAGE OverloadedStrings #-}
module ATrade.Backtest.Execution (
mkExecutionAgent,
ExecutionAgent(..),
executePending,
executeStep
) where
import qualified Data.Text as T
import qualified Data.Map as M
import qualified Data.List as L
import ATrade.Types
import ATrade.Strategy.Types
import ATrade.Strategy
import Control.Monad.State
import Control.Monad.Trans.Writer
import Data.Decimal
import Data.Time.Clock
import Data.Time.Calendar
data Position = Position {
ticker :: T.Text,
balance :: Int }
data ExecutionAgent = ExecutionAgent {
pendingOrders :: [Order],
cash :: Decimal,
currentTime :: UTCTime,
orderIdCounter :: Integer
}
mkExecutionAgent startCash = ExecutionAgent { pendingOrders = [],
cash = startCash,
currentTime = UTCTime (fromGregorian 1970 1 1) 0,
orderIdCounter = 1 }
executeAtPrice :: Order -> Decimal -> WriterT [Event] (State ExecutionAgent) ()
executeAtPrice order price = do
when (orderState order == Unsubmitted) $ tell [OrderSubmitted order]
tell [OrderUpdate (orderId order) Executed]
timestamp <- gets currentTime
tell [NewTrade (mkTradeForOrder timestamp order price)]
case orderOperation order of
Buy -> modify' (\agent -> agent { cash = cash agent - price * realFracToDecimal 10 (toRational $ orderQuantity order) })
Sell -> modify' (\agent -> agent { cash = cash agent + price * realFracToDecimal 10 (toRational $ orderQuantity order) })
mkTradeForOrder timestamp order price = Trade { tradeOrderId = orderId order,
tradePrice = price,
tradeQuantity = orderQuantity order,
tradeVolume = price * realFracToDecimal 10 (toRational $ orderQuantity order),
tradeVolumeCurrency = "TEST_CURRENCY",
tradeOperation = orderOperation order,
tradeAccount = orderAccountId order,
tradeSecurity = orderSecurity order,
tradeTimestamp = timestamp,
tradeSignalId = orderSignalId order }
executePending :: Bars -> WriterT [Event] (State ExecutionAgent) ()
executePending bars = do
orders <- gets pendingOrders
let (executedOrders, leftover) = L.partition shouldExecute orders
mapM_ executeAtOrdersPrice executedOrders
modify' (\s -> s { pendingOrders = leftover } )
where
executeAtOrdersPrice order = case orderPrice order of
Limit price -> executeAtPrice order price
_ -> return () -- TODO handle stops
shouldExecute order = case M.lookup (orderSecurity order) bars of
Just (DataSeries ((ts, bar) : _)) -> case orderPrice order of
Limit price -> crosses bar price
_ -> False
Nothing -> False
crosses bar price = (barClose bar > price && barOpen bar < price) || (barClose bar < price && barOpen bar > price)
executeStep :: Bars -> [Order] -> WriterT [Event] (State ExecutionAgent) ()
executeStep bars orders = do
-- Assign consecutive IDs
orders' <- mapM (\o -> do
id <- gets orderIdCounter
modify(\s -> s { orderIdCounter = id + 1 })
return o { orderId = id }) orders
let (executableNow, pending) = L.partition isExecutableNow orders'
mapM_ (executeOrderAtLastPrice bars) executableNow
modify' (\s -> s { pendingOrders = pending ++ pendingOrders s })
where
isExecutableNow order = case M.lookup (orderSecurity order) bars of
Just (DataSeries (x:xs)) -> case orderPrice order of
Limit price -> (orderOperation order == Buy && price >= (barClose . snd) x) || (orderOperation order == Sell && price <= (barClose . snd) x)
Market -> True
_ -> False
executeOrderAtLastPrice bars order = case M.lookup (orderSecurity order) bars of
Just (DataSeries ((ts, bar) : _)) -> executeAtPrice order (barClose bar)
_ -> return ()