It appears that context.getInstrument() and InputParameterInfo.getInstrument() always return null within IIndicator, which renders all calls to IHistory.getBars(instrument....) useless / unworkable!
(Unless you provide a pre-specified instrument - which is pointless for an indicator - it should be attached to a chart which in turn supplies the instrument).
Is there are workaround for this issue? I can rewrite the logic of the indicators to get multiple timeframes and parse them in a different fashion, but ideally I really only want to work with the last X daily/weekly/monthly bars, so history.getBar(...) is the logical and efficient approach... but it cannot be called from within an IIndicator!
Is this likely to be fixed anytime soon?
Consider the follow code snippet, specifically the parts highlighted in RED and the associated console output for detail example of the issue:
public class CPRIndicator implements IIndicator, IDrawingIndicator { private IndicatorInfo indicatorInfo; private InputParameterInfo[] inputParameterInfos; private OutputParameterInfo[] outputParameterInfos; private OptInputParameterInfo[] optInputParameterInfos; private IBar[][] inputs = new IBar[2][]; private double[][] outputs = new double[4][]; private InputParameterInfo calculationTimeframe, displayTimeframe; private DecimalFormat decimalFormat; private final GeneralPath generalPath = new GeneralPath(); private List<Point> tmpHandlesPoints = new ArrayList<Point>(); private boolean displayPeriod = false; private int offset = 2; private int noDays = 5; private IIndicatorContext context = null; private Instrument instrument = null; // private IFeedDescriptor feedDescriptor; private IHistory history = null; private IConsole console; private int period = 2; // default = DAILY int[] periodValues = new int[5]; String[] periodNames = new String[5]; private Color DemiBlack = new Color(64, 64, 64); private Color Orchid = new Color(218, 112, 214); private Color Gray = new Color(128, 128, 128); public static DecimalFormat df = new DecimalFormat("0.0000");
public void onStart(IIndicatorContext context) { this.context = context; console = context.getConsole(); instrument = context.getInstrument(); // feedDescriptor = context.getFeedDescriptor(); history = context.getHistory(); indicatorInfo = new IndicatorInfo("CENTRAL PIVOT RANGE ", "Pivot Range", "Overlap Studies", true, false, true, 2, 3, 4); indicatorInfo.setSparceIndicator(true); indicatorInfo.setRecalculateAll(true); calculationTimeframe = new InputParameterInfo("Calculation Input data", InputParameterInfo.Type.BAR); calculationTimeframe.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY); calculationTimeframe.setFilter(Filter.WEEKENDS); displayTimeframe = new InputParameterInfo("Display Input data", InputParameterInfo.Type.BAR); displayTimeframe.setFilter(Filter.WEEKENDS); inputParameterInfos = new InputParameterInfo[] { displayTimeframe, calculationTimeframe }; instrument = displayTimeframe.getInstrument(); if (instrument != null) { console.getOut().println("instrument="+instrument.toString()); // this never outputs anything }
periodValues[0] = 0; periodNames[0] = "Hourly"; periodValues[1] = 1; periodNames[1] = "4 Hour"; periodValues[2] = 2; periodNames[2] = "Daily"; periodValues[3] = 3; periodNames[3] = "Weekly"; periodValues[4] = 4; periodNames[4] = "Monthly";
optInputParameterInfos = new OptInputParameterInfo[] { new OptInputParameterInfo("Period", OptInputParameterInfo.Type.OTHER, new IntegerListDescription( period, periodValues, periodNames)), new OptInputParameterInfo("Number Days Lookback", OptInputParameterInfo.Type.OTHER, new IntegerRangeDescription(noDays, 2, 20, 1)), new OptInputParameterInfo("Display Period", OptInputParameterInfo.Type.OTHER, new BooleanOptInputDescription(displayPeriod)) };
outputParameterInfos = new OutputParameterInfo[] { createOutputParameterInfo("PV", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.LINE), createOutputParameterInfo("TC", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.LINE), createOutputParameterInfo("BC", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.LINE), createOutputParameterInfo("Separators", OutputParameterInfo.Type.DOUBLE, OutputParameterInfo.DrawingStyle.DASH_LINE ) }; outputParameterInfos[0].setColor(DemiBlack); outputParameterInfos[1].setColor(Orchid); outputParameterInfos[2].setColor(Orchid); outputParameterInfos[3].setColor(Gray); decimalFormat = new DecimalFormat("0.0000"); } private OutputParameterInfo createOutputParameterInfo(String name, Type type, DrawingStyle drawingStyle) { return new OutputParameterInfo(name, type, drawingStyle, false) {{ setDrawnByIndicator(true); }}; } public IndicatorResult calculate(int startIndex, int endIndex) { console.getOut().println("calculate: startIndex="+startIndex+" endIndex="+endIndex); // is the pivot period smaller than the chart period? if ( calculationTimeframe.getPeriod().isSmallerThan(displayTimeframe.getPeriod()) || calculationTimeframe.getPeriod().equals(displayTimeframe.getPeriod()) ) /* || calculationTimeframe.getPeriod().isSmallerThan(Period.DAILY_SUNDAY_IN_MONDAY) */ { new IndicatorResult(0, 0); } console.getOut().println("calculate: 2"); if (instrument != null) { console.getOut().println("instrument="+instrument.toString()); // this never outputs anything i.e. instrument is null } IBar bar = null; try { bar = history.getBar(context.getInstrument(), calculationTimeframe.getPeriod(), OfferSide.BID, 0); } // this fails because instrument is null catch (JFException e) { e.printStackTrace(); } if (bar != null) { double open = bar.getOpen(); console.getOut().println("today's open="+df.format(open)); // this never outputs anything either } //default values for indicator outputs Arrays.fill(outputs[0], Double.NaN); Arrays.fill(outputs[1], Double.NaN); Arrays.fill(outputs[2], Double.NaN); Arrays.fill(outputs[3], 0); // find the first noDays calculation timeframe bars, then convert to display timeframe start and endIndex IBar currentBar = null; IBar previousBar = null; int timeIndex = -1; for (int i = 0; i < noDays; i++) { // console.getOut().println("i="+i); // using this approach I can probably limit my inputs to just the chart timeframe in bars i.e. IBar inputs[][1] try { currentBar = history.getBar(instrument, calculationTimeframe.getPeriod(), OfferSide.BID, i); } catch (JFException e) { e.printStackTrace(); } try { previousBar = history.getBar(instrument, calculationTimeframe.getPeriod(), OfferSide.BID, i+1); } catch (JFException e) { e.printStackTrace(); } if (previousBar != null) { // console.getOut().println("previousBar !=null, i="+i); double high = previousBar.getHigh(); double low = previousBar.getLow(); double close = previousBar.getClose(); double p = (close + high + low)/3; double bc = (high + low)/2; double tc = (2*p - bc); if (bc > tc) { double temp = bc; bc = tc; tc = temp; } console.getOut().println("pivot("+i+") ="+ df.format(p)+ " high="+ df.format(high)+" low="+df.format(low)+" close="+df.format(close)); // get the open time of the current calculationTimeframe.bar, // and find this time value on the display (chart) timeframe timeIndex = getTimeIndex(currentBar.getTime(), inputs[0]); // found the right bar in the display timeframe, populate values // question - do we need to populate all indexes between each new // timeIndex with pivot values to display them or not? if (timeIndex > -1) { console.getOut().println(" time=" + df.format(timeIndex) + " pivot =" + df.format(p)); outputs[0][timeIndex] = p; // PV outputs[1][timeIndex] = tc; // TC outputs[2][timeIndex] = bc; // BC outputs[3][timeIndex] = 0d; // Separator bar } } } // IndicatorResult(int firstValueIndex, int numberOfElements, int lastValueIndex) int noElements = endIndex - startIndex - timeIndex; return new IndicatorResult(startIndex, noElements); // return new IndicatorResult(startIndex, endIndex); } private int getTimeIndex(long time, IBar[] target) { if (target == null) { return -1; }
int first = 0; int upto = target.length; while (first < upto) { int mid = (first + upto) / 2; IBar data = target[mid]; if (data.getTime() == time) { return mid; } else if (time < data.getTime()) { upto = mid; } else if (time > data.getTime()) { first = mid + 1; } } return -1; }
public IndicatorInfo getIndicatorInfo() { return indicatorInfo; }
public InputParameterInfo getInputParameterInfo(int index) { if (index <= inputParameterInfos.length) { return inputParameterInfos[index]; } return null; }
public int getLookback() { return 0; }
public int getLookforward() { return 0; }
public OptInputParameterInfo getOptInputParameterInfo(int index) { if (index <= optInputParameterInfos.length) { return optInputParameterInfos[index]; } return null; }
public OutputParameterInfo getOutputParameterInfo(int index) { if (index <= outputParameterInfos.length) { return outputParameterInfos[index]; } return null; }
public void setInputParameter(int index, Object array) { inputs[index] = (IBar[]) array; }
public void setOptInputParameter(int index, Object value) { if (index == 0) { int period = ((Integer) value).intValue(); switch (period) { case 0 : calculationTimeframe.setPeriod(Period.ONE_HOUR); break; case 1 : calculationTimeframe.setPeriod(Period.FOUR_HOURS); break; case 2 : calculationTimeframe.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY); break; case 3 : calculationTimeframe.setPeriod(Period.WEEKLY); break; case 4 : calculationTimeframe.setPeriod(Period.MONTHLY); break; default: calculationTimeframe.setPeriod(Period.DAILY_SUNDAY_IN_MONDAY); } } else if (index == 1) { noDays = (Integer) value; } else if (index == 2) { displayPeriod= (Boolean) value; } }
public void setOutputParameter(int index, Object array) { outputs[index] = (double[]) array; }
|