Home > Blockchain >  How to solve the problem that the global variable is not defined in python?
How to solve the problem that the global variable is not defined in python?

Time:06-05

I'm writing a script for stock / crpyto trading. A very crude strategy is to buy when the RSI (a technical indicator in stock) reaches 50 and sell when the RSI falls below 50 for 3 consecutive days or below 40 in a single day. My origin script is like this:

def simpleRSIstrategy(indicator, threshold1 = 50, threshold2 = 40, days = 3):
    
    buySellTable = dict()
    
    # simple RSI strategy
      
    hold = False
    count = 0  
    
    for i in range(len(indicator)):
        # buying strategy
        if indicator['RSI7'][i] > threshold1 and not hold:
            date = indicator.index[i]
            buySellTable[date] = 'Buy'
            hold = True  
            
        # selling strategy    
        if indicator['RSI7'][i] < threshold1 and hold:
            count  = 1
            if count == days or indicator['RSI7'][i] < threshold2:
                date = indicator.index[i]
                buySellTable[date] = 'Sell'
                hold = False
                count = 0       
        if indicator['RSI7'][i] > threshold1 and hold:
            count = 0
    return buySellTable

What this script is supposed to do is applying the aforementioned simple RSI strategy and return the 'buySellTable', which is a dictionary with the dates as keys and 'Buy' and 'Sell' as items. There is nothing wrong with this script. Since it's a crude strategy, it's necessary to get optimized. I want to split it into two parts - buying strategy and selling strategy so that I can do the optimization respectively. Then I rewrote it into the following scripts:

def simpleRSIstrategy_split(indicator, threshold1 = 50, threshold2 = 40, days = 3):
    
    
    buySellTable = dict()
    hold = False
    count = 0 
    startIdx = 0
   
    while True:
       
        simpleBuyStrategy_split(indicator, threshold1)
     
        simpleSellStrategy_split(indicator, threshold1, threshold2, days)
        
        if startIdx == len(indicator)-1:
            break
        
    return buySellTable
def simpleBuyStrategy_split(indicator, threshold1):
    global startIdx, hold, buySellTable
    
    for i in range(startIdx, len(indicator)):
            if indicator['RSI7'][i] > threshold1 and not hold:
                date = indicator.index[i]
                buySellTable[date] = 'Buy'
                hold = True  
                startIdx = i 1
                break

def simpleSellStrategy_split(indicator, threshold1, threshold2,  days):
    global startIdx, count, hold, buySellTable
    
    for i in range(startIdx, len(indicator)):
        if indicator['RSI7'][i] < threshold1 and hold:
            count  = 1
            if count == days or indicator['RSI7'][i] < threshold2:
                date = indicator.index[i]
                buySellTable[date] = 'Sell'
                hold = False
                count = 0  
                startIdx = i 1
                break
        if indicator['RSI7'][i] > threshold1 and hold:
            count = 0

In the 'simpleRSIstrategy_split' script, I want to treat buySellTable, hold, count and startIdx as global variables that can be used and edited by the scripts 'simpleBuyStrategy_split' and 'simpleSellStrategy_split'. But I got the error message when I run the script: name 'startIdx' is not defined. I check the output from the console and found that when it's excuting the line

for i in range(startIdx, len(indicator)):

in the script 'simpleBuyStrategy_split', it complains that startIdx is not defined. But I already defined it as a global variable. I don't know why this happens and how can I solve it?

CodePudding user response:

Apparently you are unclear about the usage of the keyword global. The global keyword is used to access variables defined in a global scope inside a function. Consider this example,

var = 0
def func():
    var = 2
func()
print (var)

Here you get the output:

0
>>>

because a local copy of the variable var is created when we call func(). This copy of var, if modified, will not affect the global copy (i.e. the var = 0 defined outside func() in the global scope). Hence we get 0 as the output, since print (var) is in the global scope. Now consider this:

var = 0
def func():
    global var
    var = 2
func()
print (var)

Here you get the output:

2
>>>

because when func() is called Python searches for var in the global scope, finds it, and changes its value to 2. Hence print (var) will show 2 because the global variable var was changed inside the function when we wrote var = 2 after global var.

But if you do something like this:

def func():
    global var
    var = 2
func()
print (var)

Here you get NameError while executing print (var) because var was never defiend in the global scope. When func() is called, Python searches for var in the global scope but doesn't find it and so just a local copy of the variable var is created. So global var in this case is pretty much redundant because var was never defined in the global scope.

Simply put, the solution to your problem would be to put

buySellTable = dict()
hold = False
count = 0 
startIdx = 0

outside the function simpleRSIstrategy_split(). Your code should then work as expected.

CodePudding user response:

You got the scoping wrong. Python is a lexically scoped language.

Without knowing, you are writing closures. Meaning you use variables in simpleBuyStrategy_split and simpleSellStrategy_split which are not in the function argument list - but outside the function definition.

Basically

    buySellTable = dict()
    hold = False
    count = 0 
    startIdx = 0

You treat them as if they would have dynamic scoping. (that they would change dependent where these two functions are called).

Since python arguments are mostly call-by-reference (due to performance reasons) - so they work like C pointers, the arguments themselves are changed if they are changed in the function body - except when they are deep-copied explicitely in the function body.

Therefore you could use them as if they are dynamically scoped, if you just pass them as function arguments.

Like this:

# first define them globally

buySellTable = dict()
hold = False
count = 0 
startIdx = 0

# then in each function definition, pass them as function arguments

def simpleRSIstrategy_split(indicator, threshold1 = 50, threshold2 = 40, days = 3, buySellTable=buySellTable, hold=hold, count=count, startIdx=startIdx):

    while True:
       
        simpleBuyStrategy_split(indicator, threshold1, buySellTable=buySellTable, hold=hold, count=count, startIdx=startIdx)
     
        simpleSellStrategy_split(indicator, threshold1, threshold2, days, buySellTable=buySellTable, hold=hold, count=count, startIdx=startIdx)
        
        if startIdx == len(indicator)-1:
            break
        
    return buySellTable

def simpleBuyStrategy_split(indicator, threshold1, buySellTable=buySellTable, hold=hold, count=count, startIdx=startIdx):
    
    for i in range(startIdx, len(indicator)):
            if indicator['RSI7'][i] > threshold1 and not hold:
                date = indicator.index[i]
                buySellTable[date] = 'Buy'
                hold = True  
                startIdx = i 1
                break

def simpleSellStrategy_split(indicator, threshold1, threshold2,  days, buySellTable=buySellTable, hold=hold, count=count, startIdx=startIdx):

    for i in range(startIdx, len(indicator)):
        if indicator['RSI7'][i] < threshold1 and hold:
            count  = 1
            if count == days or indicator['RSI7'][i] < threshold2:
                date = indicator.index[i]
                buySellTable[date] = 'Sell'
                hold = False
                count = 0  
                startIdx = i 1
                break
        if indicator['RSI7'][i] > threshold1 and hold:
            count = 0

I see, somehow you realized that they have globally to refer, but then, the simpleRSI function split must also do it:


buySellTable = dict()
hold = False
count = 0 
startIdx = 0
   
def simpleRSIstrategy_split(indicator, threshold1 = 50, threshold2 = 40, days = 3):
    global buySellTable, hold, count, startIdx
    
    while True:
       
        simpleBuyStrategy_split(indicator, threshold1)
     
        simpleSellStrategy_split(indicator, threshold1, threshold2, days)
        
        if startIdx == len(indicator)-1:
            break
        
    return buySellTable

And the other two functions then as you had them. This might also work.

But this is not so nice. Because one should avoid global variables.

Solution: Encapsulate Using Classes

In Python, you would avoid global variables by using classes. You could create a class where the global variables are class Attributes. (prepended by: self. And these three functions are methods of the class. Since they have then to refer to these four variables using self. you don't need the 'global' delcarations in them. And the class itself is encapsulated.

Like this:

class SimpleRSITrader:
    def __init__(self, indicator, threshold1 = 50, threshold2 = 40, days = 3, rsi='RSI7'):
        self.buy_sell_table = {}
        self.hold = False
        self.count = 0
        self.start_idx = 0
        self.indicator = indicator
        self.thresh1 = threshold1
        self.thrseh2 = threshold2
        self.days = days
        self.rsi = rsi
    
    def run(self):
        while True:
            self.buy()
            self.sell()     
            if self.startIdx == len(self.indicator)-1:
                break
        return self.buy_sell_table
    
    def buy(self):
        for i in range(self.start_idx, len(self.indicator)):
            if self.indicator[self.rsi][i] > self.thresh1 and not self.hold:
                date = self.indicator.index[i]
                self.buy_sell_table[date] = 'Buy'
                self.hold = True
                self.start_idx = i   1
                break
                
    def sell(self):
        for i in range(self.start_idx, len(self.indicator)):
            if self.indictaor[self.rsi] < self.thresh1 and self.hold:
                self.count  = 1
                if count == self.days or self.indictoar[self.rsi][i] < self.thresh2:
                    date = self.indicator.index[i]    
                    self.buy_sell_table[date] = 'Sell'
                    self.hold = False
                    self.count = 0
                    self.start_idx = i   1
                    break
            if self.indicator[self.rsi][i] > self.thresh1 and self.hold:
                self.count = 0

The good thing of this is, you can anytime from outside check the status of the attributes. And thus also communicate with other parts of the program - while you don't have global variables - because the previously global variables are now encapsulated in the class - therefore packed into objects.

So the idea is that for each indicator with new threshold settings and day settings you create a new instance for the simple RSI strategy.

This class, you can reuse even for different RSI's (other than 'RSI7'.

Through encapsulation, the context for your buy and sell becomes clear - thus the method names or attribute names become simpler too - therefore everything more easily readable and your code is structured - every forme global variables and functions are now encapsulated into the class - thus the code is clearly structured and the context of each of them clear - therefore you can be shorter with your names.

  • Related