Skip to content

assumption that all callables define __name__ attribute: #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
arunavo4 opened this issue Jun 20, 2020 · 10 comments
Closed

assumption that all callables define __name__ attribute: #93

arunavo4 opened this issue Jun 20, 2020 · 10 comments
Labels
bug Something isn't working

Comments

@arunavo4
Copy link

@kernc I am working an issue where I need to use the abstract module of talib

from backtesting import Backtest, Strategy
from backtesting.lib import crossover

from backtesting.test import GOOG
from talib import abstract


class SmaCross(Strategy):
    def init(self):
        Ind = abstract.Function('sma')
        inputs = {
            'open': self.data.Open,
            'high': self.data.High,
            'low': self.data.Low,
            'close': self.data.Close,
            'volume': self.data.Volume
        }
        self.ma1 = self.I(Ind, inputs, 10)
        self.ma2 = self.I(Ind, inputs, 20)

    def next(self):
        if crossover(self.ma1, self.ma2):
            self.buy()
        elif crossover(self.ma2, self.ma1):
            self.sell()


bt = Backtest(GOOG, SmaCross,
              cash=10000, commission=.002)
print(bt.run())
bt.plot()

I need this feature as I am dynamically getting Indicators using just function name

Running this ends up in this error

Traceback (most recent call last):
  File "/home/skywalker/PycharmProjects/backtester/backtest.py", line 30, in <module>
    print(bt.run())
  File "/home/skywalker/PycharmProjects/backtester/venv/lib/python3.8/site-packages/backtesting/backtesting.py", line 692, in run
    strategy.init()
  File "/home/skywalker/PycharmProjects/backtester/backtest.py", line 18, in init
    self.ma1 = self.I(Ind, inputs, 10)
  File "/home/skywalker/PycharmProjects/backtester/venv/lib/python3.8/site-packages/backtesting/backtesting.py", line 118, in I
    func_name = _as_str(func)
  File "/home/skywalker/PycharmProjects/backtester/venv/lib/python3.8/site-packages/backtesting/_util.py", line 24, in _as_str
    name = value.__name__.replace('<lambda>', 'λ')
AttributeError: 'Function' object has no attribute '__name__'

The issue seems to be way the function is constructed later in the backtesting.py

        if name is None:
            params = ','.join(filter(None, map(_as_str, chain(args, kwargs.values()))))
            func_name = _as_str(func)
            name = ('{}({})' if params else '{}').format(func_name, params)

Any tips on how to get around it?

  • Backtesting version: latest master
@arunavo4
Copy link
Author

@kernc Is there a way to get around this?

@kernc
Copy link
Owner

kernc commented Jun 21, 2020

The assumption is here:

if callable(value):
name = value.__name__.replace('<lambda>', 'λ')

talib.abstract.Function objects, as passed to Strategy.I(), are callable, but they don't have .__name__. We have to accommodate for that fact so that the interpreter isn't crashed, rather that some other, suitable name is used instead.

@kernc
Copy link
Owner

kernc commented Jun 21, 2020

getattr(value, '__name__', alternative)

or some such. But what is the alternative?

@arunavo4
Copy link
Author

arunavo4 commented Jun 21, 2020

@kernc Thanks for the valuable Tip I found a way To solve it. Rather found the alternative you asked for.

    if callable(value):
        try:
            name = value.__name__.replace('<lambda>', 'λ')
        except AttributeError:
            name = getattr(value, '__name__', repr(value))

@arunavo4
Copy link
Author

arunavo4 commented Jun 21, 2020

After this, I Tested To make sure Everything worked correctly

class SmaCross(Strategy):
    def init(self):
        Ind = abstract.Function('sma')
        inputs = {
            'open': self.data.Open,
            'high': self.data.High,
            'low': self.data.Low,
            'close': self.data.Close,
            'volume': self.data.Volume
        }
        self.ma1 = self.I(Ind, inputs, 10)
        self.ma2 = self.I(Ind, inputs, 20)

    def next(self):
        if crossover(self.ma1, self.ma2):
            self.buy()
        elif crossover(self.ma2, self.ma1):
            self.sell()


bt = Backtest(GOOG, SmaCross,
              cash=10000, commission=.002)
print(bt.run())

Results

Start                     2004-08-19 00:00:00
End                       2013-03-01 00:00:00
Duration                   3116 days 00:00:00
Exposure [%]                          94.2875
Equity Final [$]                      69665.1
Equity Peak [$]                       69722.1
Return [%]                            596.651
Buy & Hold Return [%]                 703.458
Max. Drawdown [%]                    -33.6059
Avg. Drawdown [%]                    -5.62833
Max. Drawdown Duration      689 days 00:00:00
Avg. Drawdown Duration       41 days 00:00:00
# Trades                                   93
Win Rate [%]                          53.7634
Best Trade [%]                        56.9786
Worst Trade [%]                      -17.0259
Avg. Trade [%]                        2.44454
Max. Trade Duration         121 days 00:00:00
Avg. Trade Duration          32 days 00:00:00
Expectancy [%]                        6.91837
SQN                                   1.77227
Sharpe Ratio                         0.220629
Sortino Ratio                        0.541607
Calmar Ratio                        0.0727415
_strategy                            SmaCross
dtype: object

The Results are the same as the normal way of doing it as mentioned in your README.md

@kernc
Copy link
Owner

kernc commented Jun 21, 2020

repr of that object doesn't make an ideal indicator name, though. You might notice it if you do the plot ...

>>> repr(talib.abstract.Function('sma'))                                                             
"{'name': 'SMA', 'group': 'Overlap Studies', 'display_name': 'Simple Moving Average', 'function_flags': ['Output scale same as input'], 'input_names': OrderedDict([('price', 'close')]), 'parameters': OrderedDict([('timeperiod', 30)]), 'output_flags': OrderedDict([('real', ['Line'])]), 'output_names': ['real']}"

@arunavo4
Copy link
Author

arunavo4 commented Jun 21, 2020

@kernc ok I after more digging I found that rather than defining the std __name__ attribute there is something usable

>>> SMA = abstract.Function('sma')
>>> SMA.__dict__
{'_Function__name': b'SMA', '_Function__namestr': 'SMA', '_Function__info': {'name': 'SMA', 'group': 'Overlap Studies',............................}
>>> SMA._Function__namestr
SMA

@kernc
Copy link
Owner

kernc commented Jun 21, 2020

abstract.Function.__name is a mangled private member; it shouldn't be used in other code.

So this won't work. It's also too specific to talib, and we need a more generic fix, such as something derived on repr(), the convention talib seems to be offending.

@arunavo4
Copy link
Author

@kernc I understand your concern. But I could not find anything else that would work as a replacement for this in talib.

@kernc kernc added the bug Something isn't working label Jun 22, 2020
@kernc kernc closed this as completed in ad0b754 Jul 15, 2020
@arunavo4
Copy link
Author

@kernc perfect solution

Goblincomet pushed a commit to Goblincomet/forex-trading-backtest that referenced this issue Jul 5, 2023
Callable class instances might not. The solution is generic.
Fixes kernc/backtesting.py#93
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants