Files
Mutual_Fund/scripts/seed_holdings_for_customer.py

140 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""按 fin_product 为指定客户生成 fin_holdings 持仓数据(本地开发/演示用)。
用法:
python scripts/seed_holdings_for_customer.py --customer-id 18
python scripts/seed_holdings_for_customer.py --customer-id 18 --count 5 --force
规则:
- 只挑选 fin_product 中 status=在售 的产品,按 id 升序取前 count 只;
- 每笔持仓的买入净值 = 当前净值 × (1 - 浮动),浮动由固定 seed 生成,保证可复现;
- shares = cost_amount / 买入净值(4 位小数),current_value = shares × 当前净值;
- profit_loss / profit_ratio 由上述字段推导;
- 客户已有持仓时默认拒绝,需 --force 才会追加。
"""
from __future__ import annotations
import argparse
import asyncio
import random
import sys
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from sqlalchemy import select
from config.database.mysql import get_session_factory
from model.fin_holdings import FinHoldings
from model.fin_product import FinProduct
from model.sys_user import SysUser
_MONEY = Decimal("0.01")
_SHARES = Decimal("0.0001")
_RATIO = Decimal("0.0001")
def _money(value: Decimal) -> Decimal:
return value.quantize(_MONEY, rounding=ROUND_HALF_UP)
async def seed(customer_id: int, *, count: int, force: bool) -> list[dict]:
session_factory = get_session_factory()
async with session_factory() as db:
user = await db.get(SysUser, customer_id)
if user is None or user.user_type != "CUSTOMER":
raise SystemExit(f"customer_id={customer_id} 不是客户用户或不存在")
existing = (
await db.execute(
select(FinHoldings).where(FinHoldings.customer_id == customer_id)
)
).scalars().all()
if existing and not force:
raise SystemExit(
f"客户 {customer_id} 已有 {len(existing)} 条持仓,如需追加请加 --force"
)
products = (
await db.execute(
select(FinProduct)
.where(FinProduct.status == "在售")
.order_by(FinProduct.id)
.limit(count)
)
).scalars().all()
if not products:
raise SystemExit("fin_product 中没有在售产品")
rng = random.Random(f"holdings-{customer_id}")
created: list[dict] = []
for product in products:
nav = product.nav or Decimal("1.000000")
cost_amount = _money(Decimal(rng.randint(5_000, 50_000)))
# 买入净值在当前净值的 88%~97% 之间浮动,形成有涨有跌的持仓
buy_nav = _money(nav * Decimal(str(round(rng.uniform(0.88, 0.97), 6))))
if buy_nav <= 0:
buy_nav = Decimal("1.0000")
shares = (cost_amount / buy_nav).quantize(_SHARES, rounding=ROUND_HALF_UP)
current_value = _money(shares * nav)
profit_loss = _money(current_value - cost_amount)
profit_ratio = (profit_loss / cost_amount).quantize(_RATIO, rounding=ROUND_HALF_UP)
db.add(
FinHoldings(
customer_id=customer_id,
product_id=product.id,
shares=shares,
cost_amount=cost_amount,
current_value=current_value,
profit_loss=profit_loss,
profit_ratio=profit_ratio,
status="持有中",
)
)
created.append(
{
"product_id": product.id,
"product_name": product.product_name,
"buy_nav": str(buy_nav),
"nav": str(nav),
"shares": str(shares),
"cost_amount": str(cost_amount),
"current_value": str(current_value),
"profit_loss": str(profit_loss),
"profit_ratio": str(profit_ratio),
}
)
await db.commit()
return created
def main() -> None:
parser = argparse.ArgumentParser(description="按 fin_product 生成客户持仓数据")
parser.add_argument("--customer-id", type=int, required=True)
parser.add_argument("--count", type=int, default=8, help="生成持仓条数(默认 8)")
parser.add_argument(
"--force", action="store_true", help="客户已有持仓时仍允许追加"
)
args = parser.parse_args()
created = asyncio.run(seed(args.customer_id, count=args.count, force=args.force))
total_cost = sum(Decimal(item["cost_amount"]) for item in created)
total_value = sum(Decimal(item["current_value"]) for item in created)
print(f"客户 {args.customer_id} 新增持仓 {len(created)} 条:")
for item in created:
print(
f" [{item['product_id']}] {item['product_name'][:24]}… "
f"买入净值={item['buy_nav']} 当前净值={item['nav']} "
f"份额={item['shares']} 成本={item['cost_amount']} "
f"市值={item['current_value']} 盈亏={item['profit_loss']} "
f"({item['profit_ratio']})"
)
print(f"合计:成本 {total_cost} 元,市值 {total_value} 元,"
f"盈亏 {total_value - total_cost} 元")
if __name__ == "__main__":
main()