Spaces:
Sleeping
Sleeping
Seed DSE issued shares and transaction fees
Browse files- App/routers/stocks/crud.py +1 -0
- App/routers/stocks/dse_transaction_fees.json +41 -0
- App/routers/stocks/routes.py +35 -1
- App/routers/stocks/seed.py +45 -1
- App/routers/stocks/stock_reference_data.json +262 -0
- db.py +11 -0
App/routers/stocks/crud.py
CHANGED
|
@@ -378,6 +378,7 @@ async def get_market_highlights(limit: int = 5) -> Dict:
|
|
| 378 |
"turnover": profile.turnover,
|
| 379 |
"deals": profile.deals,
|
| 380 |
"market_cap": profile.market_cap,
|
|
|
|
| 381 |
"logo_url": profile.logo_url,
|
| 382 |
"market_segment": profile.market_segment,
|
| 383 |
}
|
|
|
|
| 378 |
"turnover": profile.turnover,
|
| 379 |
"deals": profile.deals,
|
| 380 |
"market_cap": profile.market_cap,
|
| 381 |
+
"total_shares_issued": profile.total_shares_issued,
|
| 382 |
"logo_url": profile.logo_url,
|
| 383 |
"market_segment": profile.market_segment,
|
| 384 |
}
|
App/routers/stocks/dse_transaction_fees.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"currency": "TZS",
|
| 4 |
+
"unit": "percent_of_consideration",
|
| 5 |
+
"source_note": "DSE equity transaction cost table supplied for seed data on 2026-05-13.",
|
| 6 |
+
"bands": [
|
| 7 |
+
{
|
| 8 |
+
"label": "Up to 10 mln",
|
| 9 |
+
"min_consideration": 0,
|
| 10 |
+
"max_consideration": 10000000,
|
| 11 |
+
"brokerage_commission": 1.70,
|
| 12 |
+
"transaction_fee_cmsa": 0.14,
|
| 13 |
+
"transaction_fee_dse": 0.14,
|
| 14 |
+
"fidelity_fee": 0.02,
|
| 15 |
+
"csd_fee": 0.06,
|
| 16 |
+
"total_cost_to_investor": 2.06
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"label": "Next 40 mln",
|
| 20 |
+
"min_consideration": 10000000,
|
| 21 |
+
"max_consideration": 50000000,
|
| 22 |
+
"brokerage_commission": 1.50,
|
| 23 |
+
"transaction_fee_cmsa": 0.14,
|
| 24 |
+
"transaction_fee_dse": 0.14,
|
| 25 |
+
"fidelity_fee": 0.02,
|
| 26 |
+
"csd_fee": 0.06,
|
| 27 |
+
"total_cost_to_investor": 1.86
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"label": "Above 50 mln",
|
| 31 |
+
"min_consideration": 50000000,
|
| 32 |
+
"max_consideration": null,
|
| 33 |
+
"brokerage_commission": 0.80,
|
| 34 |
+
"transaction_fee_cmsa": 0.14,
|
| 35 |
+
"transaction_fee_dse": 0.14,
|
| 36 |
+
"fidelity_fee": 0.02,
|
| 37 |
+
"csd_fee": 0.06,
|
| 38 |
+
"total_cost_to_investor": 1.16
|
| 39 |
+
}
|
| 40 |
+
]
|
| 41 |
+
}
|
App/routers/stocks/routes.py
CHANGED
|
@@ -26,7 +26,7 @@ from .crud import (
|
|
| 26 |
)
|
| 27 |
from .service import fetch_dse_stock_data
|
| 28 |
from .metrics import calculate_metrics
|
| 29 |
-
from .models import Stock, StockPriceData, Dividend
|
| 30 |
from .news_service import (
|
| 31 |
build_news_queries,
|
| 32 |
extract_article_content,
|
|
@@ -42,6 +42,7 @@ from typing import Dict, List, Optional
|
|
| 42 |
from datetime import datetime, timedelta, date
|
| 43 |
from .utils import AsyncCurlCffiDividendScraper, run_stock_import_task
|
| 44 |
from .dse import DseScraper
|
|
|
|
| 45 |
from App.routers.users.utils import get_current_user
|
| 46 |
|
| 47 |
router = APIRouter(prefix="/stocks", tags=["stocks"])
|
|
@@ -100,6 +101,7 @@ async def list_stocks_orm():
|
|
| 100 |
"low": float(latest.low) if latest else (float(profile.day_low) if profile and profile.day_low is not None else None),
|
| 101 |
"volume": latest.volume if latest else (profile.volume if profile else None),
|
| 102 |
"market_cap": latest.market_cap if latest else (profile.market_cap if profile else None),
|
|
|
|
| 103 |
"latest_date": latest.date.isoformat() if latest else None,
|
| 104 |
"logo_url": profile.logo_url if profile else None,
|
| 105 |
"security_type": profile.security_type if profile else None,
|
|
@@ -117,6 +119,38 @@ async def list_stocks_orm():
|
|
| 117 |
raise AppException(status_code=500, message=f"Error retrieving stocks: {str(e)}")
|
| 118 |
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
@router.get("/{symbol}/price/{price_date}", response_model=ResponseModel)
|
| 121 |
async def get_stock_price_by_date(symbol: str, price_date: str):
|
| 122 |
"""Return the closing price for a stock on a given date (or nearest prior trading day)."""
|
|
|
|
| 26 |
)
|
| 27 |
from .service import fetch_dse_stock_data
|
| 28 |
from .metrics import calculate_metrics
|
| 29 |
+
from .models import Stock, StockPriceData, Dividend, StockProfile
|
| 30 |
from .news_service import (
|
| 31 |
build_news_queries,
|
| 32 |
extract_article_content,
|
|
|
|
| 42 |
from datetime import datetime, timedelta, date
|
| 43 |
from .utils import AsyncCurlCffiDividendScraper, run_stock_import_task
|
| 44 |
from .dse import DseScraper
|
| 45 |
+
from .seed import load_dse_transaction_fee_seed_data
|
| 46 |
from App.routers.users.utils import get_current_user
|
| 47 |
|
| 48 |
router = APIRouter(prefix="/stocks", tags=["stocks"])
|
|
|
|
| 101 |
"low": float(latest.low) if latest else (float(profile.day_low) if profile and profile.day_low is not None else None),
|
| 102 |
"volume": latest.volume if latest else (profile.volume if profile else None),
|
| 103 |
"market_cap": latest.market_cap if latest else (profile.market_cap if profile else None),
|
| 104 |
+
"total_shares_issued": profile.total_shares_issued if profile else None,
|
| 105 |
"latest_date": latest.date.isoformat() if latest else None,
|
| 106 |
"logo_url": profile.logo_url if profile else None,
|
| 107 |
"security_type": profile.security_type if profile else None,
|
|
|
|
| 119 |
raise AppException(status_code=500, message=f"Error retrieving stocks: {str(e)}")
|
| 120 |
|
| 121 |
|
| 122 |
+
@router.get("/reference/issued-shares", response_model=ResponseModel)
|
| 123 |
+
async def list_issued_shares():
|
| 124 |
+
profiles = await StockProfile.all().select_related("stock").order_by("stock__symbol")
|
| 125 |
+
data = [
|
| 126 |
+
{
|
| 127 |
+
"symbol": profile.stock.symbol,
|
| 128 |
+
"name": profile.stock.name,
|
| 129 |
+
"isin": profile.security_id,
|
| 130 |
+
"security_type": profile.security_type,
|
| 131 |
+
"issued_shares": profile.total_shares_issued,
|
| 132 |
+
"source": profile.source,
|
| 133 |
+
}
|
| 134 |
+
for profile in profiles
|
| 135 |
+
if profile.total_shares_issued is not None
|
| 136 |
+
]
|
| 137 |
+
return ResponseModel(
|
| 138 |
+
success=True,
|
| 139 |
+
message="Issued share reference data retrieved",
|
| 140 |
+
data={"stocks": data, "count": len(data)},
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
@router.get("/reference/transaction-fees", response_model=ResponseModel)
|
| 145 |
+
async def get_dse_transaction_fees():
|
| 146 |
+
payload = load_dse_transaction_fee_seed_data()
|
| 147 |
+
return ResponseModel(
|
| 148 |
+
success=True,
|
| 149 |
+
message="DSE transaction fee seed data retrieved",
|
| 150 |
+
data=payload,
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
@router.get("/{symbol}/price/{price_date}", response_model=ResponseModel)
|
| 155 |
async def get_stock_price_by_date(symbol: str, price_date: str):
|
| 156 |
"""Return the closing price for a stock on a given date (or nearest prior trading day)."""
|
App/routers/stocks/seed.py
CHANGED
|
@@ -3,10 +3,12 @@ from datetime import date
|
|
| 3 |
from pathlib import Path
|
| 4 |
from decimal import Decimal, InvalidOperation
|
| 5 |
|
| 6 |
-
from .models import Stock, StockCompanyInfo, Dividend
|
| 7 |
|
| 8 |
STOCK_COMPANY_DATA_PATH = Path(__file__).parent / "stock_company_data.json"
|
| 9 |
STOCK_DIVIDEND_DATA_PATH = Path(__file__).parent / "stock_dividends_data.json"
|
|
|
|
|
|
|
| 10 |
|
| 11 |
|
| 12 |
def load_stock_company_seed_data() -> dict:
|
|
@@ -23,6 +25,20 @@ def load_stock_dividend_seed_data() -> dict:
|
|
| 23 |
return {"dividends": []}
|
| 24 |
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
def _clip(value: str | None, max_length: int) -> str | None:
|
| 27 |
if value is None:
|
| 28 |
return None
|
|
@@ -125,3 +141,31 @@ async def sync_stock_dividends_from_json() -> int:
|
|
| 125 |
synced += 1
|
| 126 |
|
| 127 |
return synced
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from pathlib import Path
|
| 4 |
from decimal import Decimal, InvalidOperation
|
| 5 |
|
| 6 |
+
from .models import Stock, StockCompanyInfo, StockProfile, Dividend
|
| 7 |
|
| 8 |
STOCK_COMPANY_DATA_PATH = Path(__file__).parent / "stock_company_data.json"
|
| 9 |
STOCK_DIVIDEND_DATA_PATH = Path(__file__).parent / "stock_dividends_data.json"
|
| 10 |
+
STOCK_REFERENCE_DATA_PATH = Path(__file__).parent / "stock_reference_data.json"
|
| 11 |
+
DSE_TRANSACTION_FEES_PATH = Path(__file__).parent / "dse_transaction_fees.json"
|
| 12 |
|
| 13 |
|
| 14 |
def load_stock_company_seed_data() -> dict:
|
|
|
|
| 25 |
return {"dividends": []}
|
| 26 |
|
| 27 |
|
| 28 |
+
def load_stock_reference_seed_data() -> dict:
|
| 29 |
+
try:
|
| 30 |
+
return json.loads(STOCK_REFERENCE_DATA_PATH.read_text(encoding="utf-8"))
|
| 31 |
+
except Exception:
|
| 32 |
+
return {"stocks": []}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def load_dse_transaction_fee_seed_data() -> dict:
|
| 36 |
+
try:
|
| 37 |
+
return json.loads(DSE_TRANSACTION_FEES_PATH.read_text(encoding="utf-8"))
|
| 38 |
+
except Exception:
|
| 39 |
+
return {"bands": []}
|
| 40 |
+
|
| 41 |
+
|
| 42 |
def _clip(value: str | None, max_length: int) -> str | None:
|
| 43 |
if value is None:
|
| 44 |
return None
|
|
|
|
| 141 |
synced += 1
|
| 142 |
|
| 143 |
return synced
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
async def sync_stock_reference_data_from_json() -> int:
|
| 147 |
+
payload = load_stock_reference_seed_data()
|
| 148 |
+
synced = 0
|
| 149 |
+
|
| 150 |
+
for entry in payload.get("stocks", []):
|
| 151 |
+
symbol = str(entry.get("symbol") or "").strip().upper()
|
| 152 |
+
issued_shares = entry.get("issued_shares")
|
| 153 |
+
if not symbol or issued_shares is None:
|
| 154 |
+
continue
|
| 155 |
+
|
| 156 |
+
stock = await Stock.get_or_none(symbol=symbol)
|
| 157 |
+
if stock is None:
|
| 158 |
+
stock = await Stock.create(symbol=symbol, name=entry.get("legal_name") or symbol)
|
| 159 |
+
elif entry.get("legal_name") and stock.name == stock.symbol:
|
| 160 |
+
stock.name = entry["legal_name"]
|
| 161 |
+
await stock.save(update_fields=["name", "updated_at"])
|
| 162 |
+
|
| 163 |
+
profile, _ = await StockProfile.get_or_create(stock=stock)
|
| 164 |
+
profile.security_id = _clip(entry.get("isin"), 50)
|
| 165 |
+
profile.security_type = _clip(entry.get("security_type"), 30)
|
| 166 |
+
profile.total_shares_issued = int(issued_shares)
|
| 167 |
+
profile.source = _clip(payload.get("source_name"), 50)
|
| 168 |
+
await profile.save()
|
| 169 |
+
synced += 1
|
| 170 |
+
|
| 171 |
+
return synced
|
App/routers/stocks/stock_reference_data.json
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"last_verified_at": "2026-05-13",
|
| 4 |
+
"source_name": "CSD & Registry Company Limited Issuer Companies List",
|
| 5 |
+
"source_url": "https://csdr.co.tz/content/issuer-companies-list",
|
| 6 |
+
"stocks": [
|
| 7 |
+
{
|
| 8 |
+
"symbol": "CRDB",
|
| 9 |
+
"legal_name": "CRDB Bank PLC",
|
| 10 |
+
"isin": "TZ1996100305",
|
| 11 |
+
"issued_shares": 2611838584,
|
| 12 |
+
"security_type": "Equity",
|
| 13 |
+
"listing_date": "2009-06-17",
|
| 14 |
+
"listing_class": "domestic"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"symbol": "DSE",
|
| 18 |
+
"legal_name": "Dar es Salaam Stock Exchange PLC",
|
| 19 |
+
"isin": "TZ1996102434",
|
| 20 |
+
"issued_shares": 23824020,
|
| 21 |
+
"security_type": "Equity",
|
| 22 |
+
"listing_date": "2016-07-12",
|
| 23 |
+
"listing_class": "domestic"
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"symbol": "DCB",
|
| 27 |
+
"legal_name": "DCB Commercial Bank PLC",
|
| 28 |
+
"isin": "TZ1996100214",
|
| 29 |
+
"issued_shares": 195293826,
|
| 30 |
+
"security_type": "Equity",
|
| 31 |
+
"listing_date": "2008-09-16",
|
| 32 |
+
"listing_class": "domestic"
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"symbol": "JATU",
|
| 36 |
+
"legal_name": "JATU Public Limited Company",
|
| 37 |
+
"isin": "TZ1996103804",
|
| 38 |
+
"issued_shares": 19938837,
|
| 39 |
+
"security_type": "Equity",
|
| 40 |
+
"listing_date": "2020-11-23",
|
| 41 |
+
"listing_class": "domestic"
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"symbol": "MBP",
|
| 45 |
+
"legal_name": "Maendeleo Bank PLC",
|
| 46 |
+
"isin": "TZ1996101683",
|
| 47 |
+
"issued_shares": 26431550,
|
| 48 |
+
"security_type": "Equity",
|
| 49 |
+
"listing_date": "2013-11-04",
|
| 50 |
+
"listing_class": "domestic"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"symbol": "MKCB",
|
| 54 |
+
"legal_name": "Mkombozi Commercial Bank PLC",
|
| 55 |
+
"isin": "TZ1996101972",
|
| 56 |
+
"issued_shares": 23555002,
|
| 57 |
+
"security_type": "Equity",
|
| 58 |
+
"listing_date": "2014-12-29",
|
| 59 |
+
"listing_class": "domestic"
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"symbol": "MUCOBA",
|
| 63 |
+
"legal_name": "MUCOBA Bank PLC",
|
| 64 |
+
"isin": "TZ1996102418",
|
| 65 |
+
"issued_shares": 32666227,
|
| 66 |
+
"security_type": "Equity",
|
| 67 |
+
"listing_date": "2016-06-08",
|
| 68 |
+
"listing_class": "domestic"
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"symbol": "MCB",
|
| 72 |
+
"legal_name": "Mwalimu Commercial Bank PLC",
|
| 73 |
+
"isin": "TZ1996102129",
|
| 74 |
+
"issued_shares": 61824920,
|
| 75 |
+
"security_type": "Equity",
|
| 76 |
+
"listing_date": "2015-11-27",
|
| 77 |
+
"listing_class": "domestic"
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"symbol": "NICO",
|
| 81 |
+
"legal_name": "National Investment Company Limited",
|
| 82 |
+
"isin": "TZ1996103077",
|
| 83 |
+
"issued_shares": 61644834,
|
| 84 |
+
"security_type": "Equity",
|
| 85 |
+
"listing_date": "2018-06-06",
|
| 86 |
+
"listing_class": "domestic"
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"symbol": "NMB",
|
| 90 |
+
"legal_name": "National Microfinance Bank PLC",
|
| 91 |
+
"isin": "TZ1996100222",
|
| 92 |
+
"issued_shares": 500000000,
|
| 93 |
+
"security_type": "Equity",
|
| 94 |
+
"listing_date": "2008-11-06",
|
| 95 |
+
"listing_class": "domestic"
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"symbol": "PAL",
|
| 99 |
+
"legal_name": "Precision Air Services PLC",
|
| 100 |
+
"isin": "TZ1996101048",
|
| 101 |
+
"issued_shares": 160469800,
|
| 102 |
+
"security_type": "Equity",
|
| 103 |
+
"listing_date": "2011-12-21",
|
| 104 |
+
"listing_class": "domestic"
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"symbol": "SWALA",
|
| 108 |
+
"legal_name": "Swala Oil and Gas Tanzania PLC",
|
| 109 |
+
"isin": "TZ1996101865",
|
| 110 |
+
"issued_shares": 106201621,
|
| 111 |
+
"security_type": "Equity",
|
| 112 |
+
"listing_date": "2014-08-11",
|
| 113 |
+
"listing_class": "domestic"
|
| 114 |
+
},
|
| 115 |
+
{
|
| 116 |
+
"symbol": "SWIS",
|
| 117 |
+
"legal_name": "Swissport Tanzania PLC",
|
| 118 |
+
"isin": "TZ1996100040",
|
| 119 |
+
"issued_shares": 36000000,
|
| 120 |
+
"security_type": "Equity",
|
| 121 |
+
"listing_date": "2003-06-03",
|
| 122 |
+
"listing_class": "domestic"
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"symbol": "TCCL",
|
| 126 |
+
"legal_name": "Tanga Cement PLC",
|
| 127 |
+
"isin": "TZ1996100057",
|
| 128 |
+
"issued_shares": 63671045,
|
| 129 |
+
"security_type": "Equity",
|
| 130 |
+
"listing_date": "2002-09-26",
|
| 131 |
+
"listing_class": "domestic"
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"symbol": "TBL",
|
| 135 |
+
"legal_name": "Tanzania Breweries Limited PLC",
|
| 136 |
+
"isin": "TZ1996100016",
|
| 137 |
+
"issued_shares": 295056063,
|
| 138 |
+
"security_type": "Equity",
|
| 139 |
+
"listing_date": "1998-09-19",
|
| 140 |
+
"listing_class": "domestic"
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"symbol": "TCC",
|
| 144 |
+
"legal_name": "Tanzania Cigarette Company Limited",
|
| 145 |
+
"isin": "TZ1996100032",
|
| 146 |
+
"issued_shares": 100000000,
|
| 147 |
+
"security_type": "Equity",
|
| 148 |
+
"listing_date": "2000-11-16",
|
| 149 |
+
"listing_class": "domestic"
|
| 150 |
+
},
|
| 151 |
+
{
|
| 152 |
+
"symbol": "TPCC",
|
| 153 |
+
"legal_name": "Tanzania Portland Cement Company Limited",
|
| 154 |
+
"isin": "TZ1996100024",
|
| 155 |
+
"issued_shares": 179923100,
|
| 156 |
+
"security_type": "Equity",
|
| 157 |
+
"listing_date": "2006-09-29",
|
| 158 |
+
"listing_class": "domestic"
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"symbol": "TTP",
|
| 162 |
+
"legal_name": "TATEPA Limited",
|
| 163 |
+
"isin": "TZ1996100065",
|
| 164 |
+
"issued_shares": 95057184,
|
| 165 |
+
"security_type": "Equity",
|
| 166 |
+
"listing_date": "1999-12-07",
|
| 167 |
+
"listing_class": "domestic"
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"symbol": "AFRIPRISE",
|
| 171 |
+
"aliases": ["TICL"],
|
| 172 |
+
"legal_name": "Afriprise Investment PLC",
|
| 173 |
+
"isin": "TZ1996103010",
|
| 174 |
+
"issued_shares": 146034913,
|
| 175 |
+
"security_type": "Equity",
|
| 176 |
+
"listing_date": "2018-03-16",
|
| 177 |
+
"listing_class": "domestic"
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"symbol": "TOL",
|
| 181 |
+
"legal_name": "TOL Gases PLC",
|
| 182 |
+
"isin": "TZ1996100008",
|
| 183 |
+
"issued_shares": 57505963,
|
| 184 |
+
"security_type": "Equity",
|
| 185 |
+
"listing_date": "1998-04-15",
|
| 186 |
+
"listing_class": "domestic"
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"symbol": "VODA",
|
| 190 |
+
"legal_name": "Vodacom Tanzania PLC",
|
| 191 |
+
"isin": "TZ1996102715",
|
| 192 |
+
"issued_shares": 2240000300,
|
| 193 |
+
"security_type": "Equity",
|
| 194 |
+
"listing_date": "2017-08-15",
|
| 195 |
+
"listing_class": "domestic"
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"symbol": "YETU",
|
| 199 |
+
"legal_name": "Yetu Microfinance PLC",
|
| 200 |
+
"isin": "TZ1996102343",
|
| 201 |
+
"issued_shares": 12112894,
|
| 202 |
+
"security_type": "Equity",
|
| 203 |
+
"listing_date": "2016-03-10",
|
| 204 |
+
"listing_class": "domestic"
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"symbol": "EABL",
|
| 208 |
+
"legal_name": "East African Breweries Limited",
|
| 209 |
+
"isin": "KE0000000216",
|
| 210 |
+
"issued_shares": 790774356,
|
| 211 |
+
"security_type": "Equity",
|
| 212 |
+
"listing_date": "2005-06-29",
|
| 213 |
+
"listing_class": "foreign_cross_listed"
|
| 214 |
+
},
|
| 215 |
+
{
|
| 216 |
+
"symbol": "JHL",
|
| 217 |
+
"legal_name": "Jubilee Holdings Limited",
|
| 218 |
+
"isin": "KE0000000273",
|
| 219 |
+
"issued_shares": 72472950,
|
| 220 |
+
"security_type": "Equity",
|
| 221 |
+
"listing_date": "2006-12-20",
|
| 222 |
+
"listing_class": "foreign_cross_listed"
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"symbol": "KA",
|
| 226 |
+
"legal_name": "Kenya Airways Limited",
|
| 227 |
+
"isin": "KE0000000307",
|
| 228 |
+
"issued_shares": 5681423711,
|
| 229 |
+
"security_type": "Equity",
|
| 230 |
+
"listing_date": "2004-10-01",
|
| 231 |
+
"listing_class": "foreign_cross_listed"
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"symbol": "KCB",
|
| 235 |
+
"legal_name": "KCB Group Limited",
|
| 236 |
+
"isin": "KE0000000315",
|
| 237 |
+
"issued_shares": 2970340000,
|
| 238 |
+
"security_type": "Equity",
|
| 239 |
+
"listing_date": "2008-12-23",
|
| 240 |
+
"listing_class": "foreign_cross_listed"
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
"symbol": "NMG",
|
| 244 |
+
"legal_name": "Nation Media Group",
|
| 245 |
+
"isin": "KE0000000380",
|
| 246 |
+
"issued_shares": 188542286,
|
| 247 |
+
"security_type": "Equity",
|
| 248 |
+
"listing_date": "2014-06-12",
|
| 249 |
+
"listing_class": "foreign_cross_listed"
|
| 250 |
+
},
|
| 251 |
+
{
|
| 252 |
+
"symbol": "USL",
|
| 253 |
+
"legal_name": "Uchumi Supermarket Limited",
|
| 254 |
+
"isin": "KE0000000489",
|
| 255 |
+
"issued_shares": 364965594,
|
| 256 |
+
"security_type": "Equity",
|
| 257 |
+
"listing_date": "2014-08-15",
|
| 258 |
+
"listing_class": "foreign_cross_listed"
|
| 259 |
+
}
|
| 260 |
+
]
|
| 261 |
+
}
|
| 262 |
+
|
db.py
CHANGED
|
@@ -69,6 +69,7 @@ async def init_db():
|
|
| 69 |
await ensure_user_auth_profile_schema()
|
| 70 |
await ensure_admin_schema()
|
| 71 |
await ensure_stock_news_schema()
|
|
|
|
| 72 |
await sync_fund_info_seed_data()
|
| 73 |
await sync_stock_company_seed_data()
|
| 74 |
await sync_stock_dividend_seed_data()
|
|
@@ -154,6 +155,16 @@ async def sync_stock_company_seed_data():
|
|
| 154 |
return
|
| 155 |
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
async def sync_stock_dividend_seed_data():
|
| 158 |
"""Populate curated stock dividend history from bundled research JSON."""
|
| 159 |
try:
|
|
|
|
| 69 |
await ensure_user_auth_profile_schema()
|
| 70 |
await ensure_admin_schema()
|
| 71 |
await ensure_stock_news_schema()
|
| 72 |
+
await sync_stock_reference_seed_data()
|
| 73 |
await sync_fund_info_seed_data()
|
| 74 |
await sync_stock_company_seed_data()
|
| 75 |
await sync_stock_dividend_seed_data()
|
|
|
|
| 155 |
return
|
| 156 |
|
| 157 |
|
| 158 |
+
async def sync_stock_reference_seed_data():
|
| 159 |
+
"""Populate stock issued-share reference data from the bundled seed JSON."""
|
| 160 |
+
try:
|
| 161 |
+
from App.routers.stocks.seed import sync_stock_reference_data_from_json
|
| 162 |
+
|
| 163 |
+
await sync_stock_reference_data_from_json()
|
| 164 |
+
except Exception:
|
| 165 |
+
return
|
| 166 |
+
|
| 167 |
+
|
| 168 |
async def sync_stock_dividend_seed_data():
|
| 169 |
"""Populate curated stock dividend history from bundled research JSON."""
|
| 170 |
try:
|