Take the Monoid and Run(State-WriterT)!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
module Analytics.Trading.Advice.Backtesting where

import Prelude hiding (log)
import Control.Monad.Writer
import Data.Time

import Analytics.Trading.Advice.SMA   -- http://lpaste.net/109712
import Analytics.Trading.Data.Advice  -- xxx
import Analytics.Trading.Data.Order   -- xxx
import Control.DList                  -- http://lpaste.net/107607             
import Data.Monetary.USD              -- http://lpaste.net/109653

{-- BONUS --------------------------------------------------------------------

So, how did your advice turn out? If you followed your advice, would you still
be here now? Or would you be somewhere in the Bahamas, next to a red-faced guy
named Melvin, sipping Piña Coladas?

Backtest your advice with a pool of moolah and see if you came out ahead of
the game ... OR NOT! Dun-da-DUUUUUUAH! (there's that gravitas, again) --}

data Backtest = Results { startPool :: USD, reserve :: USD, 
                          shares :: Shares, closingPrice :: USD,
                          totalEndValue :: USD, gain :: Percentage }
   deriving (Eq, Ord, Show)

backtest :: Monad m => USD -> [Advice] -> WriterT (DList Order) m Backtest

{-- So.

There are ~200 trading days in a year. We, here, assume we're backtesting
over a year only, so let's commit 1 / 200th of our funds at most per day,
and that amount changes with our reserves.

On updown.com, each trade (buy or sell) has a $10 commission. Let's factor
that in as well.
--}

backtest pool advises = 
  bt pool (S 0) (cls (head advises)) (reverse advises) >>= \(res, S sh, clz) ->
  let endval = res + USD (sh * value clz)
      gain = floor (100 * value ((endval - pool) / pool))
  in  return (Results pool res (S sh) clz endval (P gain))

log :: Monad m => a -> WriterT (DList a) m ()
log = tell . DL . cons

spew :: Show a => DList a -> IO ()
spew = mapM_ print . dlToList

-- and to spew just a few lines of the log:
-- spew (DL ((take 10) . unDL log)) -- lolneatz!

bt :: Monad m => USD -> Shares -> USD -> [Advice] 
              -> WriterT (DList Order) m (USD, Shares, USD)
bt reserve committed close [] = return (reserve, committed, close)
bt reserve (S committed) _ (Infallible day close Buy:advises) =
   let outlay = reserve / 200 -- + 10
       sharesBought = value (outlay / close)
       order = PlaceOrder day Buy (S sharesBought) close outlay
   in  log order >> bt (reserve - outlay - commission order)
                       (S $ committed + sharesBought) close advises
bt reserve (S 0) _ (Infallible day close Sell:advises) =
   log (NoShortSelling day) >> bt reserve (S 0) close advises
bt reserve (S shares) _ (Infallible day close Sell:advises) =
   let sharesSold = if shares < 10 then shares else max 10 (shares / 10)
       sale = USD (sharesSold * value close)
       order = PlaceOrder day Sell (S sharesSold) close sale
   in  log order >> bt (reserve + sale - commission order)
                       (S $ shares - sharesSold) close advises
bt res shares _ (Infallible day close Hold:advises) =
   log (HoldOrder day) >> bt res shares close advises

{-- So:

*Analytics.Trading.Advice.Backtesting> 
advises >>= return . runWriter . backtest (USD 1000000) ~>
returns you (Results, DList Order)

(where advises were got from:
liftM (advice . smaAnalysis) (readRows "UpDown/data/movers/screens/buys/c.csv")

(TODO: update comment when advice takes different analysts' inputs ...)

(remember to load in Data.Row, and Charts.SMA))

Not short-selling on 2014-04-08 ...
2014-05-02: BUY 104.75 shares ...
2014-05-22: SELL 144.39 shares ...
etc ... 200 more times ...
then:
Results {startPool= $1000000.00, reserve= $864539.39, shares= 2483.47 shares, 
         closingPrice= $51.52, totalEndValue= $992488.21, gain= -1%}

Which says on an initial investment of $1,000,000.00 and after all brokerage
fees we lost ~$7500 for a 1% loss. Eh.

--}

{-- Standard disclaimer: I'm not an investment guru, so if you lose your shirt,
that's your problem, not mine, as these are educational exercises for your
edification and supreme enlightenment. Your financial well-being is your (sole)
responsibility, and blah-di-blah and more legal mumbo jumbo that nobody reads
anyway, except lawyers on their days off to poke fun at the gaping loopholes
in all these boilerplate disclaimers, anyway, so there it is. --}