financegy 2.0__tar.gz → 3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: financegy
3
- Version: 2.0
3
+ Version: 3.0
4
4
  Summary: Unofficial Python library for accessing GSE (Guyana Stock Exchange) financial data
5
5
  Author-email: Ezra Minty <ezranminty@gmail.com>
6
6
  License: MIT
@@ -23,6 +23,13 @@ from financegy.modules.securities import (
23
23
  get_historical_trades,
24
24
  )
25
25
 
26
+ from financegy.modules.portfolio import (
27
+ calculate_position_value,
28
+ calculate_position_return,
29
+ calculate_position_return_percent,
30
+ calculate_portfolio_summary,
31
+ )
32
+
26
33
  __all__ = [
27
34
  "get_securities",
28
35
  "get_security_by_symbol",
@@ -41,6 +48,10 @@ __all__ = [
41
48
  "search_securities",
42
49
  "get_trades_for_year",
43
50
  "get_historical_trades",
51
+ "calculate_position_value",
52
+ "calculate_position_return",
53
+ "calculate_position_return_percent",
54
+ "calculate_portfolio_summary",
44
55
  "clear_cache",
45
56
  "save_to_csv",
46
57
  "to_dataframe",
@@ -1,4 +1,4 @@
1
- __version__ = "2.0"
1
+ __version__ = "3.0"
2
2
 
3
3
  BASE_URL = "https://guyanastockexchangeinc.com"
4
4
  HEADERS = {
@@ -1,6 +1,3 @@
1
- # Function to convert GSE price string to float type
2
-
3
-
4
1
  def to_float(value):
5
2
  if value is None:
6
3
  return None
@@ -0,0 +1,157 @@
1
+ from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
2
+ from financegy.modules import securities
3
+
4
+
5
+ def _to_decimal(value, field_name="value"):
6
+ """
7
+ Convert strings/numbers to Decimal safely.
8
+ Handles commas like "3,000.0".
9
+ """
10
+ try:
11
+ clean_value = str(value).replace(",", "")
12
+ return Decimal(clean_value)
13
+ except (InvalidOperation, TypeError) as e:
14
+ raise ValueError(f"Invalid {field_name}: {value}") from e
15
+
16
+
17
+ def calculate_position_value(symbol: str, shares):
18
+ try:
19
+ shares = _to_decimal(shares, "shares")
20
+ if shares <= 0:
21
+ raise ValueError("Shares must be greater than zero")
22
+
23
+ last_trade = securities.get_recent_trade(symbol)
24
+ last_trade_price = _to_decimal(
25
+ last_trade.get("last_trade_price"), "last_trade_price"
26
+ )
27
+
28
+ position_value = last_trade_price * shares
29
+
30
+ return {
31
+ "last_trade": last_trade,
32
+ "position_value": str(position_value),
33
+ }
34
+
35
+ except (ValueError, KeyError, TypeError) as e:
36
+ raise ValueError(f"[calculate_position_value] Invalid input or data: {e}")
37
+
38
+
39
+ def calculate_position_return(symbol: str, shares, purchase_price):
40
+ try:
41
+ shares = _to_decimal(shares, "shares")
42
+ purchase_price = _to_decimal(purchase_price, "purchase_price")
43
+
44
+ if shares <= 0:
45
+ raise ValueError("Shares must be greater than zero")
46
+ if purchase_price <= 0:
47
+ raise ValueError("Purchase price must be greater than zero")
48
+
49
+ last_trade = securities.get_recent_trade(symbol)
50
+ last_trade_price = _to_decimal(
51
+ last_trade.get("last_trade_price"), "last_trade_price"
52
+ )
53
+
54
+ position_return = (last_trade_price - purchase_price) * shares
55
+
56
+ return {
57
+ "last_trade": last_trade,
58
+ "position_return": str(position_return),
59
+ }
60
+
61
+ except (ValueError, KeyError, TypeError) as e:
62
+ raise ValueError(f"[calculate_position_return] Invalid input or data: {e}")
63
+
64
+
65
+ def calculate_position_return_percent(symbol, shares, purchase_price):
66
+ try:
67
+ shares = _to_decimal(shares, "shares")
68
+ purchase_price = _to_decimal(purchase_price, "purchase_price")
69
+
70
+ if shares <= 0:
71
+ raise ValueError("Shares must be greater than zero")
72
+ if purchase_price <= 0:
73
+ raise ValueError("Purchase price must be greater than zero")
74
+
75
+ last_trade = securities.get_recent_trade(symbol)
76
+ last_trade_price = _to_decimal(
77
+ last_trade.get("last_trade_price"), "last_trade_price"
78
+ )
79
+
80
+ return_percent = (
81
+ (last_trade_price - purchase_price) / purchase_price
82
+ ) * Decimal("100")
83
+
84
+ return {
85
+ "last_trade": last_trade,
86
+ "position_return_percent": str(round(return_percent, 2)),
87
+ }
88
+
89
+ except (ValueError, KeyError, TypeError) as e:
90
+ raise ValueError(
91
+ f"[calculate_position_return_percent] Invalid input or data: {e}"
92
+ )
93
+
94
+
95
+ def calculate_portfolio_summary(positions: list[dict]):
96
+ try:
97
+ total_invested = Decimal("0")
98
+ total_value = Decimal("0")
99
+ detailed_positions = []
100
+
101
+ for pos in positions:
102
+ symbol = pos["symbol"]
103
+ shares = _to_decimal(pos.get("shares"), f"shares for {symbol}")
104
+ purchase_price = _to_decimal(
105
+ pos.get("purchase_price"), f"purchase_price for {symbol}"
106
+ )
107
+
108
+ if shares <= 0:
109
+ raise ValueError(f"Invalid shares for {symbol}")
110
+ if purchase_price <= 0:
111
+ raise ValueError(f"Invalid purchase price for {symbol}")
112
+
113
+ last_trade = securities.get_recent_trade(symbol)
114
+ last_trade_price = _to_decimal(
115
+ last_trade.get("last_trade_price"), f"last_trade_price for {symbol}"
116
+ )
117
+
118
+ invested = shares * purchase_price
119
+ current_value = shares * last_trade_price
120
+ gain_loss = current_value - invested
121
+
122
+ total_invested += invested
123
+ total_value += current_value
124
+
125
+ detailed_positions.append(
126
+ {
127
+ "symbol": symbol,
128
+ "shares": str(shares),
129
+ "purchase_price": str(purchase_price),
130
+ "last_trade_price": str(last_trade_price),
131
+ "invested": str(invested),
132
+ "current_value": str(current_value),
133
+ "gain_loss": str(gain_loss),
134
+ }
135
+ )
136
+
137
+ total_gain_loss = total_value - total_invested
138
+
139
+ if total_invested != 0:
140
+ return_percent = (
141
+ total_gain_loss / total_invested * Decimal("100")
142
+ ).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
143
+ else:
144
+ return_percent = Decimal("0.00")
145
+
146
+ return {
147
+ "summary": {
148
+ "total_invested": str(total_invested),
149
+ "current_value": str(total_value),
150
+ "total_gain_loss": str(total_gain_loss),
151
+ "return_percent": str(return_percent),
152
+ },
153
+ "positions": detailed_positions,
154
+ }
155
+
156
+ except (ValueError, KeyError, TypeError) as e:
157
+ raise ValueError(f"[calculate_portfolio_summary] Invalid portfolio data: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: financegy
3
- Version: 2.0
3
+ Version: 3.0
4
4
  Summary: Unofficial Python library for accessing GSE (Guyana Stock Exchange) financial data
5
5
  Author-email: Ezra Minty <ezranminty@gmail.com>
6
6
  License: MIT
@@ -13,6 +13,7 @@ financegy/core/parser.py
13
13
  financegy/core/request_handler.py
14
14
  financegy/helpers/safe_text.py
15
15
  financegy/helpers/to_float.py
16
+ financegy/modules/portfolio.py
16
17
  financegy/modules/securities.py
17
18
  financegy/utils/utils.py
18
19
  tests/test_securities.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "financegy"
7
- version = "2.0"
7
+ version = "3.0"
8
8
  description = "Unofficial Python library for accessing GSE (Guyana Stock Exchange) financial data"
9
9
  authors = [{ name = "Ezra Minty", email = "ezranminty@gmail.com" }]
10
10
  dependencies = ["requests", "beautifulsoup4", "pandas", "openpyxl"]
@@ -91,6 +91,30 @@ def test_search_securities():
91
91
  assert isinstance(result, list)
92
92
 
93
93
 
94
+ def test_calculate_position_value():
95
+ result = calculate_position_value("ddl", "10")
96
+ assert isinstance(result, (dict, type(None)))
97
+
98
+
99
+ def test_calculate_position_return():
100
+ result = calculate_position_return("ddl", "10", "31")
101
+ assert isinstance(result, (dict, type(None)))
102
+
103
+
104
+ def test_calculate_position_return_percent():
105
+ result = calculate_position_return_percent("ddl", "10", "31")
106
+ assert isinstance(result, dict)
107
+
108
+
109
+ def test_calculate_portfolio_summary():
110
+ positions = [
111
+ {"symbol": "DTC", "shares": "100", "purchase_price": "300"},
112
+ {"symbol": "DDL", "shares": "50", "purchase_price": "250"},
113
+ ]
114
+ result = calculate_portfolio_summary(positions)
115
+ assert isinstance(result, dict)
116
+
117
+
94
118
  def test_to_dataframe():
95
119
  result = to_dataframe(get_securities())
96
120
  assert isinstance(result, pd.DataFrame)
File without changes
File without changes
File without changes