|
From: andrew g. <ag8...@ya...> - 2019-08-19 09:48:23
|
hi, (resending- previous post is apparently garbled) thanks for maintaining venice, posting this after a long break as i started trying out the updated version. exponential moving average: i think the implementation for MACD (chart) and EMA (exponential moving average) isn't 'correct'. the smoothing constant is often left open for users to fill up. what is more commonly practiced in charting stocks is to use the smoothing constant alpha = 2 / (N + 1), where N is the time period e.g. days. https://en.wikipedia.org/wiki/Moving_average#Relationship_between_SMA_and_EMA https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp the reason for using this is that it is rather common to compare between sma (simple moving average) and ema and using this formula would give it a 'center of mass' basis vs sma. hence it would be more common for users to refer to say a 20 days ema literally means an ema with the smoothing constant alpha = 2 / (N + 1) = 2 / (20 + 1) = 2/21 in addition ema is normally computed using the recurrence formula ema = ema + alpha x ( current_value - ema) this is pretty much correct, but that it is continuously compounding rather than based only on the values within a period. unlike sma. hence, rather than requiring the user to enter a smoothing constant, only the number of days or period is required, and the application should compute smoothing constant using alpha = 2 / (N+1). the issue of continuously compounding ema seemed harder to solve as current codes seem to be based on using values in an interval of periods. macd chart: apparently the current macd chart plots the 26 days ema vs 12 days ema. this would deviate from a commonly understood macd chart. what is really needed is the difference between the 2 ema. i.e. MACD=12-Period EMA ??? 26-Period EMA https://www.investopedia.com/terms/m/macd.asp https://en.wikipedia.org/wiki/MACD and another 9 period EMA is plotted as the 'signal' line. a main 'use' of macd is that its cross-over often coincides with the beginning of a new trend though not all the time. hence, mov's implementation which allows the user to specify the periods literally helps as the user can experiment with different periods for the 'fast' ema, 'slow' ema and the 'signal' (9 period) ema. i actually tried the simple moving average option for macd as well and i found that using sma almost always miss the turning points. my guess is that because sma has higher frequency components (side lobes) in the transfer function, it doesn't remove the higher frequency components as well as ema which is basically a low pass filter. i tidied up the MACD graph in my copy and omitted the sma implementation. i've also omitted the call to the ema function available as i wanted a 'continuously compounding' ema. i've also added a 'style' option. the 'line' option plots the macd and 9ema of macd as 2 curves the 'bar' option plots ( macd - 9ema ) as a bar graph it is also no longer plotted as the primary graph, but rather place below as a secondary graph so that it looks like an indicator ============?? nz.org.venice.chart.graph.MACDGraph.java ================ /* Merchant of Venice - technical analysis software for the stock market. ???? Copyright (C) 2002 Andrew Leppard (ale...@pi...) ???? This program is free software; you can redistribute it and/or modify ???? it under the terms of the GNU General Public License as published by ???? the Free Software Foundation; either version 2 of the License, or ???? (at your option) any later version. ???? This program is distributed in the hope that it will be useful, ???? but WITHOUT ANY WARRANTY; without even the implied warranty of ???? MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.?? See the ???? GNU General Public License for more details. ???? You should have received a copy of the GNU General Public License ???? along with this program; if not, write to the Free Software ???? Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307?? USA */ package nz.org.venice.chart.graph; import java.awt.*; import java.util.HashMap; import java.util.Iterator; import java.util.List; import nz.org.venice.chart.*; import nz.org.venice.chart.source.*; import nz.org.venice.parser.EvaluationException; import nz.org.venice.quote.QuoteFunctions; import nz.org.venice.util.Locale; import nz.org.venice.util.TradingDate; /** ??* Moving Average Convergence Divergence graph. This graph draws two ??* graphs macd (ema slow (26) - ema fast (12) and signal line ema9 of macd ??* When the two lines cross they indicate a possible turning point ??*/ public class MACDGraph extends AbstractGraph { ?????? private Graphable mSignal; ?????? private Graphable mMACD; ?????? private String mStyle = MACDGraphUI.LINE; ?????? /** ???????? * Create a new MACD graph. ???????? * ???????? * @param?????? source?????? the source to create two moving averages from ???????? */ ?????? public MACDGraph(GraphSource source) { ?????? super(source); ?????????????? setSettings(new HashMap()); ?????? } ?????? /** ???????? * Create a new MACD graph. ???????? * ???????? * @param?????? source?????? the source to create two moving averages from ???????? * @param?????? settings the settings of the graph ???????? */ ?????? public MACDGraph(GraphSource source, HashMap settings) { ?????? super(source); ?????? setSettings(settings); ?????? } ?????? /** ???????? * Create a new MACD graph according to Exponential Moving Average. ???????? * ???????? * @param?????? source?????? the source to create two moving averages from ???????? * @param?????? periodOne?????? period of one of the moving averages ???????? * @param?????? periodTwo?????? period of the other moving average ???????? * @param?????? periodSignal?????? signal period ???????? * @param?????? ema???????? ema or sma ???????? */ ?????? private void createMACDGraph(Graphable source, int periodOne, int periodTwo, int periodSignal) { ?????????????? // Create averaged data sources. ?????????????? int slowPeriod = Math.max(periodOne, periodTwo); ?????????????? int fastPeriod = Math.min(periodOne, periodTwo); ?????????????? mMACD = createMACD(source, slowPeriod, fastPeriod); ?????????????? mSignal = createSignal(mMACD, periodSignal); ?????????????? if(mStyle == MACDGraphUI.BAR) { ?????????????????????? Graphable macd = new Graphable(); ?????????????????????? Iterator<TradingDate> iter = mMACD.iterator(); ?????????????????????? while(iter.hasNext()) { ?????????????????????????????? TradingDate date = iter.next(); ?????????????????????????????? double delta = mMACD.getY(date) - mSignal.getY(date); ?????????????????????????????? macd.putY(date, delta); ?????????????????????? } ?????????????????????? mMACD = macd; ?????????????? } ?????? } ?????? public static Graphable createMACD(Graphable source, int slowperiod, int fastperiod) { ?????????????? Graphable macd = new Graphable(); ?????????????? TradingDate date = (TradingDate) source.getStartX(); ?????????????? if (slowperiod == 0) ?????????????????????? slowperiod = 26; ?????????????? if (fastperiod == 0) ?????????????????????? fastperiod = 12; ?????????????? double slowsmooth = 2.0D / ((double) slowperiod + 1.0D); ?????????????? double fastsmooth = 2.0D / ((double) fastperiod + 1.0D); ?????????????? double slowaverage = 0.0D, fastaverage = 0.0D; ?????????????? for (Iterator<TradingDate> iterator = source.iterator(); iterator.hasNext();) { ?????????????????????? date = (TradingDate) iterator.next(); ?????????????????????? slowaverage = slowaverage + slowsmooth * (source.getY(date) - slowaverage); ?????????????????????? fastaverage = fastaverage + fastsmooth * (source.getY(date) - fastaverage); ?????????????????????? double cd = fastaverage - slowaverage; ?????????????????????? macd.putY(date, new Double(cd)); ?????????????? } ?????????????? return macd; ?????? } ?????? public static Graphable createSignal(Graphable source, int period) { ?????????????? Graphable signal = new Graphable(); ?????????????? TradingDate date = (TradingDate) source.getStartX(); ?????????????? if(period == 0) period = 9; ?????????????? double smooth = 2.0D / ((double) period + 1.0D); ?????????????? double ema = 0.0D; ?????????????? for (Iterator<TradingDate> iterator = source.iterator(); iterator.hasNext();) { ?????????????????????? date = iterator.next(); ?????????????????????? ema = ema + smooth * (source.getY(date) - ema); ?????????????????????? signal.putY(date, new Double(ema)); ?????????????? } ?????????????? return signal; ?????? } ?????? public void render(Graphics g, Color colour, int xoffset, int yoffset, double horizontalScale, double verticalScale, ?????????????????????? double topLineValue, double bottomLineValue, List dates, boolean vertOrientation) { ?????????????? // We ignore the graph colours and use our own custom colours ?????????????? if (mStyle == MACDGraphUI.BAR) { ?????????????????????? g.setColor(Color.blue); ?????????????????????? GraphTools.renderBar(g, mMACD, xoffset, yoffset, horizontalScale, verticalScale, ?????????????????????????????????????? topLineValue, bottomLineValue, dates, vertOrientation); ?????????????? } else { ?????????????????????? // Fast moving line ?????????????????????? g.setColor(Color.red.darker()); ?????????????????????? GraphTools.renderLine(g, mMACD, xoffset, yoffset, horizontalScale, verticalScale, topLineValue, ?????????????????????????????????????? bottomLineValue, dates, vertOrientation); ?????????????????????? // Slow moving line ?????????????????????? g.setColor(Color.green.darker()); ?????????????????????? GraphTools.renderLine(g, mSignal, xoffset, yoffset, horizontalScale, verticalScale, topLineValue, ?????????????????????????????????????? bottomLineValue, dates, vertOrientation); ?????????????? } ?????? } ?????? public String getToolTipText(Comparable x, int y, int yoffset, ???????????????????????????????? double verticalScale, ???????????????????????????????? double bottomLineValue) ?????? { ?????? return null; // we never give tool tip information ?????? } ?????? // Highest Y value is the highest of both the moving averages ?????? public double getHighestY(List x) { ?????? double signalY = mSignal.getHighestY(x); ?????? double macdY = mMACD.getHighestY(x); ?????? if (mStyle == MACDGraphUI.BAR) { ?????????????? signalY = 0.0; ?????? } ?????? return ?????????????? signalY > macdY? ?????????????? signalY : ?????????????? macdY; ?????? } ?????? // Lowest Y value is the lowest of both the moving averages ?????? public double getLowestY(List x) { ?????? double signalY = mSignal.getLowestY(x); ?????? double macdY = mMACD.getLowestY(x); ?????? if (mStyle == MACDGraphUI.BAR) { ?????????????? signalY = 0.0; ?????? } ?????? return ?????????????? signalY < macdY? ?????????????? signalY : ?????????????? macdY; ?????? } ?????? /** ???????? * Return the name of this graph. ???????? * ???????? * @return?????? <code>MACD</code> ???????? */ ?????? public String getName() { ?????? return Locale.getString("MACD"); ?????? } ?????? public boolean isPrimary() { ?????????????? return false; ?????? } ?????? public void setSettings(HashMap settings) { ?????????????? super.setSettings(settings); ?????????????? // Retrieve values from hashmap ?????????????? int periodFirstAverage = MACDGraphUI.getPeriodFirstAverage(settings); ?????????????? int periodSecondAverage = MACDGraphUI.getPeriodSecondAverage(settings); ?????????????? int periodSignal = MACDGraphUI.getSignalPeriod(settings); ?????????????? mStyle = MACDGraphUI.getStyle(settings); ?????????????? createMACDGraph(getSource().getGraphable(), periodFirstAverage, periodSecondAverage, periodSignal); ?????? } ?????? /** ???????? * Return the graph's user interface. ???????? * ???????? * @param settings the initial settings ???????? * @return user interface ???????? */ ?????? public GraphUI getUI(HashMap settings) { ?????????????? return new MACDGraphUI(settings); ?????? } } ============?? nz.org.venice.chart.graph.MACDGraphUI.java ================ /* Merchant of Venice - technical analysis software for the stock market. ???? Copyright (C) 2002 Andrew Leppard (ale...@pi...) ???? This program is free software; you can redistribute it and/or modify ???? it under the terms of the GNU General Public License as published by ???? the Free Software Foundation; either version 2 of the License, or ???? (at your option) any later version. ???? This program is distributed in the hope that it will be useful, ???? but WITHOUT ANY WARRANTY; without even the implied warranty of ???? MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.?? See the ???? GNU General Public License for more details. ???? You should have received a copy of the GNU General Public License ???? along with this program; if not, write to the Free Software ???? Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307?? USA */ package nz.org.venice.chart.graph; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.HashMap; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; import nz.org.venice.ui.GridBagHelper; import nz.org.venice.util.Locale; /** ??* The MACD graph user interface. ??* ??* @author Andrew Leppard ??* @see MACDGraph ??*/ public class MACDGraphUI implements GraphUI, ActionListener { ?????? // String name of settings ?????? private final static String PERIOD_FIRST_AVERAGE = "MACD period first"; ?????? private final static String PERIOD_SECOND_AVERAGE = "MACD period second"; ?????? private final static String PERIOD_SIGNAL = "MACD signal period"; ?????? // Limits ?????? private final static int MINIMUM_PERIOD = 2; ?????? // Default values from Technical Analysis Explained by Gerald Appel. ?????? private final static int DEFAULT_PERIOD_FIRST_AVERAGE = 26; ?????? private final static int DEFAULT_PERIOD_SECOND_AVERAGE = 12; ?????? private final static int DEFAULT_PERIOD_SIGNAL = 9; ?????? // The graph's user interface ?????? private JPanel panel; ?????? private JPanel panelTextBoxes; ?????? // Avg and EMA, i.e. Simple Moving Average and Exponential Moving Average ?????? private ButtonGroup group = new ButtonGroup(); ?????? private JRadioButton[] radioButtons = new JRadioButton[2]; ?????? // Details of Averages ?????? private JTextField periodFirstAverageTextField; ?????? private JTextField periodSecondAverageTextField; ?????? private JTextField periodSignalTextField; ?????? // styles ?????? public final static String BAR = "Bar"; ?????? public final static String LINE = "Line"; ?????? public final static String STYLE = "Style"; ?????? private final static String DEFAULT_STYLE = LINE; ?????? private String actualstyle = DEFAULT_STYLE; ?????? /** ???????? * Create a new MACD user interface with the initial settings. ???????? * ???????? * @param settings the initial settings ???????? */ ?????? public MACDGraphUI(HashMap settings) { ?????????????? buildPanel(); ?????????????? setSettings(settings); ?????? } ?????? /** ???????? * Build the user interface JPanel. ???????? */ ?????? private void buildPanel() { ?????????????? panel = new JPanel(); ?????????????? panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); ?????????????? panelTextBoxes = new JPanel(); ?????????????? GridBagLayout layout = new GridBagLayout(); ?????????????? GridBagConstraints c = new GridBagConstraints(); ?????????????? panelTextBoxes.setLayout(layout); ?????????????? c.weightx = 1.0; ?????????????? c.ipadx = 5; ?????????????? c.anchor = GridBagConstraints.WEST; ?????????????? radioButtons[0] = new JRadioButton("line chart"); ?????????????? radioButtons[0].setActionCommand(LINE); ?????????????? group.add(radioButtons[0]); ?????????????? radioButtons[0].setAlignmentX(Component.LEFT_ALIGNMENT); ?????????????? radioButtons[0].addActionListener(this); ?????????????? panel.add(radioButtons[0]); ?????????????? radioButtons[1] = new JRadioButton("bar chart"); ?????????????? radioButtons[1].setActionCommand(BAR); ?????????????? group.add(radioButtons[1]); ?????????????? radioButtons[1].setAlignmentX(Component.LEFT_ALIGNMENT); ?????????????? radioButtons[1].addActionListener(this); ?????????????? panel.add(radioButtons[1]); ?????????????? periodFirstAverageTextField = GridBagHelper.addTextRow(panelTextBoxes, Locale.getString("PERIOD_FIRST_AVERAGE"), "", ???????????????????????????????????????????????????????????????????????????????????????????????????? layout, c, 8); ?????????????? periodSecondAverageTextField = GridBagHelper.addTextRow(panelTextBoxes, Locale.getString("PERIOD_SECOND_AVERAGE"), "", ???????????????????????????????????????????????????????????????????????????????????????????????????? layout, c, 8); ?????????????? periodSignalTextField = GridBagHelper.addTextRow(panelTextBoxes, "Signal period", "", ?????????????????????????????? layout, c, 8); ?????????????? panelTextBoxes.setAlignmentX(Component.LEFT_ALIGNMENT); ?????????????? panel.add(panelTextBoxes); ?????? } ?????? public String checkSettings() { ?????? return checkSettings(getSettings()); ?????? } ?????? public String checkSettings(HashMap settings) { ?????????????? // Check periods ?????????????? String periodFirstAverageString = (String)settings.get(PERIOD_FIRST_AVERAGE); ?????????????? String periodSecondAverageString = (String)settings.get(PERIOD_SECOND_AVERAGE); ?????????????? String periodSignal = (String)settings.get(PERIOD_SIGNAL); ?????????????? int period; ?????????????? try { ?????????????????????? period = Integer.parseInt(periodFirstAverageString); ?????????????? } ?????????????? catch(NumberFormatException e) { ?????????????????????? return Locale.getString("ERROR_PARSING_NUMBER", periodFirstAverageString); ?????????????? } ?????????????? if (period < MINIMUM_PERIOD) ?????????????????????? return Locale.getString("PERIOD_TOO_SMALL"); ?????????????? try { ?????????????????????? period = Integer.parseInt(periodSecondAverageString); ?????????????? } ?????????????? catch(NumberFormatException e) { ?????????????????????? return Locale.getString("ERROR_PARSING_NUMBER", periodSecondAverageString); ?????????????? } ?????????????? if (period < MINIMUM_PERIOD) ?????????????????????? return Locale.getString("PERIOD_TOO_SMALL"); ?????????????? try { ?????????????????????? period = Integer.parseInt(periodSignal); ?????????????? } ?????????????? catch(NumberFormatException e) { ?????????????????????? return Locale.getString("ERROR_PARSING_NUMBER", periodSecondAverageString); ?????????????? } ?????????????? if (period < MINIMUM_PERIOD) ?????????????????????? return Locale.getString("PERIOD_TOO_SMALL"); ?????????????? // Settings are OK ?????????????? return null; ?????? } ?????? public HashMap getSettings() { ?????????????? HashMap settings = new HashMap(); ?????????????? settings.put(PERIOD_FIRST_AVERAGE, periodFirstAverageTextField.getText()); ?????????????? settings.put(PERIOD_SECOND_AVERAGE, periodSecondAverageTextField.getText()); ?????????????? settings.put(PERIOD_SIGNAL, periodSignalTextField.getText()); ?????????????? settings.put(PERIOD_SIGNAL, periodSignalTextField.getText()); ?????????????? settings.put(STYLE, actualstyle); ?????????????? return settings; ?????? } ?????? public void setSettings(HashMap settings) { ?????????????? actualstyle = getStyle(settings); ?????????????? if (actualstyle.compareTo(LINE)==0) ?????????????????????? radioButtons[0].setSelected(true); ?????????????? if (actualstyle.compareTo(BAR)==0) ?????????????????????? radioButtons[1].setSelected(true); periodFirstAverageTextField.setText(Integer.toString(getPeriodFirstAverage(settings))); periodSecondAverageTextField.setText(Integer.toString(getPeriodSecondAverage(settings))); periodSignalTextField.setText(Integer.toString(getSignalPeriod(settings))); ?????? } ?????? public JPanel getPanel() { ?????????????? return panel; ?????? } ?????? /** ???????? * Retrieve the average type (EMA or SMA) from the settings hashmap. If the hashmap ???????? * is empty, then return the default average type (i.e. EMA). ???????? * ???????? * @param settings the settings ???????? * @return the average type ???????? */ ?????? public static String getStyle(HashMap settings) { ?????????????? String style = DEFAULT_STYLE; ?????????????? String stylesaved = (String)settings.get(STYLE); ?????????????? if(stylesaved != null) { ?????????????????????? style = stylesaved; ?????????????? } ?????????????? return style; ?????? } ?????? /** ???????? * Retrieve the period of the first average from the settings hashmap. If the hashmap ???????? * is empty, then return the default. ???????? * ???????? * @param settings the settings ???????? * @return the period ???????? */ ?????? public static int getPeriodFirstAverage(HashMap settings) { ?????????????? int period = DEFAULT_PERIOD_FIRST_AVERAGE; ?????????????? String text = (String)settings.get(PERIOD_FIRST_AVERAGE); ?????????????? if(text != null) { ?????????????????????? try { ?????????????????????????????? period = Integer.parseInt(text); ?????????????????????? } ?????????????????????? catch(NumberFormatException e) { ?????????????????????????????? // Value should already be checked ?????????????????????????????? assert false; ?????????????????????? } ?????????????? } ?????????????? return period; ?????? } ?????? /** ???????? * Retrieve the period of the second average from the settings hashmap. If the hashmap ???????? * is empty, then return the default. ???????? * ???????? * @param settings the settings ???????? * @return the period ???????? */ ?????? public static int getPeriodSecondAverage(HashMap settings) { ?????????????? int period = DEFAULT_PERIOD_SECOND_AVERAGE; ?????????????? String text = (String)settings.get(PERIOD_SECOND_AVERAGE); ?????????????? if(text != null) { ?????????????????????? try { ?????????????????????????????? period = Integer.parseInt(text); ?????????????????????? } ?????????????????????? catch(NumberFormatException e) { ?????????????????????????????? // Value should already be checked ?????????????????????????????? assert false; ?????????????????????? } ?????????????? } ?????????????? return period; ?????? } ?????? /** ???????? * Retrieve the signal period from the settings hashmap. If the hashmap ???????? * is empty, then return the default. ???????? * ???????? * @param settings the settings ???????? * @return the period ???????? */ ?????? public static int getSignalPeriod(HashMap settings) { ?????????????? int period = DEFAULT_PERIOD_SIGNAL; ?????????????? String text = (String)settings.get(PERIOD_SIGNAL); ?????????????? if(text != null) { ?????????????????????? try { ?????????????????????????????? period = Integer.parseInt(text); ?????????????????????? } ?????????????????????? catch(NumberFormatException e) { ?????????????????????????????? // Value should already be checked ?????????????????????????????? assert false; ?????????????????????? } ?????????????? } ?????????????? return period; ?????? } ?????? @Override ?????? public void actionPerformed(ActionEvent e) { ?????????????? if (e.getActionCommand().equals(LINE)) { ?????????????????????? actualstyle = LINE; ?????????????? } else if (e.getActionCommand().equals(BAR)) { ?????????????????????? actualstyle = BAR; ?????????????? } ?????? } } |