@marcos_feitoza/personal-finance-backen-trades-assets 1.0.0 → 1.0.2
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.
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.0.2](https://github.com/MarcosOps/personal-finance-backend-trades-assets/compare/v1.0.1...v1.0.2) (2025-10-17)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* invest working ([d0ca897](https://github.com/MarcosOps/personal-finance-backend-trades-assets/commit/d0ca897cf8018aeecd2c10d037d94e987e241a58))
|
|
7
|
+
|
|
8
|
+
## [1.0.1](https://github.com/MarcosOps/personal-finance-backend-trades-assets/compare/v1.0.0...v1.0.1) (2025-09-24)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* update docker ([267efb1](https://github.com/MarcosOps/personal-finance-backend-trades-assets/commit/267efb11d839175803c7aa1866da0585ed4659f9))
|
|
14
|
+
|
|
1
15
|
# 1.0.0 (2025-09-19)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -5,6 +5,7 @@ from personal_finance_shared.database import get_db
|
|
|
5
5
|
import logging
|
|
6
6
|
import requests
|
|
7
7
|
import os
|
|
8
|
+
import httpx
|
|
8
9
|
|
|
9
10
|
logging.basicConfig(level=logging.INFO)
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
@@ -14,39 +15,34 @@ router = APIRouter(
|
|
|
14
15
|
tags=["trades"],
|
|
15
16
|
)
|
|
16
17
|
|
|
17
|
-
MARKET_DATA_SERVICE_URL = os.getenv("MARKET_DATA_SERVICE_URL", "http://personal-finance-backend-market-data:8000")
|
|
18
|
+
MARKET_DATA_SERVICE_URL = os.getenv("MARKET_DATA_SERVICE_URL", "http://personal-finance-backend-market-data.app.svc.cluster.local:8000")
|
|
19
|
+
BALANCE_SERVICE_URL = os.getenv("BALANCE_SERVICE_URL", "http://personal-finance-backend-balance-service.app.svc.cluster.local:8000")
|
|
18
20
|
|
|
19
21
|
def _get_asset_details_from_service(symbol: str) -> dict:
|
|
20
22
|
"""Fetches asset details from the market-data service."""
|
|
21
23
|
try:
|
|
22
24
|
url = f"{MARKET_DATA_SERVICE_URL}/api/market-data/details/{symbol.upper()}"
|
|
23
25
|
logger.info(f"Fetching details from: {url}")
|
|
24
|
-
response = requests.post(url, timeout=10)
|
|
25
|
-
response.raise_for_status()
|
|
26
|
+
response = requests.post(url, timeout=10)
|
|
27
|
+
response.raise_for_status()
|
|
26
28
|
return response.json()
|
|
27
29
|
except requests.exceptions.RequestException as e:
|
|
28
30
|
logger.error(f"Error calling market-data service for {symbol}: {e}")
|
|
29
31
|
return {}
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
33
|
@router.post("/", response_model=schemas.TradeResponse)
|
|
34
|
-
def create_trade(trade: schemas.TradeCreate, db: Session = Depends(get_db)):
|
|
34
|
+
async def create_trade(trade: schemas.TradeCreate, db: Session = Depends(get_db)):
|
|
35
35
|
trade_symbol = trade.symbol.upper()
|
|
36
36
|
logger.info(f"Processing trade for symbol: {trade_symbol}")
|
|
37
37
|
|
|
38
|
-
# Check if asset exists with the provided symbol or its canonical variations
|
|
39
38
|
db_asset = db.query(models.Asset).filter(models.Asset.symbol == trade_symbol).first()
|
|
40
39
|
if not db_asset and '.' not in trade_symbol:
|
|
41
40
|
db_asset = db.query(models.Asset).filter(models.Asset.symbol == f"{trade_symbol}.TO").first()
|
|
42
41
|
|
|
43
42
|
if not db_asset:
|
|
44
43
|
logger.info(f"Asset with symbol {trade_symbol} not found. Fetching details...")
|
|
45
|
-
|
|
46
44
|
asset_details = _get_asset_details_from_service(trade_symbol)
|
|
47
45
|
canonical_symbol = asset_details.get("canonical_symbol", trade_symbol)
|
|
48
|
-
|
|
49
|
-
# Double-check if an asset with the canonical symbol already exists
|
|
50
46
|
db_asset = db.query(models.Asset).filter(models.Asset.symbol == canonical_symbol).first()
|
|
51
47
|
|
|
52
48
|
if not db_asset:
|
|
@@ -66,27 +62,30 @@ def create_trade(trade: schemas.TradeCreate, db: Session = Depends(get_db)):
|
|
|
66
62
|
else:
|
|
67
63
|
logger.info(f"Found existing asset with ID: {db_asset.id}")
|
|
68
64
|
|
|
69
|
-
# --- Balance Validation for 'buy' trades ---
|
|
70
65
|
if trade.trade_type == 'buy':
|
|
71
66
|
investment_account_name = trade.investment_account
|
|
72
67
|
trade_cost = trade.shares * trade.price
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
current_balance = 0
|
|
69
|
+
try:
|
|
70
|
+
async with httpx.AsyncClient() as client:
|
|
71
|
+
response = await client.get(f"{BALANCE_SERVICE_URL}/api/balance/{investment_account_name}")
|
|
72
|
+
response.raise_for_status()
|
|
73
|
+
data = response.json()
|
|
74
|
+
current_balance = data['balance']
|
|
75
|
+
logger.info(f"Calculated cash balance for '{investment_account_name}' is {current_balance}")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error(f"Could not retrieve balance for validation: {e}")
|
|
78
|
+
|
|
79
|
+
if float(current_balance) < float(trade_cost):
|
|
78
80
|
logger.warning(f"Insufficient funds in '{investment_account_name}'. Balance: {current_balance}, Required: {trade_cost}")
|
|
79
81
|
raise HTTPException(
|
|
80
82
|
status_code=400,
|
|
81
83
|
detail=f"Insufficient cash balance in '{investment_account_name}' to complete trade."
|
|
82
84
|
)
|
|
83
|
-
# --- End Validation Logic ---
|
|
84
85
|
|
|
85
|
-
# Create the trade record, linking it to the asset via asset_id
|
|
86
86
|
trade_data = trade.dict(exclude={'symbol', 'name', 'asset_type', 'industry'})
|
|
87
87
|
db_trade = models.Trade(**trade_data, asset_id=db_asset.id)
|
|
88
88
|
|
|
89
|
-
# Log for stock purchase/sale
|
|
90
89
|
if db_trade.trade_type == 'buy':
|
|
91
90
|
logger.info(f"STOCK PURCHASE: Bought {db_trade.shares} shares of {trade_symbol} at ${db_trade.price:.2f} each.")
|
|
92
91
|
elif db_trade.trade_type == 'sell':
|
|
@@ -111,4 +110,4 @@ def read_trades(
|
|
|
111
110
|
|
|
112
111
|
trades = query.order_by(models.Trade.date.desc()).offset(skip).limit(limit).all()
|
|
113
112
|
logger.info(f"Found {len(trades)} trades")
|
|
114
|
-
return trades
|
|
113
|
+
return trades
|
package/requirements.txt
CHANGED