Skip to content

Commit

Permalink
๐Ÿ—ƒ๏ธ update position data structure (#102)
Browse files Browse the repository at this point in the history
๐Ÿ—ƒ๏ธ update position data structure

chore: release version 0.9.5

---

<details open><summary>Generated summary (powered by <a href="https://app.graphite.dev">Graphite</a>)</summary>

> ## TL;DR
> This pull request updates the version of the `pyrb` package to `0.9.5`, introduces new asset class enums, refactors the `Position` model to include an `Asset` object, and updates related code to reflect these changes.
> 
> ## What changed
> - Updated the version of the `pyrb` package to `0.9.5` in `pyproject.toml`.
> - Added new asset class enums (`STOCK`, `BOND`, `CASH`, `COMMODITY`, `OTHER`) in `enums.py`.
> - Refactored the `Position` model to include an `Asset` object with `symbol` and `label` fields.
> - Updated code in various files to use the new `Asset` object instead of directly accessing the `symbol` field.
> 
> ## How to test
> 1. Update the `pyrb` package to version `0.9.5`.
> 2. Check the new asset class enums (`STOCK`, `BOND`, `CASH`, `COMMODITY`, `OTHER`) in `enums.py`.
> 3. Verify that the `Position` model now includes an `Asset` object with `symbol` and `label` fields.
> 4. Test related code changes in `controllers/cli/main.py`, `repositories/brokerages/ebest/portfolio.py`, `tests/conftest.py`, and `tests/controllers/test_api.py`.
> 
> ## Why make this change
> - Introduces a more structured approach to handling asset classes in the `pyrb` package.
> - Enhances the readability and maintainability of the code by using an `Asset` object in the `Position` model.
> - Aligns the codebase with best practices for modeling financial assets and positions.
</details>
  • Loading branch information
mingi3314 authored Mar 16, 2024
1 parent 9f0b1e1 commit ed382da
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 15 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyrb"
version = "0.9.4"
version = "0.9.5"
description = "Python Rebalancer"
authors = ["Minki Kim <[email protected]>"]
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion pyrb/controllers/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def _print_portfolio_table(context: RebalanceContext) -> None:
rtn_style = "blue"

table.add_row(
position.symbol,
position.asset.symbol,
_format(position.quantity, "number"),
_format(position.sellable_quantity, "number"),
_format(position.average_buy_price, "currency"),
Expand Down
8 changes: 8 additions & 0 deletions pyrb/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ class OrderSide(StrEnum):

class AssetAllocationStrategyEnum(StrEnum):
ALL_WEATHER_KR = "all-weather-kr"


class AssetClassEnum(StrEnum):
STOCK = "STOCK"
BOND = "BOND"
CASH = "CASH"
COMMODITY = "COMMODITY"
OTHER = "OTHER"
26 changes: 24 additions & 2 deletions pyrb/models/position.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
from pydantic import BaseModel, PositiveFloat, PositiveInt
from pydantic import BaseModel, PositiveFloat, PositiveInt, computed_field

from pyrb.enums import AssetClassEnum

class Position(BaseModel):
asset_class_by_symbols: dict[str, AssetClassEnum] = {
"361580": AssetClassEnum.STOCK, # KBSTAR 200TR
"379800": AssetClassEnum.STOCK, # KODEX ๋ฏธ๊ตญS&P500TR
"411060": AssetClassEnum.COMMODITY, # ACE KRX๊ธˆํ˜„๋ฌผ
"365780": AssetClassEnum.BOND, # ACE ๊ตญ๊ณ ์ฑ„10๋…„
"308620": AssetClassEnum.BOND, # KODEX ๋ฏธ๊ตญ์ฑ„10๋…„์„ ๋ฌผ
"272580": AssetClassEnum.CASH, # TIGER ๋‹จ๊ธฐ์ฑ„๊ถŒ์•กํ‹ฐ๋ธŒ
"005930": AssetClassEnum.STOCK, # ์‚ผ์„ฑ์ „์ž
"000660": AssetClassEnum.STOCK, # SKํ•˜์ด๋‹‰์Šค
}


class Asset(BaseModel):
symbol: str # ์ข…๋ชฉ์ฝ”๋“œ
label: str # ์ข…๋ชฉ๋ช…

@computed_field
def asset_class(self) -> str:
return asset_class_by_symbols.get(self.symbol, AssetClassEnum.OTHER)


class Position(BaseModel):
asset: Asset # ์ข…๋ชฉ์ฝ”๋“œ
quantity: PositiveInt # ๋ณด์œ ์ˆ˜๋Ÿ‰
sellable_quantity: PositiveInt # ๋งค๋„๊ฐ€๋Šฅ์ˆ˜๋Ÿ‰
average_buy_price: PositiveFloat # ๋งค์ž…๋‹จ๊ฐ€
Expand Down
10 changes: 6 additions & 4 deletions pyrb/repositories/brokerages/ebest/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pydantic import NonNegativeFloat

from pyrb.models.position import Position
from pyrb.models.position import Asset, Position
from pyrb.repositories.brokerages.base.portfolio import Portfolio
from pyrb.repositories.brokerages.ebest.client import EbestAPIClient

Expand All @@ -24,7 +24,7 @@ def cash_balance(self) -> NonNegativeFloat:
def positions(self) -> list[Position]:
positions = [
Position(
symbol=item["expcode"],
asset=Asset(symbol=item["expcode"], label=item["hname"]),
quantity=item["janqty"],
sellable_quantity=item["mdposqt"],
average_buy_price=item["pamt"],
Expand All @@ -38,10 +38,12 @@ def positions(self) -> list[Position]:

@property
def holding_symbols(self) -> list[str]:
return [position.symbol for position in self.positions]
return [position.asset.symbol for position in self.positions]

def get_position(self, symbol: str) -> Position | None:
return next((position for position in self.positions if position.symbol == symbol), None)
return next(
(position for position in self.positions if position.asset.symbol == symbol), None
)

def get_position_amount(self, symbol: str) -> NonNegativeFloat:
position = self.get_position(symbol)
Expand Down
12 changes: 7 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest

from pyrb.models.order import Order
from pyrb.models.position import Position
from pyrb.models.position import Asset, Position
from pyrb.models.price import CurrentPrice
from pyrb.repositories.account import AccountRepository, LocalConfigAccountRepository
from pyrb.repositories.brokerages.base.fetcher import PriceFetcher
Expand All @@ -30,15 +30,15 @@ def cash_balance(self) -> float:
def positions(self) -> list[Position]:
return [
Position(
symbol="000660",
asset=Asset(symbol="000660", label="SKํ•˜์ด๋‹‰์Šค"),
quantity=100,
sellable_quantity=100,
average_buy_price=100,
total_amount=10000,
rtn=0.0,
),
Position(
symbol="005930",
asset=Asset(symbol="005930", label="์‚ผ์„ฑ์ „์ž"),
quantity=50,
sellable_quantity=50,
average_buy_price=150,
Expand All @@ -49,10 +49,12 @@ def positions(self) -> list[Position]:

@property
def holding_symbols(self) -> list[str]:
return [position.symbol for position in self.positions]
return [position.asset.symbol for position in self.positions]

def get_position(self, symbol: str) -> Position | None:
return next((position for position in self.positions if position.symbol == symbol), None)
return next(
(position for position in self.positions if position.asset.symbol == symbol), None
)

def get_position_amount(self, symbol: str) -> float:
position = self.get_position(symbol)
Expand Down
4 changes: 2 additions & 2 deletions tests/controllers/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ def test_get_portfolio(fake_rebalance_context: RebalanceContext) -> None:
"cash_balance": 0,
"positions": [
{
"symbol": "000660",
"asset": {"symbol": "000660", "label": "SKํ•˜์ด๋‹‰์Šค", "asset_class": "STOCK"},
"quantity": 100,
"sellable_quantity": 100,
"average_buy_price": 100,
"total_amount": 10000,
"rtn": 0.0,
},
{
"symbol": "005930",
"asset": {"symbol": "005930", "label": "์‚ผ์„ฑ์ „์ž", "asset_class": "STOCK"},
"quantity": 50,
"sellable_quantity": 50,
"average_buy_price": 150,
Expand Down

0 comments on commit ed382da

Please sign in to comment.