Skip to content
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

Fractional Shares / Currencies not supported #1159

Closed
realfishsam opened this issue Jul 17, 2024 · 1 comment
Closed

Fractional Shares / Currencies not supported #1159

realfishsam opened this issue Jul 17, 2024 · 1 comment

Comments

@realfishsam
Copy link

realfishsam commented Jul 17, 2024

Expected Behavior

Fractional shares are supported by numerous brokers. Here is a list of some:
Charles Schwab
Fidelity Investments
Interactive Brokers
Robinhood
E-Trade
Merrill Edge
Vanguard
Tastytrade
SoFi Active Investing
WellsTrade

If you are trading currencies, it makes sense to allow for a dollar and a quarter. So, why isn't it allowed?

Hence, the expected behavior when calling self.buy(size=1.1) is not an error, but rather the absence of any error. Printing output['_trades'] should result in this output:

    Size  EntryBar  ExitBar  EntryPrice  ExitPrice         PnL  ReturnPct  \
0   -1.1        63       75   168.68196     179.13  -11.492844  -0.061939   
1    1.1        75       85   179.48826     182.00    2.762914   0.013994   
2   -1.1        85       88   181.63600     187.45   -6.395400  -0.032009   
3    1.1        88      110   187.82490     179.27   -9.410390  -0.045547   
4   -1.1       110      119   178.91146     196.96  -19.853394  -0.100880   
..   ...       ...      ...         ...        ...         ...        ...   
89   1.1      1947     1960   611.57070     588.72  -25.135770  -0.037364   
90  -1.1      1960     1983   587.54256     580.01    8.285816   0.012820   
91   1.1      1983     2059   581.17002     705.58  136.850978   0.214068   
92  -1.1      2059     2087   704.16884     702.24    2.121724   0.002739   
93   1.1      2087     2147   703.64448     797.80  103.571072   0.133811   

    EntryTime   ExitTime Duration  
0  2004-11-17 2004-12-06  19 days  
1  2004-12-06 2004-12-20  14 days  
2  2004-12-20 2004-12-23   3 days  
3  2004-12-23 2005-01-26  34 days  
4  2005-01-26 2005-02-08  13 days  
..        ...        ...      ...  
89 2012-05-11 2012-05-31  20 days  
90 2012-05-31 2012-07-03  33 days  
91 2012-07-03 2012-10-19 108 days  
92 2012-10-19 2012-12-03  45 days  
93 2012-12-03 2013-03-01  88 days  

[94 rows x 10 columns]

Given that the size of a buy or sell order is a fraction.

Actual Behavior

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[2], [line 27](vscode-notebook-cell:?execution_count=2&line=27)
     [20](vscode-notebook-cell:?execution_count=2&line=20)             self.sell(size=1.1)                 # changed to fractional
     [23](vscode-notebook-cell:?execution_count=2&line=23) bt = Backtest(GOOG, SmaCross,
     [24](vscode-notebook-cell:?execution_count=2&line=24)               cash=10000, commission=.002,
     [25](vscode-notebook-cell:?execution_count=2&line=25)               exclusive_orders=True)
---> [27](vscode-notebook-cell:?execution_count=2&line=27) output = bt.run()
     [28](vscode-notebook-cell:?execution_count=2&line=28) bt.plot()

File /opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:1170, in Backtest.run(self, **kwargs)
   [1167](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:1167)         break
   [1169](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:1169)     # Next tick, a moment before bar close
-> [1170](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:1170)     strategy.next()
   [1171](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:1171) else:
   [1172](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:1172)     # Close any remaining open trades so they produce some stats
   [1173](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:1173)     for trade in broker.trades:

Cell In[2], [line 20](vscode-notebook-cell:?execution_count=2&line=20)
     [18](vscode-notebook-cell:?execution_count=2&line=18)     self.buy(size=1.1)                  # changed to fractional
     [19](vscode-notebook-cell:?execution_count=2&line=19) elif crossover(self.sma2, self.sma1):
---> [20](vscode-notebook-cell:?execution_count=2&line=20)     self.sell(size=1.1)

File /opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:223, in Strategy.sell(self, size, limit, stop, sl, tp)
    [212](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:212) def sell(self, *,
    [213](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:213)          size: float = _FULL_EQUITY,
    [214](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:214)          limit: float = None,
    [215](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:215)          stop: float = None,
    [216](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:216)          sl: float = None,
    [217](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:217)          tp: float = None):
    [218](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:218)     """
    [219](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:219)     Place a new short order. For explanation of parameters, see `Order` and its properties.
    [220](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:220) 
    [221](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:221)     See also `Strategy.buy()`.
    [222](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:222)     """
--> [223](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:223)     assert 0 < size < 1 or round(size) == size, \
    [224](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:224)         "size must be a positive fraction of equity, or a positive whole number of units"
    [225](https://file+.vscode-resource.vscode-cdn.net/opt/homebrew/lib/python3.10/site-packages/backtesting/backtesting.py:225)     return self._broker.new_order(-size, limit, stop, sl, tp)

AssertionError: size must be a positive fraction of equity, or a positive whole number of units

Steps to Reproduce

  1. Copy and paste the Backtesting.py snippet into your IDE
  2. Change argument size in self.buy() and/or self.sell() to a floating point number
  3. An assertion error will occur.
from backtesting import Backtest, Strategy
from backtesting.lib import crossover

from backtesting.test import SMA, GOOG


class SmaCross(Strategy):
    n1 = 10
    n2 = 20

    def init(self):
        close = self.data.Close
        self.sma1 = self.I(SMA, close, self.n1)
        self.sma2 = self.I(SMA, close, self.n2)

    def next(self):
        if crossover(self.sma1, self.sma2):
            self.buy(size=1.1)
        elif crossover(self.sma2, self.sma1):
            self.sell(size=1.1)


bt = Backtest(GOOG, SmaCross,
              cash=10000, commission=.002,
              exclusive_orders=True)

output = bt.run()
bt.plot()

Additional info

realfishsam added a commit to realfishsam/backtesting.py that referenced this issue Jul 17, 2024
Fixes fractional shares issue.

This:
```
size = copysign(max(1, round(abs(self.__size) * portion)), -self.__size)
```

had to be changed to this:
```
size = copysign(max(1, abs(self.__size) * portion), -self.__size)
```

Otherwise numbers less than 0 are handled like percentages of total cash
@realfishsam
Copy link
Author

This closes the issue:
#1160

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant