A BUY/SELL/HOLD Monoidal Strategy

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
module Analytics.Trading.Advice.SMA where

import Control.Arrow

import Analytics.Trading.Charts.SMA    -- http://lpaste.net/3427480809555099648
import Analytics.Trading.Data.Row      -- http://lpaste.net/109658
import Analytics.Trading.Data.Advice   -- http://lpaste.net/7565330848883408896

-- A solution to the advisor problem posted at
-- http://lpaste.net/5255378926062010368

advice :: Approach -> Symbol -> [Row] -> [Advice]

-- we're going to pursue a simple strategy here: when SMA-15 is above
-- SMA-50 we buy, when it's below we (short-(?)) sell.

-- very ... 'jittery,' I know for the aggressive approach

-- the curious approach is to buy/sell only at sma15 and sma50 crossings

advice Curious = adviseWith cross smaAnalysis

-- a passive approach is based on the slope and the relative position
-- of sma15 and sma50. See http://lpaste.net/955577567060951040

advice Passive =
   adviseWith (uncurry smaRecommendation . (head &&& tail)) smaAnalysis

-- The aggressive approach is buy when sma15 above sma50 and sell otherwise

advice Aggressive = adviseWith trending smaAnalysis

-- so we come up with a middle-ground approach based on slope and height

smaRecommendation :: SMArow -> [SMArow] -> Recommendation
smaRecommendation _ [] = Hold
smaRecommendation today (yesterday:_) =

-- here we'll say SMA-15 is ascending or descending based on just one day of
-- history, but we can extend that to more than one day, also, the difference
-- between sma15, sma50 or closing price may influence buy/sell call or even
-- how many shares, but we won't look at that today.

-- Hint: the function slope from Analytics.Trading.Charts.SMA ... 'may' help
-- Hint': There may be periods now when you wish neither to buy nor to sell!

   analysis (slope today yesterday reactor)
            (uncurry compare ((reactor &&& baseline) today))

-- our strategy is as we discussed above, and if neither condition is met,
-- we hold the line.

analysis :: Direction -> Ordering -> Recommendation
analysis Ascending  LT = Buy
analysis Descending GT = Sell
analysis _          _  = Hold

{-- So, from 

*Main> liftM (advice StarvingZ . smaAnalysis) (readRows "screens/buys/c.csv")

we get:

[Infallible {dayo = 2015-04-07, cls = $51.52, rec = Buy},
 Infallible {dayo = 2015-04-06, cls = $51.61, rec = Buy},
 Infallible {dayo = 2015-04-02, cls = $51.85, rec = Buy},
 Infallible {dayo = 2015-04-01, cls = $51.61, rec = Buy},
 Infallible {dayo = 2015-03-31, cls = $51.52, rec = Buy}, ...]

for every single day of the aggressive approach.

Our backtesting reveals these results (after we accommodate 'Hold' *AHEM*):

*Main Data.Monetary.USD> backtest (USD 1000000) advises ~>
... blah, blah, blah ... 
Results {startPool = $1000000.00, reserve = $864539.39, 
         shares = 2483.47 shares, closingPrice = $51.52, 
         totalEndValue = $992488.21, gain = -1%}

... as before ...

... btw, changing backtesting to the writer monad so the spewage is optional!

... SHEESH!

... DONE! SO:

*Main Control.Monad.Writer> let advicesIO = 
   liftM (\rows -> rows =>> analyst StarvingZ . smaAnalysis) 
         (readRows "UpDown/data/movers/screens/buys/c.csv")

and *Main Control.Monad.Writer> advicesIO >>= return . head ~>
Infallible {dayo = 2015-04-07, cls = $51.52, rec = Buy}

so we get:

*Main> liftM (fst . runWriter . backtest (USD 1000000)) advicesIO ~>
Results {startPool = $1000000.00, reserve = $864539.39, 
         shares = 2483.47 shares, closingPrice = $51.52, 
         totalEndValue = $992488.21, gain = -1%}

with StarvingZ.

Now let's try the HappyZ:

*Main> let advicesIO = liftM (\rows -> rows =>> analyst HappyZ . smaAnalysis) 
                             (readRows "UpDown/data/movers/screens/buys/c.csv")

as before, and then:

*Main> advicesIO >>= return . runWriter . backtest (USD 1000000) ~>
       let ans = it ~> fst
Results {startPool = $1000000.00, reserve = $976679.77, 
         shares = 444.22 shares, closingPrice = $51.52, 
         totalEndValue = $999566.46, gain = -1%}

... SO-the-much BETTER! *headdesk* ... let's look at the log:

*Main Control.DList> length $ dlToList  $ snd ans ~> 251

... SHOOT! I want to filter out the Holds! Need to log actual information
for that to make filtering simple (instead of filtering on Strings, ick!)

... need to update backtesting so that we log buy-sell-hold instead of text.

... done. So now we can print out our place-orders:

*Main> let log = snd ans
*Main> spew (DL ((take 10) . unDL log))
HoldOrder 2014-04-08
NoShortSelling 2014-04-09
NoShortSelling 2014-04-10
NoShortSelling 2014-04-11
NoShortSelling 2014-04-14 ...

*Main> let prs = partition ordered (dlToList log)
*Main> mapM_ print (take 27 (fst prs))
PlaceOrder 2014-05-02 Buy 104.75 shares $47.72 $5000.00 ... etc ...
PlaceOrder 2014-05-22 Sell 144.39 shares $47.14 $6806.62 ... etc ...
PlaceOrder 2014-06-10 Sell 40.78 shares $49.32 $2011.70 ... etc ...

Okay, so we have *Main> length (fst prs) ~> 234 place orders with na‚àö√òve strat.

How many place-orders do we have with the Ascending/below strat?

*Main> let advicesIO = liftM (\rows -> rows =>> analyst HappyZ . smaAnalysis)
                             (readRows "UpDown/data/movers/screens/buys/c.csv")
*Main> advicesIO >>= return . runWriter . backtest (USD 1000000)
*Main> let ans = it ~> fst ans ~>
Results {startPool = $1000000.00, reserve = $976679.77, 
         shares = 444.22 shares, closingPrice = $51.52, 
         totalEndValue = $999566.46, gain = -1%}

*Main> let (res, log) = ans
*Main> let prs = partition ordered (dlToList log)
*Main> length (fst prs) ~> 110

MUCH less thrash, but TOO much less thrash as we are missing buying and
selling opportunities ... we're MUCH too conservative now!

AND we can be more aggressive in the quantities bought and sold, based on the
distance of SMA-15 from close or from SMA-50 prices.

but ...

*Main> mapM_ print (fst prs) ~>
PlaceOrder 2014-04-29 Buy 103.82 shares $48.15 $5000.00
PlaceOrder 2014-04-30 Buy 103.83 shares $47.90 $4974.95
PlaceOrder 2014-05-01 Buy 103.64 shares $47.75 $4950.02
PlaceOrder 2014-05-06 Sell 31.13 shares $46.35 $1443.20
PlaceOrder 2014-05-07 Sell 28.01 shares $46.70 $1308.41
PlaceOrder 2014-05-08 Sell 25.21 shares $47.14 $1188.66 ...

We will look at improving the improvements to the strat (taking into account
close price and distances from SMA-50 and close) tomorrow.

Here's something lolneatz!

c : $500 loss, bac : $3000 loss, fas : $9200 gain ... hmmm! (yummy!)

kbh : $4800 gain hmmm! zsl: $30k gain !?!? wowzers! 

So, over all ~$40k gain for a total gain of 4% :/

These all are with the HappyZ strat. The StarvingZ strat for all of the above
lost money over the year. :/

So, still much to be done, still advantages of SMA to seize. We'll look further
tomorrow.

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. --}