pfund-blocks 0.0.1__tar.gz

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.
Files changed (29) hide show
  1. pfund_blocks-0.0.1/PKG-INFO +14 -0
  2. pfund_blocks-0.0.1/README.md +4 -0
  3. pfund_blocks-0.0.1/pyproject.toml +26 -0
  4. pfund_blocks-0.0.1/pyproject.toml.orig +24 -0
  5. pfund_blocks-0.0.1/src/pfund_blocks/__init__.py +0 -0
  6. pfund_blocks-0.0.1/src/pfund_blocks/entities/markets/__init__.py +4 -0
  7. pfund_blocks-0.0.1/src/pfund_blocks/entities/markets/market_base.py +40 -0
  8. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/__init__.py +13 -0
  9. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/asset_type.py +137 -0
  10. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/crypto.py +12 -0
  11. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/derivative.py +25 -0
  12. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/etf.py +11 -0
  13. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/forex.py +11 -0
  14. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/future.py +35 -0
  15. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/index.py +11 -0
  16. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/inverse.py +11 -0
  17. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/option.py +43 -0
  18. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/perpetual.py +26 -0
  19. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/mixins/stock.py +11 -0
  20. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/product_base.py +222 -0
  21. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/product_basis.py +67 -0
  22. pfund_blocks-0.0.1/src/pfund_blocks/entities/products/product_factory.py +38 -0
  23. pfund_blocks-0.0.1/src/pfund_blocks/enums/asset_type.py +86 -0
  24. pfund_blocks-0.0.1/src/pfund_blocks/enums/env.py +11 -0
  25. pfund_blocks-0.0.1/src/pfund_blocks/enums/month_code.py +25 -0
  26. pfund_blocks-0.0.1/src/pfund_blocks/enums/option_type.py +6 -0
  27. pfund_blocks-0.0.1/src/pfund_blocks/enums/timeframe.py +102 -0
  28. pfund_blocks-0.0.1/src/pfund_blocks/resolution.py +287 -0
  29. pfund_blocks-0.0.1/src/pfund_blocks/utils/__init__.py +13 -0
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.3
2
+ Name: pfund-blocks
3
+ Version: 0.0.1
4
+ Summary: Building blocks for `pfund`
5
+ Author: softwareentrepreneer
6
+ Author-email: softwareentrepreneer <softwareentrepreneer+pfund-blocks@gmail.com>
7
+ Requires-Dist: pydantic>=2.14.0
8
+ Requires-Python: >=3.13
9
+ Description-Content-Type: text/markdown
10
+
11
+ [pfund]: https://github.com/PFund-Software-Ltd/pfund
12
+
13
+ # pfund-blocks
14
+ Building blocks for [pfund]
@@ -0,0 +1,4 @@
1
+ [pfund]: https://github.com/PFund-Software-Ltd/pfund
2
+
3
+ # pfund-blocks
4
+ Building blocks for [pfund]
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "pfund-blocks"
3
+ version = "0.0.1"
4
+ description = "Building blocks for `pfund`"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = ["pydantic>=2.14.0"]
8
+
9
+ [[project.authors]]
10
+ name = "softwareentrepreneer"
11
+ email = "softwareentrepreneer+pfund-blocks@gmail.com"
12
+
13
+ [build-system]
14
+ requires = ["uv_build>=0.13.5,<0.14.0"]
15
+ build-backend = "uv_build"
16
+
17
+ [tool.uv]
18
+ exclude-newer = "14 days"
19
+
20
+ [tool.uv.exclude-newer-package]
21
+ uv_build = false
22
+ pfund-kit = false
23
+
24
+ [tool.uv.build-backend]
25
+ module-name = "pfund_blocks"
26
+ module-root = "src"
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "pfund-blocks"
3
+ version = "0.0.1"
4
+ description = "Building blocks for `pfund`"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "softwareentrepreneer", email = "softwareentrepreneer+pfund-blocks@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.13"
10
+ dependencies = [
11
+ "pydantic>=2.14.0",
12
+ ]
13
+
14
+ [build-system]
15
+ requires = ["uv_build>=0.13.5,<0.14.0"]
16
+ build-backend = "uv_build"
17
+
18
+ [tool.uv]
19
+ exclude-newer = "14 days"
20
+ exclude-newer-package = { uv_build = false, pfund-kit = false }
21
+
22
+ [tool.uv.build-backend]
23
+ module-name = "pfund_blocks"
24
+ module-root = "src"
File without changes
@@ -0,0 +1,4 @@
1
+ from pfund_blocks.entities.markets.market_base import BaseMarket
2
+
3
+
4
+ __all__ = ["BaseMarket"]
@@ -0,0 +1,40 @@
1
+ from decimal import Decimal
2
+ from datetime import datetime, timedelta
3
+
4
+ from pydantic import BaseModel, Field, ConfigDict
5
+
6
+
7
+ class BaseMarket(BaseModel):
8
+ model_config = ConfigDict(extra="allow")
9
+
10
+ symbol: str
11
+ base_asset: str
12
+ quote_asset: str
13
+ asset_type: str
14
+ tick_size: Decimal | None = Field(
15
+ default=None, description="Tick size, minimum price increment"
16
+ )
17
+ lot_size: Decimal | None = Field(
18
+ default=None, description="Lot size, minimum quantity incrementc"
19
+ )
20
+ min_leverage: Decimal | None = Field(default=None, description="Minimum leverage")
21
+ max_leverage: Decimal | None = Field(default=None, description="Maximum leverage")
22
+ leverage_step: Decimal | None = Field(
23
+ default=None, description="Leverage step size"
24
+ )
25
+ min_price: Decimal | None = Field(default=None, description="Minimum price")
26
+ max_price: Decimal | None = Field(default=None, description="Maximum price")
27
+ min_quantity: Decimal | None = Field(
28
+ default=None, description="Minimum order quantity"
29
+ )
30
+ max_quantity: Decimal | None = Field(
31
+ default=None, description="Maximum order quantity"
32
+ )
33
+ listed_at: datetime | None = Field(
34
+ default=None, description="Date the market was listed"
35
+ )
36
+ expiration: datetime | None = Field(default=None, description="expiration date")
37
+ settle_currency: str | None = Field(default=None, description="Settlement currency")
38
+ funding_interval: timedelta | None = Field(
39
+ default=None, description="Funding interval"
40
+ )
@@ -0,0 +1,13 @@
1
+ from pfund_blocks.entities.products.product_factory import ProductFactory
2
+ from pfund_blocks.entities.products.product_base import BaseProduct, ProductKey
3
+ from pfund_blocks.entities.products.asset_type import AssetType
4
+ from pfund_blocks.entities.products.product_basis import ProductBasis
5
+
6
+
7
+ __all__ = [
8
+ "ProductFactory",
9
+ "BaseProduct",
10
+ "ProductKey",
11
+ "AssetType",
12
+ "ProductBasis",
13
+ ]
@@ -0,0 +1,137 @@
1
+ from typing import Any
2
+
3
+ from functools import cached_property
4
+
5
+ from pydantic import BaseModel, ConfigDict, field_validator, model_validator
6
+
7
+ from pfund_blocks.enums.asset_type import (
8
+ ASSET_TYPE_ALIASES,
9
+ AllAssetType,
10
+ AssetTypeModifier,
11
+ CryptoAssetType,
12
+ TraditionalAssetType,
13
+ PredictionMarketAssetType,
14
+ )
15
+
16
+
17
+ class AssetType(BaseModel):
18
+ model_config = ConfigDict(
19
+ arbitrary_types_allowed=True, frozen=True, ignored_types=(cached_property,)
20
+ )
21
+
22
+ value: str # e.g. 'STOCK'/'STK', 'INVERSE-FUTURE'/'IFUT', 'CRYPTO' etc.
23
+
24
+ @model_validator(mode="before")
25
+ @classmethod
26
+ def _coerce_string(cls, data: Any) -> Any:
27
+ # allow a bare string (e.g. "PERP") to be used wherever an
28
+ # AssetType is expected, by treating it as the `value` field
29
+ if isinstance(data, str):
30
+ return {"value": data.upper()}
31
+ return data
32
+
33
+ @staticmethod
34
+ def _standardize_asset_type_string(asset_type_string: str) -> str:
35
+ """
36
+ Expand aliases (e.g. 'IPERP' -> 'INVERSE-PERPETUAL') into their canonical hyphenated form.
37
+ """
38
+ asset_types_and_modifiers = asset_type_string.split("-")
39
+ standardized_asset_types_and_modifiers = [
40
+ ASSET_TYPE_ALIASES.get(atm.upper(), atm)
41
+ for atm in asset_types_and_modifiers
42
+ ]
43
+ return "-".join(standardized_asset_types_and_modifiers)
44
+
45
+ @staticmethod
46
+ def _parse_asset_type_string_to_tuple(
47
+ asset_type_string: str,
48
+ ) -> tuple[AssetTypeModifier | AllAssetType, ...]:
49
+ """
50
+ Convert asset type string (e.g. 'INVERSE-FUTURE') to tuple of AssetTypeModifier and AssetType (e.g. (AssetTypeModifier.INVERSE, AssetType.FUTURE)).
51
+ """
52
+ asset_type_string = AssetType._standardize_asset_type_string(asset_type_string)
53
+ parsed: list[AssetTypeModifier | AllAssetType] = []
54
+ for atm in asset_type_string.split("-"):
55
+ if atm in AssetTypeModifier.__members__:
56
+ parsed.append(AssetTypeModifier[atm])
57
+ elif atm in AllAssetType.__members__:
58
+ parsed.append(AllAssetType[atm])
59
+ # NOTE: some aliases are not in AllAssetType, e.g. 'SPOT' only exists in CryptoAssetType.
60
+ # The sub-enums' values are AllAssetType members, so normalize to AllAssetType.
61
+ elif atm in TraditionalAssetType.__members__:
62
+ parsed.append(AllAssetType(TraditionalAssetType[atm]))
63
+ elif atm in CryptoAssetType.__members__:
64
+ parsed.append(AllAssetType(CryptoAssetType[atm]))
65
+ elif atm in PredictionMarketAssetType.__members__:
66
+ parsed.append(AllAssetType(PredictionMarketAssetType[atm]))
67
+ else:
68
+ raise ValueError(f"Invalid asset type: {atm}")
69
+ return tuple(parsed)
70
+
71
+ @field_validator("value", mode="after")
72
+ @classmethod
73
+ def _normalize(cls, v: str) -> str:
74
+ return "-".join(cls._parse_asset_type_string_to_tuple(v))
75
+
76
+ @cached_property
77
+ def as_tuple(self) -> tuple[AssetTypeModifier | AllAssetType, ...]:
78
+ return self._parse_asset_type_string_to_tuple(self.value)
79
+
80
+ def __eq__(self, other: Any):
81
+ if isinstance(other, str):
82
+ other_tuple = self._parse_asset_type_string_to_tuple(other)
83
+ return self.as_tuple == other_tuple
84
+ elif isinstance(other, AssetType):
85
+ return self.as_tuple == other.as_tuple
86
+ return False
87
+
88
+ def __hash__(self) -> int:
89
+ return hash(self.as_tuple)
90
+
91
+ def __contains__(self, item: Any):
92
+ return item in self.as_tuple
93
+
94
+ def __iter__(self):
95
+ return iter(self.as_tuple)
96
+
97
+ def __len__(self):
98
+ return len(self.as_tuple)
99
+
100
+ def __str__(self):
101
+ return self.value
102
+
103
+ def is_inverse(self) -> bool:
104
+ return AssetTypeModifier.INVERSE in self.as_tuple
105
+
106
+ def is_crypto(self) -> bool:
107
+ return AllAssetType.CRYPTO in self.as_tuple
108
+
109
+ def is_future(self) -> bool:
110
+ return AllAssetType.FUTURE in self.as_tuple
111
+
112
+ def is_perpetual(self) -> bool:
113
+ return AllAssetType.PERPETUAL in self.as_tuple
114
+
115
+ def is_option(self) -> bool:
116
+ return AllAssetType.OPTION in self.as_tuple
117
+
118
+ def is_stock(self) -> bool:
119
+ return AllAssetType.STOCK in self.as_tuple
120
+
121
+ def is_etf(self) -> bool:
122
+ return AllAssetType.ETF in self.as_tuple
123
+
124
+ def is_forex(self) -> bool:
125
+ return AllAssetType.FOREX in self.as_tuple
126
+
127
+ def is_bond(self) -> bool:
128
+ return AllAssetType.BOND in self.as_tuple
129
+
130
+ def is_mutual_fund(self) -> bool:
131
+ return AllAssetType.FUND in self.as_tuple
132
+
133
+ def is_commodity(self) -> bool:
134
+ return AllAssetType.CMDTY in self.as_tuple
135
+
136
+ def is_index(self) -> bool:
137
+ return AllAssetType.INDEX in self.as_tuple
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ pass
7
+
8
+ from pfund_blocks.entities.products.mixins.forex import ForexMixin
9
+
10
+
11
+ class CryptoMixin(ForexMixin):
12
+ pass
@@ -0,0 +1,25 @@
1
+ # pyright: reportAttributeAccessIssue=false
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from pfund_blocks.entities.products.product_base import BaseProduct
8
+
9
+ from decimal import Decimal
10
+
11
+
12
+ class DerivativeMixin:
13
+ underlying: str = ""
14
+ # TODO: information that requires data fetching or config loading
15
+ multiplier: Decimal | None = None
16
+
17
+ # contract_unit: str | None = None # e.g. barrels
18
+ # notional: Decimal | None = None
19
+ # leverage: Decimal | None = None
20
+ # initial_margin: Decimal | None = None
21
+ # maintenance_margin: Decimal | None = None
22
+ # settlement_type: SettlementType
23
+
24
+ def __mixin_post_init__(self: DerivativeMixin | BaseProduct):
25
+ self.underlying = self.base_asset # TODO: inaccurate
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from pfund_blocks.entities.products.product_base import BaseProduct
7
+
8
+
9
+ class ETFMixin:
10
+ def _create_symbol(self: BaseProduct) -> str:
11
+ return self.base_asset
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from pfund_blocks.entities.products.product_base import BaseProduct
7
+
8
+
9
+ class ForexMixin:
10
+ def _create_symbol(self: BaseProduct) -> str:
11
+ return self.base_asset + self.quote_asset
@@ -0,0 +1,35 @@
1
+ # pyright: reportAttributeAccessIssue=false, reportUninitializedInstanceVariable=false
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from pfund_blocks.entities.products.product_base import BaseProduct
8
+
9
+ from datetime import date
10
+
11
+ from pfund_blocks.entities.products.mixins.derivative import DerivativeMixin
12
+
13
+
14
+ class FutureMixin(DerivativeMixin):
15
+ expiration: date
16
+ contract_code: str = ""
17
+
18
+ def __mixin_post_init__(self):
19
+ super().__mixin_post_init__()
20
+ self.contract_code = self._derive_contract_code()
21
+
22
+ def _derive_contract_code(self):
23
+ from pfund_blocks.enums.month_code import FutureMonthCode
24
+
25
+ expiration_year, expiration_month, _ = str(self.expiration).split("-")
26
+ expiration_year = expiration_year[-2:]
27
+ month_code = [code.value for code in FutureMonthCode][int(expiration_month) - 1]
28
+ contract_code = month_code + expiration_year
29
+ return contract_code
30
+
31
+ def _create_symbol(self: FutureMixin | BaseProduct) -> str:
32
+ """
33
+ Creates default symbol e.g. ESZ23
34
+ """
35
+ return self.base_asset + self.contract_code
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from pfund_blocks.entities.products.product_base import BaseProduct
7
+
8
+
9
+ class IndexMixin:
10
+ def _create_symbol(self: BaseProduct) -> str:
11
+ return self.base_asset
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ pass
7
+
8
+
9
+ # TODO
10
+ class InverseMixin:
11
+ pass
@@ -0,0 +1,43 @@
1
+ # pyright: reportAttributeAccessIssue=false, reportUninitializedInstanceVariable=false
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from pfund_blocks.entities.products.product_base import BaseProduct
8
+
9
+ from datetime import date
10
+ from decimal import Decimal
11
+
12
+ from pydantic import Field, field_validator
13
+
14
+ from pfund_blocks.entities.products.mixins.derivative import DerivativeMixin
15
+ from pfund_blocks.enums.option_type import OptionType
16
+ from pfund_blocks.utils import trim_trailing_zeros
17
+
18
+
19
+ # TODO: add Greeks
20
+ class OptionMixin(DerivativeMixin):
21
+ expiration: date
22
+ strike_price: Decimal = Field(ge=0.0)
23
+ option_type: OptionType
24
+
25
+ @field_validator("option_type", mode="before")
26
+ @classmethod
27
+ def _uppercase_option_type(cls, option_type: str) -> str:
28
+ return option_type.upper()
29
+
30
+ @field_validator("strike_price", mode="after")
31
+ @classmethod
32
+ def _remove_redundant_zeros(cls, strike_price: Decimal) -> Decimal:
33
+ return trim_trailing_zeros(strike_price)
34
+
35
+ def _create_symbol(self: OptionMixin | BaseProduct) -> str:
36
+ """
37
+ Creates default symbol e.g. TSLA241213C00075000 following OSI format (Option Symbology Initiative)
38
+ """
39
+ expiration = self.expiration.strftime("%y%m%d") # convert expiration to yymmdd
40
+ num_of_digits = 8
41
+ strike_price = str(int(self.strike_price * 1000)).zfill(num_of_digits)
42
+ symbol = self.base_asset + expiration + self.option_type.value[0] + strike_price
43
+ return symbol
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from typing_extensions import override
6
+
7
+ if TYPE_CHECKING:
8
+ from pfund_blocks.entities.products.product_base import BaseProduct
9
+
10
+ from pfund_blocks.entities.products.mixins.future import FutureMixin
11
+
12
+
13
+ class PerpetualMixin(FutureMixin):
14
+ expiration: None = None
15
+ contract_code: None = None
16
+
17
+ @override # override FutureMixin._derive_contract_code
18
+ def _derive_contract_code(self) -> None:
19
+ return None
20
+
21
+ @override # override FutureMixin._create_symbol
22
+ def _create_symbol(self: FutureMixin | BaseProduct) -> str:
23
+ """
24
+ Creates default symbol e.g. ESZ23
25
+ """
26
+ return self.base_asset + "_" + self.quote_asset
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from pfund_blocks.entities.products.product_base import BaseProduct
7
+
8
+
9
+ class StockMixin:
10
+ def _create_symbol(self: StockMixin | BaseProduct) -> str:
11
+ return self.base_asset