Skip to content

Commit 2539cd6

Browse files
committed
Add a way to define the percentages wanted for each stock
1 parent ddf8861 commit 2539cd6

File tree

6 files changed

+90
-59
lines changed

6 files changed

+90
-59
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pip install allokation
2323

2424
## Usage
2525

26-
- It's quite simple to use this package, you just need to import the function `allocate_money`, pass a list of tickers you want and the available money you have to invest.
26+
- It's quite simple to use this package, you just need to import the function `allocate_money`, pass a list of tickers you want and the available money you have to invest. If you want, you can also pass a list of the percentages of each stocks you want in your portfolio. This list of percentages must have the same length of the tickers.
2727

2828
- It will return a dict containing the allocations you must need and the total money you must need to have this portfolio (This total will be less or equal than the available money you informed to the `allocate_money` function). For each stock, it will be returned the `price` that was used to calculate the portfolio, the `amount` of stocks you will need to buy, the `total` money you need to buy this amount of this stock and the `percentage` that this stock represents in your portfolio. For example:
2929

allokation/allokate.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
1-
from allokation.utils import (calculate_amount, calculate_multiplier,
1+
from allokation.utils import (calculate_amount,
22
calculate_percentage_of_each_ticker,
33
calculate_total_for_each_ticker,
4-
get_closing_price_from_yahoo, get_target_date,
5-
map_columns_without_suffix, transpose_prices)
4+
get_closing_price_from_yahoo,
5+
get_percentage_of_stocks, get_target_date,
6+
transpose_prices)
67

78

8-
def allocate_money(available_money, tickers):
9-
# import ipdb; ipdb.set_trace()
9+
def allocate_money(available_money, tickers, percentages=None):
10+
if percentages and len(tickers) != len(percentages):
11+
raise Exception('Tickers and percentages must have the same lenght')
12+
13+
percentage_multiplier = get_percentage_of_stocks(tickers=tickers, percentages=percentages)
14+
1015
target_date = get_target_date()
1116
prices = get_closing_price_from_yahoo(tickers=tickers, date=target_date)
12-
renamed_columns = map_columns_without_suffix(tickers)
13-
prices.rename(columns=renamed_columns, inplace=True)
1417
df = transpose_prices(prices)
15-
multiplier = calculate_multiplier(df, number_of_tickers=len(tickers), available_money=available_money)
1618

17-
df['amount'] = calculate_amount(df, multiplier)
19+
df['amount'] = calculate_amount(df, available_money, percentage_multiplier)
20+
1821
df['total'] = calculate_total_for_each_ticker(df)
1922
df['percentage'] = calculate_percentage_of_each_ticker(df)
2023

2124
result = {}
2225
result['allocations'] = df.set_index('symbol').T.to_dict()
23-
result['total_value'] = df["total"].sum()
26+
result['total_value'] = df["total"].sum().round(2)
2427

2528
return result

allokation/utils.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
from pandas_datareader import data as web
55

66

7+
def get_percentage_of_stocks(tickers, percentages=None):
8+
if percentages:
9+
return pd.Series(percentages)/100
10+
11+
return 1/(len(tickers))
12+
13+
714
def get_target_date(base_date=date.today()):
815
weekdays = [
916
'monday',
@@ -30,10 +37,6 @@ def get_closing_price_from_yahoo(tickers, date):
3037
return result['Adj Close']
3138

3239

33-
def map_columns_without_suffix(tickers):
34-
return {ticker: ticker[:-3] for ticker in tickers}
35-
36-
3740
def transpose_prices(prices):
3841
df = pd.DataFrame()
3942
df['symbol'] = prices.columns
@@ -42,15 +45,8 @@ def transpose_prices(prices):
4245
return df
4346

4447

45-
def calculate_multiplier(df, number_of_tickers, available_money):
46-
max_price = df['price'].max()
47-
percentage = 1 / number_of_tickers
48-
multiplier = (available_money * percentage) / max_price
49-
return multiplier
50-
51-
52-
def calculate_amount(df, multiplier):
53-
return (df['price'].max()*multiplier/df['price']).round(0)
48+
def calculate_amount(df, available_money, percentage_multiplier):
49+
return (available_money * percentage_multiplier / df['price']).round(0)
5450

5551

5652
def calculate_total_for_each_ticker(df):

example/example.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@
1212
'VVAR3.SA',
1313
]
1414

15-
result = allocate_money(available_money=AVAILABLE_MONEY, tickers=tickers)
15+
percentages = [60, 10, 10, 10, 10]
16+
17+
result = allocate_money(available_money=AVAILABLE_MONEY, tickers=tickers, percentages=percentages)
1618
pp(result)

tests/test_allokate.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import date
22

3+
import pytest
34
import requests_cache
45
from pandas_datareader import data as web
56

@@ -34,8 +35,23 @@ def test_allocate_money(mocker):
3435

3536
assert result.get('allocations', None)
3637
assert result.get('total_value', None)
37-
assert result['allocations'].get('B3SA3', None)
38-
assert result['allocations'].get('BBDC4', None)
39-
assert result['allocations'].get('MGLU3', None)
40-
assert result['allocations'].get('PETR4', None)
41-
assert result['allocations'].get('VVAR3', None)
38+
assert result['allocations'].get('B3SA3.SA', None)
39+
assert result['allocations'].get('BBDC4.SA', None)
40+
assert result['allocations'].get('MGLU3.SA', None)
41+
assert result['allocations'].get('PETR4.SA', None)
42+
assert result['allocations'].get('VVAR3.SA', None)
43+
44+
45+
def test_allocate_money_with_percentage_with_different_length_than_stocks():
46+
with pytest.raises(Exception):
47+
tickers = [
48+
'B3SA3.SA',
49+
'BBDC4.SA',
50+
'MGLU3.SA',
51+
'PETR4.SA',
52+
'VVAR3.SA',
53+
]
54+
55+
percentages = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
56+
57+
allocate_money(1000, tickers, percentages=percentages)

tests/test_utils.py

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,49 @@
22
from datetime import date, timedelta
33

44
import pandas as pd
5-
import pytest
65
import requests_cache
76
from pandas_datareader import data as web
87

9-
from allokation.utils import (calculate_amount, calculate_multiplier,
8+
from allokation.utils import (calculate_amount,
109
calculate_percentage_of_each_ticker,
1110
calculate_total_for_each_ticker,
12-
get_closing_price_from_yahoo, get_target_date,
13-
map_columns_without_suffix, transpose_prices)
11+
get_closing_price_from_yahoo,
12+
get_percentage_of_stocks, get_target_date,
13+
transpose_prices)
1414

1515
STOCKS_DATA_FILEPATH = os.path.join(os.path.dirname(__file__), './data/stocks.csv')
1616

1717

18+
def test_get_percentage_of_stocks_without_percentage_should_return_equal_distribution():
19+
tickers = [
20+
'B3SA3.SA',
21+
'BBDC4.SA',
22+
'CSAN3.SA',
23+
'CYRE3.SA',
24+
]
25+
26+
expected = 0.25
27+
28+
result = get_percentage_of_stocks(tickers=tickers)
29+
30+
assert result == expected
31+
32+
33+
def test_get_percentage_of_stocks_with_percentage_should_return_pandas_series():
34+
tickers = [
35+
'B3SA3.SA',
36+
'BBDC4.SA',
37+
'CSAN3.SA',
38+
'CYRE3.SA',
39+
]
40+
percentages = [40, 20, 20, 20]
41+
expected = pd.Series([0.4, 0.2, 0.2, 0.2])
42+
43+
result = get_percentage_of_stocks(tickers=tickers, percentages=percentages)
44+
45+
assert result.equals(expected)
46+
47+
1848
def test_get_target_date_when_today_is_a_weekday():
1949
base_date = date(year=2020, month=9, day=4)
2050
expected = base_date
@@ -88,42 +118,26 @@ def test_transpose_prices():
88118
assert result.equals(expected)
89119

90120

91-
def test_map_columns_without_suffix():
92-
tickers = [
93-
'MGLU3.SA',
94-
'PETR4.SA',
95-
'VVAR3.SA',
96-
]
97-
98-
result = map_columns_without_suffix(tickers)
99-
100-
expected = {
101-
'MGLU3.SA': 'MGLU3',
102-
'PETR4.SA': 'PETR4',
103-
'VVAR3.SA': 'VVAR3',
104-
}
105-
assert result == expected
106-
107-
108-
def test_calculate_multiplier():
121+
def test_calculate_amount_with_equal_distribution():
109122
df = pd.read_csv(STOCKS_DATA_FILEPATH)
110-
number_of_tickers = len(df.values)
111123
available_money = 1000
124+
percentage_multiplier = 1/len(df)
112125

113-
expected = pytest.approx(3.57, 0.01)
126+
expected = (available_money*percentage_multiplier/df['price']).round(0)
114127

115-
result = calculate_multiplier(df, number_of_tickers, available_money)
128+
result = calculate_amount(df, available_money=available_money, percentage_multiplier=percentage_multiplier)
116129

117-
assert result == expected
130+
assert result.equals(expected)
118131

119132

120133
def test_calculate_amount():
121134
df = pd.read_csv(STOCKS_DATA_FILEPATH)
122-
multiplier = 1
135+
available_money = 1000
136+
percentage_multiplier = pd.Series([0.33, 0.33, 0.34])
123137

124-
expected = (df['price'].max()/df['price']).round(0)
138+
expected = (available_money*percentage_multiplier/df['price']).round(0)
125139

126-
result = calculate_amount(df, multiplier=multiplier)
140+
result = calculate_amount(df, available_money=available_money, percentage_multiplier=percentage_multiplier)
127141

128142
assert result.equals(expected)
129143

0 commit comments

Comments
 (0)