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.
103 lines
3.6 KiB
103 lines
3.6 KiB
|
7 years ago
|
{-# 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 ()
|