In my previous post, I explained how can we calculate a number of indicators based on Stock prices and that, the value on those indicators is better exploited in algorithms rather that just plotted in charts. What kind of algorithm might that be?
I mentioned that one possibility is that each indicator has a "voice" or "opinion" and that the output of the algorithm is the sum of all those opinions, some sort of consensus. Then we can translate that consensus in actual actions: Buy and Sell orders.
How can we model those opinions? Basically, they are rules responding to certain mathematical expressions. One option to evaluate is a Java-based rule engine: Drools.
But first, take a look at the result of our calculations, so you can understand better. This graph shows a "score" given by our system on how bullish or bearish (that is, whether it has an upward/downward chance) a stock is. This score is calculated using simple indicators (Simple Moving Average, New MAX, New Min, etc.)
The price and indicators above, the SCORE indicator below |
- + 1 when the price is over the SMA(200).
- + 1 when the price is making new MAX taking into account the last 200 days.
- -1 when the price is under the SMA(200).
- - 1 when the price is making new MIN taking into account the last 200 days.
So we can extrapolate that:
- If the score was negative, and it has become positive; it´s likely for the price to go up.
- If the score was +1 and becomes +2, it gets even better.
- If the score becomes negative or makes new lows, it´s time to close our position and avoid more losses (see right part of the graph).
Note: Please keep in mind that the main purpose here is learning the technology and have fun, don´t expect Warren-Buffet-like returns!
Drools
As mentioned before, we will code these rules using Drools, which is a product that exists since a long time now. Actually, my first use of it was 6-7 years ago. The basic principles of Drools are quite straightforward to understand:
- You build a session (either stateless or stateful).
- You provide a set of inputs to it (a.k.a facts)
- The rules are executed on those facts. Each rule has a set of conditions (when) and a set of actions to be carried over if such conditions are met (then).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Init a new stateful session | |
KieSession kSession = kieContainer.newKieSession(); | |
// Insert a placeholder for operations and statistics | |
TradingSession session = new TradingSession(); | |
kSession.insert(session); | |
// We add an empty indicator to our stocks | |
quotations.forEach(quot -> quot.getIndicators().put("SCORE", new ScoreIndicator())); | |
// Submit each quotation and fire | |
for (AnalizedStockQuotation quot : quotations){ | |
kSession.insert(quot); | |
kSession.fireAllRules(); | |
} | |
// Recover results and clean up the memory | |
List<Operation> toReturn = session.getOperationList(); | |
kSession.dispose(); |
Note; Regarding how to set up your KieContainer bean to be injected in the class above, you may refer to this file.
Let´s take a look to an example rule, used in our application to calculate the SCORE indicator:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* If a stock price is under the yearly moving average (200 sessions) | |
* is probably following a downward trend | |
*/ | |
rule "SMASell" | |
dialect "java" | |
when | |
// We look for a Stock Quotation in the session memory | |
$stockData : AnalizedStockQuotation() | |
// Extract the price and the SMA200 indicator | |
$price : Double() from $stockData.value | |
$sma200d : Double() from $stockData.getIndicator("SMA").value | |
// If this evals to true, the "then" part is triggerd | |
eval($price < $sma200d); | |
then | |
// Diminish the value of SCORE by 1 | |
$stockData.getIndicator("SCORE").increaseValue(-1d); | |
end |
Easy right? We can have as many rules as we want, each one increasing or decreasing the score according to its own conditions.
Adding a new set of Buy/Sell rules
Now, we are going to define two new extra rules that will execute after the "voting" rules that will specify whether a Buy or Sell order applies (or none of them).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
rule "BUY_RULE" | |
dialect "java" | |
// This is an arbitrary negative number, indicating that this rule will be executed | |
// after any rule with default salience (0) | |
salience -49 | |
when | |
// We look for a stock quotation in the session memory | |
$stockData : AnalizedStockQuotation() | |
// We ensure that there is one trading session that does not have an open position | |
// If this evaluates to false, the rule stops being processed | |
$session : TradingSession(isOnMarket() == false) | |
$todayScore : Double () from $stockData.getIndicator("SCORE").value | |
$sma200d : Double() from $stockData.getIndicator("SMA").value | |
// Buy if score is positive and distance to SMA < 5% | |
eval($todayScore > 0 && (($stockData.value / $sma200d) < 1.05d)) | |
then | |
// We store in our session a new BUY order with the price and date specified | |
modify( $session ) { addOperation("BUY",$stockData.getTimestamp(),$stockData.getValue(),$todayScore) }; | |
end |
Here we just evaluate the score and give the BUY order if the price is over the SMA200 (but not too far of it).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
rule "SELL_RULE_STOP" | |
salience -50 | |
dialect "java" | |
when | |
$stockData : AnalizedStockQuotation() | |
// This time we need to be have an open position | |
$session : TradingSession($session.isOnMarket() == true) | |
$todayScore : Double () from $stockData.getIndicator("SCORE").value | |
$opening : Double() from $session.getOpeningPrice() | |
// Sell if score is negative or distance to opening > 15% | |
eval($todayScore < 0d || (($stockData.getValue() / $opening) > 1.15d)) | |
then | |
modify( $session ) { addOperation("SELL",$stockData.getTimestamp(),$stockData.getValue(),$todayScore) }; | |
end |
When closing, we take the opposite approach: Sell whenever the score turns negative or when we have a 15% profit. In incoming posts we will see that these arbitrary parameters are the key to optimize our system,
Example of the results
Now the fun part! How good are the results? These are the operations done on the stock shown above:
Operation Type | Price | Date | Yield |
BUY | 5,69 | Nov 7, 2012 | |
SELL | 6,59 | Jan 9, 2013 | 16% |
BUY | 5,86 | Feb 4, 2013 | |
SELL | 5,79 | Mar 19, 2013 | -1% |
BUY | 5,89 | Mar 20, 2013 | |
SELL | 5,81 | Mar 21, 2013 | -1% |
BUY | 5,82 | Aug 16, 2013 | |
SELL | 5,65 | Aug 19, 2013 | -3% |
BUY | 5,77 | Sep 12, 2013 | |
SELL | 6,71 | Oct 15, 2013 | 16% |
BUY | 6,06 | Dec 12, 2013 | |
SELL | 7,04 | Apr 1, 2014 | 16% |
BUY | 7,14 | Aug 7, 2014 | |
SELL | 7,17 | Oct 9, 2014 | 0% |
BUY | 7,25 | Nov 28, 2014 | |
SELL | 7,18 | Dec 1, 2014 | -1% |
BUY | 7,27 | Dec 2, 2014 | |
SELL | 7,14 | Dec 4, 2014 | -2% |
BUY | 7,38 | Dec 5, 2014 | |
SELL | 7,03 | Dec 9, 2014 | -5% |
BUY | 7,02 | Mar 31, 2015 | |
SELL | 6,85 | Apr 14, 2015 | -2% |
BUY | 6,79 | Jun 22, 2015 | |
SELL | 6,73 | Jun 24, 2015 | -1% |
BUY | 6,78 | Jun 26, 2015 | |
SELL | 6,33 | Jun 29, 2015 | -7% |
BUY | 6,76 | Jul 16, 2015 | |
SELL | 6,55 | Jul 24, 2015 | -3% |
- On an upward trend, the system behaves well, but probably a "Buy and Hold" strategy would have clearly beaten us (less commissions and less likely to miss a price climb).
- On a lateral market, the system keeps buying and selling at almost the same price. Small losses and lots of commissions paid!
- On a downward trend, the system keeps us out of the market. We don´t lose money but we don´t gain it as we don´t support short positions yet.
What´s next?
Obviously the rules shown here are very naive. What I would like is to take these very basic set of rules and the result obtained and perform iterative optimization over different stocks and different types of markets (bearish, bullish or lateral) and try to increase the return ratio.
Also, I would like to find some time to add some more indicators, specially signalling the behavior of the background trend.
What do you think? Feel free to share your thoughts!
Resources
Wow!!!!!! Great job man!
ReplyDelete