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
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 ()
|
|
|