Browse Source

More cleanup & warning elimination

stable
Denis Tereshkin 6 years ago
parent
commit
8e34ac61d5
  1. 2
      robocom-zero.cabal
  2. 102
      src/ATrade/Backtest/Execution.hs
  3. 15
      src/ATrade/BarAggregator.hs
  4. 32
      src/ATrade/Driver/Backtest.hs
  5. 12
      src/ATrade/Driver/Real.hs
  6. 1
      src/ATrade/Driver/Real/BrokerClientThread.hs
  7. 4
      src/ATrade/Driver/Real/QuoteSourceThread.hs
  8. 153
      src/ATrade/Forums/Smartlab.hs
  9. 4
      src/ATrade/Quotes/Finam.hs
  10. 8
      src/ATrade/Quotes/HAP.hs
  11. 1
      src/ATrade/Quotes/QHP.hs
  12. 4
      src/ATrade/RoboCom/Indicators.hs
  13. 42
      src/ATrade/RoboCom/Positions.hs
  14. 3
      src/ATrade/RoboCom/Types.hs
  15. 5
      src/ATrade/RoboCom/Utils.hs

2
robocom-zero.cabal

@ -15,7 +15,7 @@ cabal-version: >=1.10
library library
hs-source-dirs: src hs-source-dirs: src
ghc-options: -Wall -fno-warn-orphans -Wno-type-defaults ghc-options: -Wall -Werror -fno-warn-orphans -Wno-type-defaults
exposed-modules: ATrade.RoboCom.Indicators exposed-modules: ATrade.RoboCom.Indicators
, ATrade.RoboCom.Monad , ATrade.RoboCom.Monad
, ATrade.RoboCom.Positions , ATrade.RoboCom.Positions

102
src/ATrade/Backtest/Execution.hs

@ -1,102 +0,0 @@
{-# 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 ()

15
src/ATrade/BarAggregator.hs

@ -32,7 +32,6 @@ import Control.Lens
import Control.Monad.State import Control.Monad.State
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Time.Clock import Data.Time.Clock
import Debug.Trace
-- | Bar aggregator state -- | Bar aggregator state
data BarAggregator = BarAggregator { data BarAggregator = BarAggregator {
@ -110,7 +109,7 @@ handleTick tick = runState $ do
else else
return Nothing return Nothing
where where
isInTimeInterval tick (a, b) = (utctDayTime . timestamp) tick >= a && (utctDayTime . timestamp) tick <= b isInTimeInterval tick' (a, b) = (utctDayTime . timestamp) tick' >= a && (utctDayTime . timestamp) tick' <= b
barFromTick !newtick = Bar { barSecurity = security newtick, barFromTick !newtick = Bar { barSecurity = security newtick,
barTimestamp = timestamp newtick, barTimestamp = timestamp newtick,
barOpen = value newtick, barOpen = value newtick,
@ -134,18 +133,6 @@ handleTick tick = runState $ do
where where
newTimestamp = timestamp newtick newTimestamp = timestamp newtick
emptyBarFrom !bar newtick = newBar
where
newTimestamp = timestamp newtick
newBar = Bar {
barSecurity = barSecurity bar,
barTimestamp = newTimestamp,
barOpen = barClose bar,
barHigh = barClose bar,
barLow = barClose bar,
barClose = barClose bar,
barVolume = 0 }
updateTime :: Tick -> BarAggregator -> (Maybe Bar, BarAggregator) updateTime :: Tick -> BarAggregator -> (Maybe Bar, BarAggregator)
updateTime tick = runState $ do updateTime tick = runState $ do
lLastTicks %= M.insert (security tick, datatype tick) tick lLastTicks %= M.insert (security tick, datatype tick) tick

32
src/ATrade/Driver/Backtest.hs

@ -14,15 +14,13 @@ module ATrade.Driver.Backtest (
) where ) where
import ATrade.Driver.Types (InitializationCallback, import ATrade.Driver.Types (InitializationCallback,
Strategy (..),
StrategyInstanceParams (..)) StrategyInstanceParams (..))
import ATrade.Exceptions import ATrade.Exceptions
import ATrade.Quotes.Finam as QF import ATrade.Quotes.Finam as QF
import ATrade.RoboCom.Monad (Event (..), EventCallback, import ATrade.RoboCom.Monad (Event (..), EventCallback,
MonadRobot (..), MonadRobot (..),
StrategyEnvironment (..), StrategyEnvironment (..),
appendToLog, seBars, seLastTimestamp, appendToLog, seBars, seLastTimestamp)
st)
import ATrade.RoboCom.Positions import ATrade.RoboCom.Positions
import ATrade.RoboCom.Types (BarSeries (..), Ticker (..), import ATrade.RoboCom.Types (BarSeries (..), Ticker (..),
Timeframe (..)) Timeframe (..))
@ -30,16 +28,15 @@ import ATrade.Types
import Conduit (awaitForever, runConduit, yield, import Conduit (awaitForever, runConduit, yield,
(.|)) (.|))
import Control.Exception.Safe import Control.Exception.Safe
import Control.Lens import Control.Lens hiding (ix)
import Control.Monad.ST (runST) import Control.Monad.ST (runST)
import Control.Monad.State import Control.Monad.State
import Data.Aeson (FromJSON (..), Result (..), import Data.Aeson (FromJSON (..), Value (..), decode)
Value (..), decode)
import Data.Aeson.Types (parseMaybe) import Data.Aeson.Types (parseMaybe)
import Data.ByteString.Lazy (readFile, toStrict) import Data.ByteString.Lazy (readFile, toStrict)
import Data.Default import Data.Default
import Data.HashMap.Strict (lookup) import Data.HashMap.Strict (lookup)
import Data.List (concat, filter, find, partition) import Data.List (partition)
import Data.List.Split (splitOn) import Data.List.Split (splitOn)
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Semigroup ((<>)) import Data.Semigroup ((<>))
@ -95,7 +92,7 @@ feedArgParser = eitherReader (\s -> case splitOn ":" s of
_ -> Left $ "Unable to parse feed id: " ++ s) _ -> Left $ "Unable to parse feed id: " ++ s)
backtestMain :: (FromJSON c, StateHasPositions s) => DiffTime -> s -> Maybe (InitializationCallback c) -> EventCallback c s -> IO () backtestMain :: (FromJSON c, StateHasPositions s) => DiffTime -> s -> Maybe (InitializationCallback c) -> EventCallback c s -> IO ()
backtestMain dataDownloadDelta defaultState initCallback callback = do backtestMain _dataDownloadDelta defaultState initCallback callback = do
params <- execParser opts params <- execParser opts
(tickerList, config) <- loadStrategyConfig params (tickerList, config) <- loadStrategyConfig params
@ -116,7 +113,7 @@ backtestMain dataDownloadDelta defaultState initCallback callback = do
feeds <- loadFeeds (paramsFeeds params) feeds <- loadFeeds (paramsFeeds params)
runBacktestDriver feeds config tickerList runBacktestDriver feeds updatedConfig tickerList
where where
opts = info (helper <*> paramsParser) opts = info (helper <*> paramsParser)
( fullDesc <> header "ATrade strategy backtesting framework" ) ( fullDesc <> header "ATrade strategy backtesting framework" )
@ -141,14 +138,11 @@ backtestMain dataDownloadDelta defaultState initCallback callback = do
Object o -> do Object o -> do
mbTickers <- "tickers" `lookup` o mbTickers <- "tickers" `lookup` o
mbParams <- "params" `lookup` o mbParams <- "params" `lookup` o
tickers <- parseMaybe parseJSON mbTickers tickers' <- parseMaybe parseJSON mbTickers
params <- parseMaybe parseJSON mbParams params <- parseMaybe parseJSON mbParams
return (tickers, params) return (tickers', params)
_ -> Nothing _ -> Nothing
resultToMaybe (Error _) = Nothing
resultToMaybe (Success a) = Just a
barStreamFromFeeds feeds = case nextBar feeds of barStreamFromFeeds feeds = case nextBar feeds of
Just (bar, feeds') -> yield bar >> barStreamFromFeeds feeds' Just (bar, feeds') -> yield bar >> barStreamFromFeeds feeds'
_ -> return () _ -> return ()
@ -166,7 +160,6 @@ backtestMain dataDownloadDelta defaultState initCallback callback = do
minIx <- newSTRef Nothing minIx <- newSTRef Nothing
forM_ [0..(V.length feeds-1)] (\ix -> do forM_ [0..(V.length feeds-1)] (\ix -> do
let feed = feeds ! ix let feed = feeds ! ix
curIx <- readSTRef minIx
curTs <- readSTRef minTs curTs <- readSTRef minTs
case feed of case feed of
x:_ -> case curTs of x:_ -> case curTs of
@ -292,13 +285,14 @@ backtestMain dataDownloadDelta defaultState initCallback callback = do
enqueueEvent event = pendingEvents %= ((:) event) enqueueEvent event = pendingEvents %= ((:) event)
instance (Default c, Default s) => Default (BacktestState s c) instance (Default c, Default s) => Default (BacktestState c s)
where where
def = defaultBacktestState def def [] def = defaultBacktestState def def []
defaultBacktestState s c tickerList = BacktestState 0 s c (StrategyEnvironment "" "" 1 tickers (UTCTime (fromGregorian 1970 1 1) 0)) [] [] [] 1 [] [] defaultBacktestState :: s -> c -> [Ticker] -> BacktestState c s
defaultBacktestState s c tickerList = BacktestState 0 s c (StrategyEnvironment "" "" 1 tickers' (UTCTime (fromGregorian 1970 1 1) 0)) [] [] [] 1 [] []
where where
tickers = M.fromList $ map (\x -> (code x, BarSeries (code x) (Timeframe (timeframeSeconds x)) [])) tickerList tickers' = M.fromList $ map (\x -> (code x, BarSeries (code x) (Timeframe (timeframeSeconds x)) [])) tickerList
newtype BacktestingMonad s c a = BacktestingMonad { unBacktestingMonad :: State (BacktestState s c) a } newtype BacktestingMonad s c a = BacktestingMonad { unBacktestingMonad :: State (BacktestState s c) a }
deriving (Functor, Applicative, Monad, MonadState (BacktestState s c)) deriving (Functor, Applicative, Monad, MonadState (BacktestState s c))
@ -324,7 +318,7 @@ instance MonadRobot (BacktestingMonad c s) c s where
pendingOrders .= otherOrders pendingOrders .= otherOrders
appendToLog txt = logs %= ((:) txt) appendToLog txt = logs %= ((:) txt)
setupTimer time = pendingTimers %= ((:) time) setupTimer time = pendingTimers %= ((:) time)
enqueueIOAction actionId action = error "Backtesting io actions is not supported" enqueueIOAction _actionId _action = error "Backtesting io actions is not supported"
getConfig = use robotParams getConfig = use robotParams
getState = use robotState getState = use robotState
setState s = robotState .= s setState s = robotState .= s

12
src/ATrade/Driver/Real.hs

@ -44,7 +44,6 @@ import Data.Time.Calendar
import Data.Time.Clock import Data.Time.Clock
import Data.Time.Clock.POSIX import Data.Time.Clock.POSIX
import Data.Maybe import Data.Maybe
import Data.Monoid
import Database.Redis hiding (info, decode) import Database.Redis hiding (info, decode)
import ATrade.Types import ATrade.Types
import ATrade.RoboCom.Monad (EventCallback, Event(..), StrategyEnvironment(..), seBars, seLastTimestamp, Event(..), MonadRobot(..)) import ATrade.RoboCom.Monad (EventCallback, Event(..), StrategyEnvironment(..), seBars, seLastTimestamp, Event(..), MonadRobot(..))
@ -141,10 +140,10 @@ instance MonadRobot (App c s) c s where
timers <- asks envTimers timers <- asks envTimers
lift $ atomicModifyIORef' timers (\s -> (t : s, ())) lift $ atomicModifyIORef' timers (\s -> (t : s, ()))
enqueueIOAction actionId action = do enqueueIOAction actionId action' = do
eventChan <- asks envEventChan eventChan <- asks envEventChan
lift $ void $ forkIO $ do lift $ void $ forkIO $ do
v <- action v <- action'
BC.writeChan eventChan $ ActionCompleted actionId v BC.writeChan eventChan $ ActionCompleted actionId v
getConfig = asks envConfigRef >>= lift . readIORef getConfig = asks envConfigRef >>= lift . readIORef
@ -282,7 +281,7 @@ robotMain dataDownloadDelta defaultState initCallback callback = do
envAggregator = agg, envAggregator = agg,
envLastTimestamp = now envLastTimestamp = now
} }
runReaderT (barStrategyDriver ctx (sourceBarTimeframe params) tickFilter strategy configRef stateRef timersRef shutdownMv) env `finally` killThread stateSavingThread) runReaderT (barStrategyDriver ctx (sourceBarTimeframe params) tickFilter strategy shutdownMv) env `finally` killThread stateSavingThread)
where where
tickFilter :: Tick -> Bool tickFilter :: Tick -> Bool
tickFilter tick = tickFilter tick =
@ -476,8 +475,8 @@ mkBarStrategy instanceParams dd params initialState cb = BarStrategy {
-- | Main function which handles incoming events (ticks/orders), passes them to strategy callback -- | Main function which handles incoming events (ticks/orders), passes them to strategy callback
-- and executes returned strategy actions -- and executes returned strategy actions
barStrategyDriver :: Context -> Maybe Int -> (Tick -> Bool) -> Strategy c s -> IORef c -> IORef s -> IORef [UTCTime] -> MVar () -> App c s () barStrategyDriver :: Context -> Maybe Int -> (Tick -> Bool) -> Strategy c s -> MVar () -> App c s ()
barStrategyDriver ctx mbSourceTimeframe tickFilter strategy configRef stateRef timersRef shutdownVar = do barStrategyDriver ctx mbSourceTimeframe tickFilter strategy shutdownVar = do
eventChan <- asks envEventChan eventChan <- asks envEventChan
brokerChan <- asks envBrokerChan brokerChan <- asks envBrokerChan
agg <- asks envAggregator agg <- asks envAggregator
@ -522,6 +521,7 @@ barStrategyDriver ctx mbSourceTimeframe tickFilter strategy configRef stateRef t
newTimers <- catMaybes <$> (mapM (checkTimer eventChan newTimestamp) $ strategyTimers strategy') newTimers <- catMaybes <$> (mapM (checkTimer eventChan newTimestamp) $ strategyTimers strategy')
(eventCallback strategy) event (eventCallback strategy) event
timersRef <- asks envTimers
lift $ writeIORef timersRef newTimers lift $ writeIORef timersRef newTimers
readAndHandleEvents agg strategy' readAndHandleEvents agg strategy'

1
src/ATrade/Driver/Real/BrokerClientThread.hs

@ -9,7 +9,6 @@ import ATrade.Broker.Client
import ATrade.Broker.Protocol import ATrade.Broker.Protocol
import ATrade.RoboCom.Monad hiding (cancelOrder, import ATrade.RoboCom.Monad hiding (cancelOrder,
submitOrder) submitOrder)
import ATrade.RoboCom.Types
import ATrade.Types import ATrade.Types
import Control.Concurrent hiding (readChan, writeChan, import Control.Concurrent hiding (readChan, writeChan,

4
src/ATrade/Driver/Real/QuoteSourceThread.hs

@ -13,7 +13,6 @@ import ATrade.RoboCom.Types
import ATrade.Types import ATrade.Types
import Data.IORef import Data.IORef
import Data.Maybe
import qualified Data.Text as T import qualified Data.Text as T
import Control.Concurrent hiding (readChan, writeChan, import Control.Concurrent hiding (readChan, writeChan,
@ -58,7 +57,4 @@ startQuoteSourceThread ctx qsEp strategy eventChan agg tickFilter maybeSourceTim
(datatype tick /= LastTradePrice || (datatype tick == LastTradePrice && volume tick > 0)) (datatype tick /= LastTradePrice || (datatype tick == LastTradePrice && volume tick > 0))
tickersList = fmap code . (tickers . strategyInstanceParams) $ strategy tickersList = fmap code . (tickers . strategyInstanceParams) $ strategy
applyTimeframeSpec t = case maybeSourceTimeframe of
Just tf -> t `T.append` T.pack (":" ++ show tf ++ ";")
Nothing -> t

153
src/ATrade/Forums/Smartlab.hs

@ -1,153 +0,0 @@
{-# OPTIONS_GHC -Wno-type-defaults #-}
module ATrade.Forums.Smartlab (
NewsItem(..),
IndexItem(..),
getIndex,
getItem
) where
import qualified Data.ByteString.Lazy as BL
import qualified Data.List as L
import Data.Maybe
import qualified Data.Text as T
import Data.Text.Encoding
import Data.Time.Calendar
import Data.Time.Clock
import Network.HTTP.Simple
import Safe
import Text.HTML.TagSoup
import Text.Parsec
import Text.Parsec.Text
import Text.StringLike
import Debug.Trace
data NewsItem = NewsItem {
niUrl :: !T.Text,
niHeader :: !T.Text,
niText :: !T.Text,
niAuthor :: !T.Text,
niPubTime :: !UTCTime
} deriving (Show, Eq)
data IndexItem = IndexItem {
iiUrl :: !T.Text,
iiTitle :: !T.Text,
iiPubTime :: !UTCTime
} deriving (Show, Eq)
monthNames :: [T.Text]
monthNames = fmap T.pack ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"]
extractBetween :: StringLike str => String -> [Tag str] -> [Tag str]
extractBetween tagName = takeWhile (~/= closeTag) . dropWhile (~/= openTag)
where
openTag = "<" ++ tagName ++ ">"
closeTag = "</" ++ tagName ++ ">"
matchClass :: T.Text -> T.Text -> Tag T.Text -> Bool
matchClass _ className (TagOpen _ attrs) = case L.lookup (T.pack "class") attrs of
Just klass -> className `L.elem` T.words klass
Nothing -> False
matchClass _ _ _ = False
parseTimestamp :: T.Text -> Maybe UTCTime
parseTimestamp text = case parse timestampParser "" text of
Left _ -> Nothing
Right val -> Just val
where
timestampParser :: Parser UTCTime
timestampParser = do
spaces
day <- read <$> many1 digit
spaces
monthName <- T.pack <$> many1 letter
case L.elemIndex monthName monthNames of
Nothing -> fail "Can't parse month"
Just month -> do
spaces
year <- fromIntegral . read <$> many1 digit
_ <- char ','
spaces
hour <- fromIntegral . read <$> many1 digit
_ <- char ':'
minute <- fromIntegral . read <$> many1 digit
return $ UTCTime (fromGregorian year (month + 1) day) (hour * 3600 + minute * 60)
getItem :: IndexItem -> IO (Maybe NewsItem)
getItem indexItem = do
rq <- parseRequest $ T.unpack (iiUrl indexItem)
resp <- httpLBS rq
if getResponseStatusCode resp == 200
then return . parseItem . decodeUtf8 . BL.toStrict . getResponseBody $ resp
else return Nothing
where
parseItem rawHtml = case parseTimestamp timestamp of
Just itemPubtime -> Just NewsItem {
niUrl = iiUrl indexItem,
niHeader = itemHeader,
niText = itemText,
niAuthor = itemAuthor,
niPubTime = itemPubtime
}
Nothing -> Nothing
where
itemHeader = innerText .
extractBetween "span" .
extractBetween "h1" .
dropWhile (not . matchClass (T.pack "div") (T.pack "topic")) $ tags
itemText = innerText .
extractBetween "div" .
dropWhile (not . matchClass (T.pack "div") (T.pack "content")) .
dropWhile (~/= "<div id=content_box>") $ tags
itemAuthor = innerText .
extractBetween "li" .
dropWhile (not . matchClass (T.pack "li") (T.pack "author")) $ tags
timestamp = traceShowId $ innerText .
extractBetween "li" .
dropWhile (not . matchClass (T.pack "li") (T.pack "date")) $ tags
tags = parseTags rawHtml
getIndex :: T.Text -> Int -> IO ([IndexItem], Bool)
getIndex rootUrl pageNumber = do
rq <- parseRequest $ T.unpack $ makeUrl rootUrl pageNumber
resp <- httpLBS rq
return $ if getResponseStatusCode resp == 200
then parseIndex . decodeUtf8 . BL.toStrict . getResponseBody $ resp
else ([], False)
where
parseIndex :: T.Text -> ([IndexItem], Bool)
parseIndex x = (mapMaybe parseIndexEntry $ partitions (matchClass (T.pack "div") (T.pack "topic")) $ parseTags x, hasNextPage $ parseTags x)
parseIndexEntry :: [Tag T.Text] -> Maybe IndexItem
parseIndexEntry divTag = do
a <- headMay . dropWhile (~/= "<a>") $ divTag
let text = innerText . takeWhile (~/= "</a>") . dropWhile (~/= "<a>") $ divTag
case a of
TagOpen _ attr -> do
href <- L.lookup (T.pack "href") attr
ts <- parseTimestamp (innerText $ takeWhile (~/= "</li>") . dropWhile (not . matchClass (T.pack "li") (T.pack "date")) $ divTag)
Just IndexItem { iiUrl = href,
iiTitle = text,
iiPubTime = ts }
_ -> Nothing
makeUrl root pagenumber
| pagenumber == 0 || pagenumber == 1 = root
| otherwise = root `T.append` (T.pack "/page") `T.append` T.pack (show pagenumber)
hasNextPage tags = if pageNumber <= 1
then paginationLinksCount > 0
else paginationLinksCount > 1
where
paginationLinksCount = length . filter (~== "<a>") . extractBetween "p" . dropWhile (~/= "<div id=pagination>") $ tags

4
src/ATrade/Quotes/Finam.hs

@ -200,8 +200,8 @@ instance FromRecord Row where
high <- v .! 5 high <- v .! 5
low <- v .! 6 low <- v .! 6
close <- v .! 7 close <- v .! 7
volume <- v .! 8 vol <- v .! 8
return $ Row tkr dt open high low close volume return $ Row tkr dt open high low close vol
| otherwise = mzero | otherwise = mzero
where where
parseDt :: B.ByteString -> B.ByteString -> Parser UTCTime parseDt :: B.ByteString -> B.ByteString -> Parser UTCTime

8
src/ATrade/Quotes/HAP.hs

@ -9,10 +9,8 @@ module ATrade.Quotes.HAP (
import ATrade.Types import ATrade.Types
import Data.Aeson import Data.Aeson
import Data.Binary.Get import Data.Binary.Get
import Data.Binary.IEEE754
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time.Calendar
import Data.Time.Clock import Data.Time.Clock
import Data.Time.Clock.POSIX import Data.Time.Clock.POSIX
import System.Log.Logger import System.Log.Logger
@ -68,10 +66,13 @@ getQuotes ctx params =
else return [] else return []
_ -> return [] _ -> return []
where where
resampleBars p bars@(firstBar:rest) = resampleBars' (periodToSec p) rest firstBar [] resampleBars p (firstBar:rest) = resampleBars' (periodToSec p) rest firstBar []
resampleBars _ [] = []
resampleBars' p (bar:bars) currentBar resampled = if barNumber p currentBar == barNumber p bar resampleBars' p (bar:bars) currentBar resampled = if barNumber p currentBar == barNumber p bar
then resampleBars' p bars (aggregate currentBar bar) resampled then resampleBars' p bars (aggregate currentBar bar) resampled
else resampleBars' p bars bar (currentBar : resampled) else resampleBars' p bars bar (currentBar : resampled)
resampleBars' _ [] _ _ = []
periodToSec Period1Min = 60 periodToSec Period1Min = 60
periodToSec Period5Min = 60 * 5 periodToSec Period5Min = 60 * 5
@ -80,6 +81,7 @@ getQuotes ctx params =
periodToSec PeriodHour = 60 * 60 periodToSec PeriodHour = 60 * 60
periodToSec PeriodDay = 60 * 60 * 24 periodToSec PeriodDay = 60 * 60 * 24
periodToSec PeriodWeek = 86400 * 7 periodToSec PeriodWeek = 86400 * 7
periodToSec PeriodMonth = 86400 * 7 * 4 -- TODO: incorrect, but what can I do?
barNumber sec bar = truncate (utcTimeToPOSIXSeconds (barTimestamp bar)) `div` sec barNumber sec bar = truncate (utcTimeToPOSIXSeconds (barTimestamp bar)) `div` sec

1
src/ATrade/Quotes/QHP.hs

@ -9,7 +9,6 @@ module ATrade.Quotes.QHP (
import ATrade.Types import ATrade.Types
import Data.Aeson import Data.Aeson
import Data.Binary.Get import Data.Binary.Get
import Data.Binary.IEEE754
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time.Calendar import Data.Time.Calendar

4
src/ATrade/RoboCom/Indicators.hs

@ -121,6 +121,6 @@ bbandUpper period devs values = sma period values + devs * sigma
mean = sma period mean = sma period
percentRank :: Int -> [Double] -> Double percentRank :: Int -> [Double] -> Double
percentRank period values@(v:vs) = fromIntegral (length (filter (\x -> x < v) $ take period values)) / fromIntegral (length (take period values)) percentRank period values@(v:_) = fromIntegral (length (filter (\x -> x < v) $ take period values)) / fromIntegral (length (take period values))
percentRank period [] = 0 percentRank _ [] = 0

42
src/ATrade/RoboCom/Positions.hs

@ -63,6 +63,7 @@ module ATrade.RoboCom.Positions
exitAtLimit, exitAtLimit,
doNothing, doNothing,
setStopLoss, setStopLoss,
setLimitStopLoss,
setTakeProfit, setTakeProfit,
setStopLossAndTakeProfit setStopLossAndTakeProfit
) where ) where
@ -75,7 +76,6 @@ import ATrade.Types
import Control.Lens import Control.Lens
import Control.Monad import Control.Monad
import Ether
import Data.Aeson import Data.Aeson
import qualified Data.List as L import qualified Data.List as L
@ -448,9 +448,9 @@ onActionCompletedEvent event f = case event of
_ -> doNothing _ -> doNothing
enterAtMarket :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => T.Text -> Operation -> m Position enterAtMarket :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => T.Text -> Operation -> m Position
enterAtMarket signalName operation = do enterAtMarket operationSignalName operation = do
env <- getEnvironment env <- getEnvironment
enterAtMarketWithParams (env ^. seAccount) (env ^. seVolume) (SignalId (env ^. seInstanceId) signalName "") operation enterAtMarketWithParams (env ^. seAccount) (env ^. seVolume) (SignalId (env ^. seInstanceId) operationSignalName "") operation
enterAtMarketWithParams :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => T.Text -> Int -> SignalId -> Operation -> m Position enterAtMarketWithParams :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => T.Text -> Int -> SignalId -> Operation -> m Position
enterAtMarketWithParams account quantity signalId operation = do enterAtMarketWithParams account quantity signalId operation = do
@ -468,15 +468,15 @@ enterAtMarketWithParams account quantity signalId operation = do
} }
enterAtLimit :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> T.Text -> Price -> Operation -> m Position enterAtLimit :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> T.Text -> Price -> Operation -> m Position
enterAtLimit timeToCancel signalName price operation = do enterAtLimit timeToCancel operationSignalName price operation = do
env <- getEnvironment env <- getEnvironment
enterAtLimitWithParams timeToCancel (env ^. seAccount) (env ^. seVolume) (SignalId (env ^. seInstanceId) signalName "") price operation enterAtLimitWithParams timeToCancel (env ^. seAccount) (env ^. seVolume) (SignalId (env ^. seInstanceId) operationSignalName "") price operation
enterAtLimitWithVolume :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> T.Text -> Price -> Int -> Operation -> m Position enterAtLimitWithVolume :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> T.Text -> Price -> Int -> Operation -> m Position
enterAtLimitWithVolume timeToCancel signalName price vol operation = do enterAtLimitWithVolume timeToCancel operationSignalName price vol operation = do
acc <- view seAccount <$> getEnvironment acc <- view seAccount <$> getEnvironment
inst <- view seInstanceId <$> getEnvironment inst <- view seInstanceId <$> getEnvironment
enterAtLimitWithParams timeToCancel acc vol (SignalId inst signalName "") price operation enterAtLimitWithParams timeToCancel acc vol (SignalId inst operationSignalName "") price operation
enterAtLimitWithParams :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> T.Text -> Int -> SignalId -> Price -> Operation -> m Position enterAtLimitWithParams :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> T.Text -> Int -> SignalId -> Price -> Operation -> m Position
enterAtLimitWithParams timeToCancel account quantity signalId price operation = do enterAtLimitWithParams timeToCancel account quantity signalId price operation = do
@ -484,17 +484,17 @@ enterAtLimitWithParams timeToCancel account quantity signalId price operation =
enterAtLimitForTickerWithParams tickerId timeToCancel account quantity signalId price operation enterAtLimitForTickerWithParams tickerId timeToCancel account quantity signalId price operation
enterAtLimitForTickerWithVolume :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> T.Text -> Price -> Int -> Operation -> m Position enterAtLimitForTickerWithVolume :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> T.Text -> Price -> Int -> Operation -> m Position
enterAtLimitForTickerWithVolume tickerId timeToCancel signalName price vol operation = do enterAtLimitForTickerWithVolume tickerId timeToCancel operationSignalName price vol operation = do
acc <- view seAccount <$> getEnvironment acc <- view seAccount <$> getEnvironment
inst <- view seInstanceId <$> getEnvironment inst <- view seInstanceId <$> getEnvironment
enterAtLimitForTickerWithParams tickerId timeToCancel acc vol (SignalId inst signalName "") price operation enterAtLimitForTickerWithParams tickerId timeToCancel acc vol (SignalId inst operationSignalName "") price operation
enterAtLimitForTicker :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> T.Text -> Price -> Operation -> m Position enterAtLimitForTicker :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> T.Text -> Price -> Operation -> m Position
enterAtLimitForTicker tickerId timeToCancel signalName price operation = do enterAtLimitForTicker tickerId timeToCancel operationSignalName price operation = do
acc <- view seAccount <$> getEnvironment acc <- view seAccount <$> getEnvironment
inst <- view seInstanceId <$> getEnvironment inst <- view seInstanceId <$> getEnvironment
vol <- view seVolume <$> getEnvironment vol <- view seVolume <$> getEnvironment
enterAtLimitForTickerWithParams tickerId timeToCancel acc vol (SignalId inst signalName "") price operation enterAtLimitForTickerWithParams tickerId timeToCancel acc vol (SignalId inst operationSignalName "") price operation
enterAtLimitForTickerWithParams :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> T.Text -> Int -> SignalId -> Price -> Operation -> m Position enterAtLimitForTickerWithParams :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> T.Text -> Int -> SignalId -> Price -> Operation -> m Position
enterAtLimitForTickerWithParams tickerId timeToCancel account quantity signalId price operation = do enterAtLimitForTickerWithParams tickerId timeToCancel account quantity signalId price operation = do
@ -514,25 +514,25 @@ enterAtLimitForTickerWithParams tickerId timeToCancel account quantity signalId
} }
enterLongAtMarket :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => T.Text -> m Position enterLongAtMarket :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => T.Text -> m Position
enterLongAtMarket signalName = enterAtMarket signalName Buy enterLongAtMarket operationSignalName = enterAtMarket operationSignalName Buy
enterShortAtMarket :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => T.Text -> m Position enterShortAtMarket :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => T.Text -> m Position
enterShortAtMarket signalName = enterAtMarket signalName Sell enterShortAtMarket operationSignalName = enterAtMarket operationSignalName Sell
enterLongAtLimit :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> Price -> T.Text -> m Position enterLongAtLimit :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> Price -> T.Text -> m Position
enterLongAtLimit timeToCancel price signalName = enterAtLimit timeToCancel signalName price Buy enterLongAtLimit timeToCancel price operationSignalName = enterAtLimit timeToCancel operationSignalName price Buy
enterLongAtLimitForTicker :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> Price -> T.Text -> m Position enterLongAtLimitForTicker :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> Price -> T.Text -> m Position
enterLongAtLimitForTicker tickerId timeToCancel price signalName = enterAtLimitForTicker tickerId timeToCancel signalName price Buy enterLongAtLimitForTicker tickerId timeToCancel price operationSignalName = enterAtLimitForTicker tickerId timeToCancel operationSignalName price Buy
enterShortAtLimit :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> Price -> T.Text -> m Position enterShortAtLimit :: (StateHasPositions s, ParamsHasMainTicker c, MonadRobot m c s) => NominalDiffTime -> Price -> T.Text -> m Position
enterShortAtLimit timeToCancel price signalName = enterAtLimit timeToCancel signalName price Sell enterShortAtLimit timeToCancel price operationSignalName = enterAtLimit timeToCancel operationSignalName price Sell
enterShortAtLimitForTicker :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> Price -> T.Text -> m Position enterShortAtLimitForTicker :: (StateHasPositions s, MonadRobot m c s) => TickerId -> NominalDiffTime -> Price -> T.Text -> m Position
enterShortAtLimitForTicker tickerId timeToCancel price signalName = enterAtLimitForTicker tickerId timeToCancel signalName price Sell enterShortAtLimitForTicker tickerId timeToCancel price operationSignalName = enterAtLimitForTicker tickerId timeToCancel operationSignalName price Sell
exitAtMarket :: (StateHasPositions s, MonadRobot m c s) => Position -> T.Text -> m Position exitAtMarket :: (StateHasPositions s, MonadRobot m c s) => Position -> T.Text -> m Position
exitAtMarket position signalName = do exitAtMarket position operationSignalName = do
inst <- view seInstanceId <$> getEnvironment inst <- view seInstanceId <$> getEnvironment
lastTs <- view seLastTimestamp <$> getEnvironment lastTs <- view seLastTimestamp <$> getEnvironment
case posCurrentOrder position of case posCurrentOrder position of
@ -559,11 +559,11 @@ exitAtMarket position signalName = do
orderQuantity = (abs . posBalance) position, orderQuantity = (abs . posBalance) position,
orderPrice = Market, orderPrice = Market,
orderOperation = if posBalance position > 0 then Sell else Buy, orderOperation = if posBalance position > 0 then Sell else Buy,
orderSignalId = (SignalId inst signalName "") orderSignalId = (SignalId inst operationSignalName "")
} }
exitAtLimit :: (StateHasPositions s, MonadRobot m c s) => NominalDiffTime -> Price -> Position -> T.Text -> m Position exitAtLimit :: (StateHasPositions s, MonadRobot m c s) => NominalDiffTime -> Price -> Position -> T.Text -> m Position
exitAtLimit timeToCancel price position signalName = do exitAtLimit timeToCancel price position operationSignalName = do
lastTs <- view seLastTimestamp <$> getEnvironment lastTs <- view seLastTimestamp <$> getEnvironment
inst <- view seInstanceId <$> getEnvironment inst <- view seInstanceId <$> getEnvironment
case posCurrentOrder position of case posCurrentOrder position of
@ -584,7 +584,7 @@ exitAtLimit timeToCancel price position signalName = do
orderQuantity = (abs . posBalance) position, orderQuantity = (abs . posBalance) position,
orderPrice = Limit price, orderPrice = Limit price,
orderOperation = if posBalance position > 0 then Sell else Buy, orderOperation = if posBalance position > 0 then Sell else Buy,
orderSignalId = SignalId inst signalName "" orderSignalId = SignalId inst operationSignalName ""
} }
doNothing :: (MonadRobot m c s) => m () doNothing :: (MonadRobot m c s) => m ()

3
src/ATrade/RoboCom/Types.hs

@ -19,9 +19,6 @@ import Data.Aeson.Types
import qualified Data.HashMap.Strict as HM import qualified Data.HashMap.Strict as HM
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Time.Clock
import Text.Read hiding (String)
newtype Timeframe = newtype Timeframe =
Timeframe Integer deriving (Show, Eq) Timeframe Integer deriving (Show, Eq)

5
src/ATrade/RoboCom/Utils.hs

@ -15,12 +15,11 @@ module ATrade.RoboCom.Utils (
import ATrade.Types import ATrade.Types
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Lazy.Builder
import Data.Time.Calendar import Data.Time.Calendar
import Data.Time.Clock import Data.Time.Clock
import Text.Read hiding (String) import Text.Read hiding (String)
rescaleToDaily :: [Bar] -> [Bar] rescaleToDaily :: [Bar] -> [Bar]
rescaleToDaily (firstBar:restBars) = rescaleToDaily' restBars firstBar rescaleToDaily (firstBar:restBars) = rescaleToDaily' restBars firstBar

Loading…
Cancel
Save