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.