cot-0.1.0.0: lib/Data/COT.hs
{-# LANGUAGE OverloadedStrings #-}
module Data.COT (COT, RptType(..), Rpt(..), Cat(..), parseCOT) where
import Control.Monad
import Data.Functor
import Data.Void
import Numeric
import Prettyprinter hiding (line)
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
type COT = [Rpt]
data RptType = Futes | OptsAndFutes
deriving (Eq, Read, Show)
instance Pretty RptType where
pretty Futes = "All Futures Combined Positions"
pretty OptsAndFutes = "Options and Futures Combined Positions"
data Rpt = Rpt{rptType :: RptType, date :: String, contract :: String
, exchange :: String, underlying :: String, code :: String
, openInterest :: Integer, changesFrom :: String, totTraders :: Integer
, prod :: Cat, swap :: Cat, managed :: Cat, other :: Cat}
deriving (Eq, Read, Show)
pwidth :: Num a => a
pwidth = 112
cwidth :: Num a => a
cwidth = 9
instance Pretty Rpt where
pretty r@Rpt{prod=p,swap=s,managed=m,other=o} = vsep
[ let pad = case rptType r of
Futes -> ""
OptsAndFutes -> " "
info = "Disaggregated Commitments of Traders-" ++ pad ++
show (pretty $ rptType r) ++ " as of " ++ date r
in pretty $ info ++ replicate (101 - length info) ' '
, ": Reportable Positions :"
, ":------------------------------------------------------------------------------------------------------------- :"
, ": Producer/Merchant : : : :"
, ": Processor/User : Swap Dealers : Managed Money : Other Reportables :"
, ": Long : Short : Long : Short :Spreading: Long : Short :Spreading: Long : Short :Spreading :"
, pretty $ replicate pwidth '-'
, let info = contract r ++ " - " ++ exchange r ++ " " ++ underlying r
pad = pwidth - length info - 1
in pretty $ info ++ replicate pad ' ' ++ ":"
, let oi = snum $ openInterest r
info = "CFTC Code #" ++ code r ++ replicate 52 ' ' ++ "Open Interest is " ++ replicate (9 - length oi) ' ' ++ oi
pad = pwidth - length info - 1
in pretty $ info ++ replicate pad ' ' ++ ":"
, ": Positions :"
, prow $ maybe "." snum <$> [posLong p, posShort p
, posLong s, posShort s, posSpread s
, posLong m, posShort m, posSpread m
, posLong o, posShort o, posSpread o]
, ": :"
, let info = "Changes from:" ++ replicate 5 ' ' ++ changesFrom r
in ":" <+> pretty info <> pretty (replicate (pwidth - length info - 3) ' ') <> ":"
, prow $ maybe "." snum <$> [changeLong p, changeShort p
, changeLong s, changeShort s, changeSpread s
, changeLong m, changeShort m, changeSpread m
, changeLong o, changeShort o, changeSpread o]
, ": :"
, ": Percent of Open Interest Represented by Each Category of Trader :"
, prow $ maybe "." sdub <$> [pctOpenLong p,pctOpenShort p
, pctOpenLong s, pctOpenShort s, pctOpenSpread s
, pctOpenLong m, pctOpenShort m, pctOpenSpread m
, pctOpenLong o, pctOpenShort o, pctOpenSpread o]
, ": :"
, let tot = snum $ totTraders r
in ": Number of Traders in Each Category Total Traders:"
<> pretty (replicate (6 - length tot) ' ' ++ tot ++ replicate (cwidth * 2 + 2) ' ') <+> ":"
, prow $ maybe "." snum <$> [tradersLong p, tradersShort p
, tradersLong s, tradersShort s, tradersSpread s
, tradersLong m, tradersShort m, tradersSpread m
, tradersLong o, tradersShort o, tradersSpread o]
, pretty $ replicate pwidth '-'
]
prow :: [String] -> Doc ann
prow es = ":" <> hsep [pretty $ replicate (cwidth - length e) ' ' ++ e | e <- es] <+> ":"
data Cat = Cat{posLong :: Maybe Integer, posShort :: Maybe Integer, posSpread :: Maybe Integer
, changeLong :: Maybe Integer, changeShort :: Maybe Integer, changeSpread :: Maybe Integer
, pctOpenLong :: Maybe Double, pctOpenShort :: Maybe Double, pctOpenSpread :: Maybe Double
, tradersLong :: Maybe Integer, tradersShort :: Maybe Integer, tradersSpread :: Maybe Integer
} deriving (Eq, Read, Show)
parseCOT :: String -> Either (ParseErrorBundle String Void) COT
parseCOT = parse (many parseRpt) ""
type Parser = Parsec Void String
parseRpt :: Parser Rpt
parseRpt = do
_ <- "Disaggregated Commitments of Traders-" <* hspace
rptType' <- ("All Futures Combined Positions" $> Futes) <|>
("Options and Futures Combined Positions" $> OptsAndFutes)
date' <- " as of " *> parseDate <* hspace <* eol
_ <- line "Reportable Positions"
_ <- line $ some "-"
_ <- count 3 $ someTill printChar eol
_ <- some "-" <* eol
contract' <- someTill printChar " - "
exchange' <- someTill printChar " "
underlying' <- someTill printChar " " <* hspace <* ":" <* eol
code' <- "CFTC Code #" *> count 6 alphaNumChar <* hspace
openInterest' <- "Open Interest is" *> hspace *> num <* hspace <* ":" <* eol
(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) <- line "Positions" *> line (row $ val num) <* line hspace
changesFrom' <- line $ "Changes from:" *> hspace *> parseDate
(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10) <- line (row $ val num) <* line hspace
_ <- line "Percent of Open Interest Represented by Each Category of Trader"
(o0,o1,o2,o3,o4,o5,o6,o7,o8,o9,o10) <- line (row $ val dub) <* line hspace
totTraders' <- line $ "Number of Traders in Each Category" *> hspace
*> "Total Traders:" *> hspace *> num
(t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10) <- line (row $ val num) <* some "-" <* eol
return Rpt{rptType=rptType', date=date', contract=contract'
, exchange=exchange', underlying=underlying', code=code'
, openInterest=openInterest', changesFrom=changesFrom', totTraders=totTraders'
, prod=Cat{posLong=p0, posShort=p1, posSpread=Nothing, changeLong=c0
, changeShort=c1, changeSpread=Nothing, pctOpenLong=o0, pctOpenShort=o1
, pctOpenSpread=Nothing, tradersLong=t0, tradersShort=t1, tradersSpread=Nothing}
, swap=Cat{posLong=p2, posShort=p3, posSpread=p4, changeLong=c2
, changeShort=c3, changeSpread=c4, pctOpenLong=o2, pctOpenShort=o3
, pctOpenSpread=o4, tradersLong=t2, tradersShort=t3, tradersSpread=t4}
, managed=Cat{posLong=p5, posShort=p6, posSpread=p7, changeLong=c5
, changeShort=c6, changeSpread=c7, pctOpenLong=o5, pctOpenShort=o6
, pctOpenSpread=o7, tradersLong=t5, tradersShort=t6, tradersSpread=t7}
, other=Cat{posLong=p8, posShort=p9, posSpread=p10, changeLong=c8
, changeShort=c9, changeSpread=c10, pctOpenLong=o8, pctOpenShort=o9
, pctOpenSpread=o10, tradersLong=t8, tradersShort=t9, tradersSpread=t10}
}
row :: Parser a -> Parser (a,a,a,a,a,a,a,a,a,a,a)
row p = (,,,,,,,,,,) <$> p <*> p <*> p <*> p <*> p <*> p <*> p <*> p <*> p
<*> p <*> p
parseDate :: Parser String
parseDate = do
m <- some letterChar <* " "
d <- some digitChar <* ", "
y <- some digitChar
return $ unwords [m, d ++ ",", y]
val :: Parser a -> Parser (Maybe a)
val p = fmap Just p <|> ("." *> hspace $> Nothing)
num :: Parser Integer
num = L.signed hspace $ fmap read go <* hspace
where
go :: Parser String
go = fmap join $ some $ fmap pure digitChar <|> ("," $> [])
snum :: Integer -> String
snum i = case show i of
'-':ns -> "-" <> fmt (reverse ns)
ns -> fmt $ reverse ns
where
fmt s = case s of
[] -> []
[d] -> [d]
[a,b] -> [b,a]
[a,b,c] -> [c,b,a]
a:b:c:ds -> fmt ds ++ "," ++ [c,b,a]
dub :: Parser Double
dub = L.signed hspace L.float <* hspace
sdub :: Double -> String
sdub d = showFFloat (Just 1) d ""
line :: Parser a -> Parser a
line p = ":" *> hspace *> p <* hspace <* ":" <* eol