@marcos_feitoza/personal-finance-backen-trades-assets 1.0.1 → 1.0.3
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.3](https://github.com/MarcosOps/personal-finance-backend-trades-assets/compare/v1.0.2...v1.0.3) (2025-11-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* crypto symbol working ([9c61bf9](https://github.com/MarcosOps/personal-finance-backend-trades-assets/commit/9c61bf91b382fc7563985c1973fc6a37e9e361c1))
|
|
7
|
+
|
|
8
|
+
## [1.0.2](https://github.com/MarcosOps/personal-finance-backend-trades-assets/compare/v1.0.1...v1.0.2) (2025-10-17)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* invest working ([d0ca897](https://github.com/MarcosOps/personal-finance-backend-trades-assets/commit/d0ca897cf8018aeecd2c10d037d94e987e241a58))
|
|
14
|
+
|
|
1
15
|
## [1.0.1](https://github.com/MarcosOps/personal-finance-backend-trades-assets/compare/v1.0.0...v1.0.1) (2025-09-24)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -18,7 +18,13 @@ def create_asset(asset: schemas.AssetCreate, db: Session = Depends(get_db)):
|
|
|
18
18
|
db_asset = db.query(models.Asset).filter(models.Asset.symbol == asset.symbol).first()
|
|
19
19
|
if db_asset:
|
|
20
20
|
raise HTTPException(status_code=400, detail="Asset with this symbol already exists")
|
|
21
|
-
db_asset = models.Asset(
|
|
21
|
+
db_asset = models.Asset(
|
|
22
|
+
symbol=asset.symbol,
|
|
23
|
+
name=asset.name,
|
|
24
|
+
asset_type=asset.asset_type,
|
|
25
|
+
industry=asset.industry,
|
|
26
|
+
id_crypto=asset.id_crypto
|
|
27
|
+
)
|
|
22
28
|
db.add(db_asset)
|
|
23
29
|
db.commit()
|
|
24
30
|
db.refresh(db_asset)
|
|
@@ -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,79 +15,68 @@ 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
|
-
if not db_asset and '.' not in trade_symbol:
|
|
41
|
-
db_asset = db.query(models.Asset).filter(models.Asset.symbol == f"{trade_symbol}.TO").first()
|
|
42
39
|
|
|
43
40
|
if not db_asset:
|
|
44
|
-
logger.info(f"Asset with symbol {trade_symbol} not found.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
name=asset_details.get("name", canonical_symbol),
|
|
57
|
-
asset_type=asset_details.get("asset_type", "Unknown"),
|
|
58
|
-
industry=asset_details.get("industry", "N/A")
|
|
59
|
-
)
|
|
60
|
-
db.add(db_asset)
|
|
61
|
-
db.commit()
|
|
62
|
-
db.refresh(db_asset)
|
|
63
|
-
logger.info(f"Created new asset with ID: {db_asset.id}")
|
|
64
|
-
else:
|
|
65
|
-
logger.info(f"Found existing asset with canonical symbol: {canonical_symbol}")
|
|
41
|
+
logger.info(f"Asset with symbol {trade_symbol} not found. Creating new asset...")
|
|
42
|
+
db_asset = models.Asset(
|
|
43
|
+
symbol=trade_symbol,
|
|
44
|
+
name=trade.name,
|
|
45
|
+
asset_type=trade.asset_type,
|
|
46
|
+
industry=trade.industry,
|
|
47
|
+
id_crypto=trade.id_crypto if hasattr(trade, 'id_crypto') else None
|
|
48
|
+
)
|
|
49
|
+
db.add(db_asset)
|
|
50
|
+
db.commit()
|
|
51
|
+
db.refresh(db_asset)
|
|
52
|
+
logger.info(f"Created new asset with ID: {db_asset.id}")
|
|
66
53
|
else:
|
|
67
54
|
logger.info(f"Found existing asset with ID: {db_asset.id}")
|
|
68
55
|
|
|
69
|
-
# --- Balance Validation for 'buy' trades ---
|
|
70
56
|
if trade.trade_type == 'buy':
|
|
71
57
|
investment_account_name = trade.investment_account
|
|
72
58
|
trade_cost = trade.shares * trade.price
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
59
|
+
current_balance = 0
|
|
60
|
+
try:
|
|
61
|
+
async with httpx.AsyncClient() as client:
|
|
62
|
+
response = await client.get(f"{BALANCE_SERVICE_URL}/api/balance/{investment_account_name}")
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
data = response.json()
|
|
65
|
+
current_balance = data['balance']
|
|
66
|
+
logger.info(f"Calculated cash balance for '{investment_account_name}' is {current_balance}")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error(f"Could not retrieve balance for validation: {e}")
|
|
69
|
+
|
|
70
|
+
if float(current_balance) < float(trade_cost):
|
|
78
71
|
logger.warning(f"Insufficient funds in '{investment_account_name}'. Balance: {current_balance}, Required: {trade_cost}")
|
|
79
72
|
raise HTTPException(
|
|
80
73
|
status_code=400,
|
|
81
74
|
detail=f"Insufficient cash balance in '{investment_account_name}' to complete trade."
|
|
82
75
|
)
|
|
83
|
-
# --- End Validation Logic ---
|
|
84
76
|
|
|
85
|
-
|
|
86
|
-
trade_data = trade.dict(exclude={'symbol', 'name', 'asset_type', 'industry'})
|
|
77
|
+
trade_data = trade.dict(exclude={'symbol', 'name', 'asset_type', 'industry', 'id_crypto'})
|
|
87
78
|
db_trade = models.Trade(**trade_data, asset_id=db_asset.id)
|
|
88
79
|
|
|
89
|
-
# Log for stock purchase/sale
|
|
90
80
|
if db_trade.trade_type == 'buy':
|
|
91
81
|
logger.info(f"STOCK PURCHASE: Bought {db_trade.shares} shares of {trade_symbol} at ${db_trade.price:.2f} each.")
|
|
92
82
|
elif db_trade.trade_type == 'sell':
|
|
@@ -111,4 +101,4 @@ def read_trades(
|
|
|
111
101
|
|
|
112
102
|
trades = query.order_by(models.Trade.date.desc()).offset(skip).limit(limit).all()
|
|
113
103
|
logger.info(f"Found {len(trades)} trades")
|
|
114
|
-
return trades
|
|
104
|
+
return trades
|