I have written the following code that draws a rectangle for bearish engulfing patterns for two inputed timeframes. I set the defaults to daily and 4 hours. When I am on the daily chart I expect that only the daily rectangles should appear for one candle and when I am on the 4 hour chart, the daily rectangle region should extend for 6 candles whiles the 4-hour rectangle shows for only one candle, and so on as I move to lower time frames.
The general idea is the rectangle should extend to cover the candles that sum it's period. But that is not happening, only one candle appears always. How can I solve this? Here's my code below:
int numBars = 1;
extern ENUM_TIMEFRAMES higherRegionPeriod = PERIOD_D1;
extern ENUM_TIMEFRAMES lowerRegionPeriod = PERIOD_H4;
extern color higherRegionColorSupply = clrRed;
extern color lowerRegionColorSupply = clrChocolate;
bool isBearishEngulfing(int current, ENUM_TIMEFRAMES cDuration) {
if((iClose(_Symbol,cDuration,current) < iOpen(_Symbol,cDuration,current)) &&
(iClose(_Symbol,cDuration,current + 1) > iOpen(_Symbol,cDuration,current + 1)) &&
(iOpen(_Symbol,cDuration,current) > iClose(_Symbol,cDuration,current + 1)) &&
(iClose(_Symbol,cDuration,current) < iOpen(_Symbol,cDuration,current + 1)))
return true;
return false;
}
void showRectangles() {
for (int i=300;i>=1;i--) {
if(isBearishEngulfing(i, lowerRegionPeriod)) {
drawBearRectangle(i + 1,iHigh(_Symbol,lowerRegionPeriod,i + 1),iOpen(_Symbol,lowerRegionPeriod,i + 1), lowerRegionPeriod, lowerRegionColorSupply);
}
if(isBearishEngulfing(i, higherRegionPeriod)) {
drawBearRectangle(i + 1,iHigh(_Symbol,higherRegionPeriod,i + 1),iOpen(_Symbol,higherRegionPeriod,i + 1), higherRegionPeriod, higherRegionColorSupply);
}
}
}
bool drawBearRectangle(int candleInt,const double top,const double bottom, ENUM_TIMEFRAMES cDuration, color rectColor)
{
const datetime starts=iTime(_Symbol,cDuration,candleInt);
const datetime ends=starts+PeriodSeconds()*NumBars;
const string name=prefix+"_"+(candleInt>0?"DEMAND":"SUPPLY")+"_"+TimeToString(starts);
if(!ObjectCreate(0,name,OBJ_RECTANGLE,0,0,0,0,0))
{
printf("%i %s: failed to create %s. error=%d",__LINE__,__FILE__,name,_LastError);
return false;
}
ObjectSetInteger(0,name,OBJPROP_TIME1,starts);
ObjectSetInteger(0,name,OBJPROP_TIME2,ends);
ObjectSetDouble(0,name,OBJPROP_PRICE1,bottom);
ObjectSetDouble(0,name,OBJPROP_PRICE2,top);
ObjectSetInteger(0,name,OBJPROP_COLOR, rectColor);
ObjectSetInteger(0,name,OBJPROP_STYLE, STYLE_DASHDOT);
ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
ObjectSetInteger(0,name,OBJPROP_FILL, false);
return true;
}
void OnDeinit(const int reason){ObjectsDeleteAll(0,prefix);}
void OnTick()
{
if(!isNewBar(higherRegionPeriod))
return; //not necessary but waste of time to check every second
if(!isNewBar(lowerRegionPeriod))
return; //not necessary but waste of time to check every second
showRectangles();
}
bool isNewBar(ENUM_TIMEFRAMES cDuration)
{
static datetime lastbar;
datetime curbar = (datetime)SeriesInfoInteger(_Symbol,cDuration,SERIES_LASTBAR_DATE);
if(lastbar != curbar)
{
lastbar = curbar;
return true;
}
return false;
Related
In Processing, I'm building a simple buttons interface. And the idea is that when you click on a button in the sketch. A text snippet from a different macOS application will launch a text snippet box.
At the moment, this text snippet box will be launched if I type a word in an email. So let's say I type (sample-a) in the email, and this will open a text snippet box that I have set up with this application.
But I want to trigger (sample-a) on a button click in Processing and not have to type this word in the email.
I searched the internet and looked at io.popen, os.execute and launch. But I wondered what the best way is to trigger a macOS "word" from Processing on a button click? Maybe do something with an echo command?
I hope someone can give me some tips or have an example code to create this function?
*added updated code:
// Import library for textfields
import g4p_controls.*;
GTextField txf1;
String sample;
boolean background = true;
// Button setup
final int btnX = 100;
final int btnY = 100;
final int btnW = 200;
final int btnH = 200;
public void setup() {
size(400, 600);
background(209, 209, 209);
// Button
rect(btnX, btnY, btnW, btnH);
// Textfield setup
txf1 = new GTextField(this, 100, 400, 200, 20);
}
public void draw() {
if (keyPressed && key == ENTER) {
}
}
public void handleTextEvents(GEditableTextControl textcontrol, GEvent event) {
if (txf1 == textcontrol && event == GEvent.ENTERED) {
sample = txf1.getText();
}
}
// Button trigger
void mousePressed() {
if (mouseX >= btnX && mouseX <= btnX + btnW && mouseY >= btnY && mouseY <= btnY + btnH) {
println("button clicked");
exec("open", "/Applications/TextExpander.app");
txf1.setText("sample");
}
}
New code for button interface with ControlP5 and Robot Class
import controlP5.*;
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
boolean background = true;
// Robot function
Robot robot;
String keyString="template-1";
Robot1 robot1;
String keyString="template-2";
ControlP5 gui;
void setup() {
size(1440, 900);
gui = new ControlP5(this);
//Add a Button
gui.addButton("Template 1")
.setPosition(50, 50)
.setSize(100, 100)
.setValue(0)
.activateBy(ControlP5.RELEASE);
;
gui.addButton("Template 2")
.setPosition(200, 50)
.setSize(100, 100)
.setValue(0)
.activateBy(ControlP5.RELEASE);
;
// Robot function
try {
robot = new Robot();
robot1 = new Robot1();
}
catch (AWTException ex) {
System.out.println(ex.getMessage());
}
frameRate(1);
// Robot function
}
public void Template1(int value) {
println("Template 1 Button pressed");
sendKeys(robot, keyString);
}
public void Template2(int value) {
println("Template 2 Button pressed");
sendKeys(robot1, keyString);
}
public void controlEvent(ControlEvent theEvent) {
}
// Robot function for Template 1
void sendKeys(Robot robot, String keys) {
for (char c : keys.toCharArray()) {
int keyCode = KeyEvent.getExtendedKeyCodeForChar(c);
if (KeyEvent.CHAR_UNDEFINED == keyCode) {
throw new RuntimeException(
"Key code not found for character '" + c + "'");
}
robot.keyPress(keyCode);
robot.delay(100);
robot.keyRelease(keyCode);
robot.delay(100);
noLoop();
}
}
// Robot function for Template 2
void sendKeys(Robot1 robot1, String keys) {
for (char c : keys.toCharArray()) {
int keyCode = KeyEvent.getExtendedKeyCodeForChar(c);
if (KeyEvent.CHAR_UNDEFINED == keyCode) {
throw new RuntimeException(
"Key code not found for character '" + c + "'");
}
robot1.keyPress(keyCode);
robot1.delay(100);
robot1.keyRelease(keyCode);
robot1.delay(100);
noLoop();
}
}
void draw() {
}
Here is your source code as posted in the comments:
boolean background = true;
void setup() {
size(400, 400);
}
void draw() {
rect(100, 100, 200, 200);
}
boolean isMouseOver(int x, int y, int w, int h) {
if (mouseX >= x && mouseX <= x + w && mouseY >= y && mouseY <= y + h) {
return true;
}
return false;
}
void mousePressed() {
// if (isMouseOver(width/2, height/2, 100, 100) == true){
if (isMouseOver(100, 100, 200, 200) == true) {
println("button clicked");
// Process proc = I'm doing more research on this exec("open", "/Applications/TextExpander.app");
// name of the TextExpander abbreviation / shortcut = "(sample-a)"
// code that fire the word "(sample-a)" so that a TextExpander snippet will popup
}
}
As written there is a problem with mousePressed(); it will only pick up a button click toward the bottom of the button. Clicks at the top do nothing. That's due to an error in the line if(isMouseOver()== true) because the parameters are incorrect; they should be the same as the parameters that you used to create the rect initially, ie (100,100,200,200).
Alternate revision which shortens the code by obviating the function isMouseOver(). Rectangle coordinates are made constants so that if you want to change the size of the button later you only will have to change the parameter once instead of finding multiple occurrences in your code. Your initial version will certainly work as is, but I am only showing you a possible way to improve it. As you continue to experiment we can edit this post to reflect changes. Keep on experimenting and you should hopefully achieve your goal.
boolean background = true;
final int btnX = 100;
final int btnY = 100;
final int btnW = 200;
final int btnH = 200;
void setup() {
size(400, 400);
rect(btnX, btnY, btnW, btnH);
}
void draw() {
}
void mousePressed() {
if (mouseX >= btnX && mouseX <= btnX + btnW && mouseY >= btnY && mouseY <= btnY + btnH) {
println("button clicked");
// Process proc = I'm doing more research on this exec("open", "/Applications/TextExpander.app");
// name of the TextExpander abbreviation / shortcut = "(sample-a)"
// code that fire the word "(sample-a)" so that a TextExpander snippet will popup
}
}
Robot revision:
You don't need two robots; one will suffice. Likewise you don't need two sendKeys() function. Use one robot and send it a different string depending on which button is pressed. Whatever name is used for the button, that string is also used to called a corresponding function and the two must match precisely. That is, if you title the button 'template_1' then the function needs to be 'template_1()' also.
import controlP5.*;
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
Robot robot;
ControlP5 gui;
String keyString = "template-1";
String keyString2 = "template-2";
void setup() {
size(350, 250);
gui = new ControlP5(this);
// Function that's called must match the button title.
gui.addButton("Template_1")
.setPosition(50, 50)
.setSize(100, 100)
.activateBy(ControlP5.RELEASE);
;
gui.addButton("Template_2")
.setPosition(200, 50)
.setSize(100, 100)
.activateBy(ControlP5.RELEASE);
;
try {
robot = new Robot();
} catch (AWTException ex) {
System.out.println(ex.getMessage());
}
}
void Template_1() {
println("Template 1 Button pressed");
sendKeys(keyString);
}
void Template_2() {
println("Template 2 Button pressed");
sendKeys(keyString2);
}
public void controlEvent(ControlEvent evnt) {
println(evnt);
}
void sendKeys(String keys) {
println("sendKeys fired.");
delay(3000); // Give user some time to set cursor
for (char c : keys.toCharArray()) {
println(c);
int keyCode = KeyEvent.getExtendedKeyCodeForChar(c);
if (KeyEvent.CHAR_UNDEFINED == keyCode) {
throw new RuntimeException(
"Key code not found for character '" + c + "'");
}
robot.keyPress(keyCode);
robot.delay(100);
robot.keyRelease(keyCode);
robot.delay(100);
}
}
void draw() {
}
I'm trying to have random text and backgrounds pop up when i click the mouse, however, when I click, the text is shown for less than half a second before a new background appears and hides the text
This is how my mouse pressed looks:
String mySentence = "Lose.txt";
String[] lose;
float mx = 20;
int posX = 0;
int posY = 0;
int butterflyX = 100;
int butterflyY = 100;
PImage v1;
PImage bf;
float xpos, ypos;
boolean playing = false;
//sentence
boolean showMySentence = false;
int mySentenceTimer = 0;
PImage [] backgrounds = new PImage[5];
int bg;
int currentBgNumber = 0;
void setup(){
size(800,501);
backgrounds = new PImage[5];
backgrounds[0] = loadImage("field.jpg");
backgrounds[1] = loadImage("galaxy.jpg");
backgrounds[2] = loadImage("tokyo.jpg");
backgrounds[3] = loadImage("water.jpg");
backgrounds[4] = loadImage("paris.jpg");
// mySentence = loadStrings(loseFile);
PFont myFont;
myFont = createFont("Futura", 30,true);
textFont(myFont);
fill(255);
}
void draw(){
image(backgrounds[currentBgNumber], 0, 0);
if (showMySentence) {
fill(255);
textSize(20);
text(mySentence, width/2, height/2);
showMySentence = millis() < mySentenceTimer;
}
void mousePressed() {
currentBgNumber++;
if (currentBgNumber>4)
currentBgNumber=0;
if (random(1) < .5) {
mySentence = "lose.txt" + "!";
mySentenceTimer = millis() + 3000;
showMySentence = true;
}
}
From what I see, after the background runs, the text should run after, but I guess not. I would appreciate it if you could show me what needs to be fixed, thank you :)
As promised (you can copy and paste this snippet in a Processing IDE and it'll run. Then play around until you get the results that you want and adapt it into your own code):
// a couple variables I'll use to manage the text's appearance
String mySentence = "";
boolean showMySentence = false;
int mySentenceTimer = 0;
void setup() {
size(500, 500);
}
void draw() {
// background paints over everything ~60 times per second (or whatever fps you did set up)
background(0);
// this would go at the end of the draw() loop
if (showMySentence) {
fill(255);
textSize(20);
text(mySentence, width/2, height/2);
showMySentence = millis() < mySentenceTimer; // flip the boolean after some time (2 seconds here)
}
}
void mousePressed() {
// this would replace the part of the mouseClicked() method where you draw the sentence
if (random(1) < .5) {
mySentence = "your sentence here";
mySentenceTimer = millis() + 2000; // show text but only for 2 seconds (I guessed that you might want that, but nevermind if that's not the case)
showMySentence = true;
}
}
I'll hang around in case you have further questions. Have fun!
My code below places sell pending orders when certain candle patterns are met on the H_1 chart. But duplicate pending orders are created when I change the chart timeframe and return to H_1. Also old orders that should have hit stop loss or take profit seem to still be open.
I need to have multiple pending orders, but the duplicates and orders that should have closed are not wanted. How can I solve this?
string prefix = "HG";
const int N_bars = 1;
int numBars = 1;
int numBarsArray[];
int tempVal = 0;
int NumOfDisplayBars = 300;
int count = 0;
extern double lotSize = 0.01;
int magicnumber = 1337;
void showRectangles()
{
for (int i=NumOfDisplayBars;i>=1;i--)
{
if(isBearishEngulfing(i))
{
drawBearRectangle(i + 1,iHigh(_Symbol,0,i + 1),iOpen(_Symbol,0,i + 1));
}
}
}
bool isBearishEngulfing(int current)
{
if( (iClose(_Symbol,0,current ) < iOpen( _Symbol,0,current ))
&& (iClose(_Symbol,0,current + 1) > iOpen( _Symbol,0,current + 1))
&& (iOpen( _Symbol,0,current ) > iClose(_Symbol,0,current + 1))
&& (iClose(_Symbol,0,current ) < iOpen( _Symbol,0,current + 1))
)
return true;
return false;
}
bool drawBearRectangle(int candleInt,const double top,const double bottom)
{
const datetime starts = iTime(_Symbol,0,candleInt);
const datetime ends = starts+PeriodSeconds()*N_bars;
const string name = prefix+"_"+(candleInt>0?"DEMAND":"SUPPLY")+"_"+TimeToString(starts);
if(!ObjectCreate(0,name,OBJ_RECTANGLE,0,0,0,0,0))
{
printf("%i %s: failed to create %s. error=%d",__LINE__,__FILE__,name,_LastError);
return false;
}
ObjectSetInteger(0,name,OBJPROP_TIME1, starts);
ObjectSetInteger(0,name,OBJPROP_TIME2, ends);
ObjectSetDouble( 0,name,OBJPROP_PRICE1,bottom);
ObjectSetDouble( 0,name,OBJPROP_PRICE2,top);
ObjectSetInteger(0,name,OBJPROP_COLOR, clrChocolate);
ObjectSetInteger(0,name,OBJPROP_STYLE, STYLE_DASHDOT);
ObjectSetInteger(0,name,OBJPROP_WIDTH, 1);
ObjectSetInteger(0,name,OBJPROP_FILL, false);
if(_Period == 60){
double entryPrice=bottom-3*_Point;
double stopLoss=top;
double slDist=fabs(entryPrice-stopLoss);
double dTakeProfit=entryPrice-2*slDist;
int ticketSell = OrderSend(Symbol(),OP_SELLLIMIT,lotSize, entryPrice,0,stopLoss,dTakeProfit,"SellOrder",magicnumber,0,Red);
}
return true;
}
void OnDeinit(const int reason){ObjectsDeleteAll(0,prefix);}
void OnTick()
{
if(!isNewBar())
return; // not necessary but waste of time to check every second
showRectangles();
}
bool isNewBar()
{
static datetime lastbar;
datetime curbar = (datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_LASTBAR_DATE);
if(lastbar != curbar)
{
lastbar = curbar;
return true;
}
return false;
}
Q : How can I solve the duplicates of pending order issues?... when I change the chart timeframe and return to H_1.
Well, this is rather a feature of the MQL4/5 code-execution ecosystem.
Solution:
Configure a preventive checkmark, setup in MT4-Terminal in Tools > Options > Expert Advisor-tab so as to become:
[x] Disable automated trading when the chart symbol or period has been changed
I wrote the following code to look through the last 100 candlesticks and draw a rectangle around a bullish engulfing candlestick patterns. I hope extend it for bearish engulfing pattern too. I don't know why, but the rectangles don't draw. Please take a look at the code below
bool isBullishEngulfing(int current) {
if((iClose(_Symbol,0,current) > iOpen(_Symbol,0,current)) && (iClose(_Symbol,0,current + 1) < iOpen(_Symbol,0,current + 1)) &&
(iOpen(_Symbol,0,current) < iClose(_Symbol,0,current + 1)) && (iClose(_Symbol,0,current) > iOpen(_Symbol,0,current + 1)))
return true;
return false;
}
void showRectangles() {
for (int i=100;i<=1;i--) {
if(isBullishEngulfing(i)) {
drawBullRectangle(i,iHigh(_Symbol,0,i),iLow(_Symbol,0,i));
}
}
}
bool drawBullRectangle(int candleInt,const double top,const double bottom)
{
const datetime starts=iTime(_Symbol,0,candleInt);
const datetime ends=starts+PeriodSeconds()*Numbars; //Numbars shows how long the rectangle should draw
const string name=prefix+"_"+(candleInt>0?"DEMAND":"SUPPLY")+"_"+TimeToString(starts);
if(!ObjectCreate(0,name,OBJ_RECTANGLE,0,0,0,0,0))
{
printf("%i %s: failed to create %s. error=%d",__LINE__,__FILE__,name,_LastError);
return false;
}
ObjectSetInteger(0,name,OBJPROP_TIME1,starts);
ObjectSetInteger(0,name,OBJPROP_TIME2,ends);
ObjectSetDouble(0,name,OBJPROP_PRICE1,top);
ObjectSetDouble(0,name,OBJPROP_PRICE2,bottom);
ObjectSetInteger(0,name,OBJPROP_COLOR, clrAqua);
ObjectSetInteger(0,name,OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
ObjectSetInteger(0,name,OBJPROP_FILL, true);
return true;
}
void OnDeinit(const int reason){ObjectsDeleteAll(0,prefix);}
void OnTick()
{
if(!isNewBar())
return; //not necessary but waste of time to check every second
showRectangles();
}
bool isNewBar()
{
static datetime lastbar;
datetime curbar = (datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_LASTBAR_DATE);
if(lastbar != curbar)
{
lastbar = curbar;
return true;
}
return false;
}
I would appreciate help to resolve this.
The error is mainly in the loop, it should be for (int i=100;i>=1;i--)
The other "possible" error is in the logic of theisBullishEngulfing() function.
Usually, the Close of the previous bar is equal to the Open of the current bar, so the following condition doesn't get fulfilled(most of the time)
iOpen(_Symbol,0,current) < iClose(_Symbol,0,current + 1)
(So, I suggest to remove this line, but this is just a suggestion, note there are occasions that your condition get fulfilled as well)
Here is my code for a cellular automaton I am working on:
UPDATE:
public class Lif1ID {
private Rule rule;
private int stepCount;
public static void main (String [ ] args) {
Lif1ID simulation = new Lif1ID ( );
simulation.processArgs (args);
simulation.producePBM ( ); LINE 9
}
// Print, in Portable Bitmap format, the image corresponding to the rule and step count
// specified on the command line.
public void producePBM ( ) {
int width = (stepCount*2+1);
System.out.println("P1 " + width + " " + (stepCount+1));
String prev_string = "";
// constructs dummy first line of rule
for (int i = 0; i < width; i++){
if (i == stepCount+1){
prev_string += "1";
} else {
prev_string += "0";
}
}
// contructs and prints out all lines prescribed by the rule, including the first
for (int i = 0; i < stepCount; i++) {
String next_string = "";
for (int j = 0; j < width; j++) {
// prints next line, one character at a time
System.out.print(prev_string.charAt(j) + " ");
// specifies cases for the edges as well as for normal inputs to Rule
if (j == 0) {
next_string += rule.output(0, Character.getNumericValue(prev_string.charAt(0)), Character.getNumericValue(prev_string.charAt(1)));
} else if (j == width-1) {
next_string += rule.output(Character.getNumericValue(prev_string.charAt(width-2)), Character.getNumericValue(prev_string.charAt(width-1)), 0);
} else {
String rule_input = prev_string.substring(j-1, j+2);
int first = Character.getNumericValue(rule_input.charAt(0));
int second = Character.getNumericValue(rule_input.charAt(1));
int third = Character.getNumericValue(rule_input.charAt(2));
next_string += rule.output(first, second, third); LINE 43
}
}
// sets prev_string to next_string so that string will be the next string in line to be printed
prev_string = next_string;
System.out.println();
}
}
// Retrieve the command-line arguments, and convert them to values for the rule number
// and the timestep count.
private void processArgs (String [ ] args) {
if (args.length != 2) {
System.err.println ("Usage: java Life1D rule# rowcount");
System.exit (1);
}
try {
rule = new Rule (Integer.parseInt(args[0]));
} catch (Exception ex) {
System.err.println ("The first argument must specify a rule number.");
System.exit (1);
}
try {
stepCount = Integer.parseInt (args[1]);
} catch (Exception ex) {
System.err.println ("The second argument must specify the number of lines in the output.");
System.exit (1);
}
if (stepCount < 1) {
System.err.println ("The number of output lines must be a positive number.");
System.exit (1);
}
}
}
class Rule {
private int a, b, c;
private String rulebin;
public Rule (int ruleNum) {
rulebin = convertToBinary(ruleNum);
}
private String convertToBinary(int input) // get the binary presentation as you want
{ // if the input is 2 you'll get "00000010"
String binary = "";
for (int i = 0; i < 8; i++){
if ((1 << i & input) != 0)
binary += "1";
else
binary+= "0";
}
binary = new StringBuffer(binary).reverse().toString();
return binary;
}
// Return the output that this rule prescribes for the given input.
// a, b, and c are each either 1 or 0; 4*a+2*b+c is the input for the rule.
public char output (int a, int b, int c) {
return rulebin.charAt(7 - 4*a + 2*b + c); LINE 106
}
}
Here is the error message I get when I type in rule 30 with 3 timesteps:
java Life1D 30 3
UPDATED error message:
P1 7 4
0 0 0 0Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 151
at java.lang.String.charAt(String.java:686)
at Rule.output(Life1D.java:106)
at Life1D.producePBM(Life1D.java:43)
at Life1D.main(Life1D.java:9)
The corresponding lines are noted in the code. Why am I getting this error, and how can I fix it? I've been trying to find the error for hours, and it'll a blessing if I could be helped.
The problem is that Rule.output() expects three int parameters, but what you're calling it with on the line
next_string += rule.output(0, prev_string.charAt(0), prev_string.charAt(1));
is actually an int and then 2 chars. Now, the actual character is '0', but due to the implicit conversion the language does for you, you get the ASCII code of '0', which is 48 and that's what's passed to the function Rule.output().
Now, to fix this problem you need to use the method Character.getNumericValue() like so:
next_string += rule.output(0, Character.getNumericValue(prev_string.charAt(0)), Character.getNumericValue(prev_string.charAt(1)));
Don't forget to change the other two invocations of Rule.output()
However, note that this is not the only problem in your code, as I'm still getting String index out of range: 7, because the parameters with which the Rule.output() method is called with are now all 0, but I've answered your original question. If you need more help, let me know.