quantification 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
- quantification/__init__.py +3 -2
- quantification/api/__init__.py +3 -0
- quantification/api/akshare/__init__.py +1 -0
- quantification/api/akshare/akshare.py +17 -0
- quantification/api/akshare/delegate/__init__.py +6 -0
- quantification/api/akshare/delegate/macro_china_fdi.py +46 -0
- quantification/api/akshare/delegate/macro_china_lpr.py +43 -0
- quantification/api/akshare/delegate/macro_china_qyspjg.py +51 -0
- quantification/api/akshare/delegate/macro_china_shrzgm.py +47 -0
- quantification/api/akshare/delegate/macro_cnbs.py +47 -0
- quantification/api/akshare/delegate/stock_zh_a_hist.py +77 -0
- quantification/api/akshare/setting.py +5 -0
- quantification/api/api.py +11 -0
- quantification/api/api.pyi +21 -0
- quantification/api/tushare/__init__.py +1 -0
- quantification/api/tushare/delegate/__init__.py +7 -0
- quantification/api/tushare/delegate/balancesheet.py +66 -0
- quantification/api/tushare/delegate/cashflow.py +29 -0
- quantification/api/tushare/delegate/common.py +64 -0
- quantification/api/tushare/delegate/daily_basic.py +81 -0
- quantification/api/tushare/delegate/fina_indicator.py +20 -0
- quantification/api/tushare/delegate/income.py +34 -0
- quantification/api/tushare/delegate/index_daily.py +61 -0
- quantification/api/tushare/delegate/pro_bar.py +80 -0
- quantification/api/tushare/setting.py +5 -0
- quantification/api/tushare/tushare.py +17 -0
- quantification/core/__init__.py +9 -0
- quantification/core/asset/__init__.py +6 -0
- quantification/core/asset/base_asset.py +96 -0
- quantification/core/asset/base_broker.py +42 -0
- quantification/core/asset/broker.py +108 -0
- quantification/core/asset/cash.py +75 -0
- quantification/core/asset/stock.py +268 -0
- quantification/core/cache.py +93 -0
- quantification/core/configure.py +15 -0
- quantification/core/data/__init__.py +5 -0
- quantification/core/data/base_api.py +109 -0
- quantification/core/data/base_delegate.py +73 -0
- quantification/core/data/field.py +213 -0
- quantification/core/data/panel.py +42 -0
- quantification/core/env.py +25 -0
- quantification/core/logger.py +94 -0
- quantification/core/strategy/__init__.py +3 -0
- quantification/core/strategy/base_strategy.py +66 -0
- quantification/core/strategy/base_trigger.py +69 -0
- quantification/core/strategy/base_use.py +69 -0
- quantification/core/trader/__init__.py +7 -0
- quantification/core/trader/base_order.py +45 -0
- quantification/core/trader/base_stage.py +16 -0
- quantification/core/trader/base_trader.py +173 -0
- quantification/core/trader/collector.py +47 -0
- quantification/core/trader/order.py +23 -0
- quantification/core/trader/portfolio.py +72 -0
- quantification/core/trader/query.py +29 -0
- quantification/core/trader/report.py +76 -0
- quantification/core/util.py +181 -0
- quantification/default/__init__.py +5 -0
- quantification/default/stage/__init__.py +1 -0
- quantification/default/stage/cn_stock.py +23 -0
- quantification/default/strategy/__init__.py +1 -0
- quantification/default/strategy/simple/__init__.py +1 -0
- quantification/default/strategy/simple/strategy.py +8 -0
- quantification/default/trader/__init__.py +2 -0
- quantification/default/trader/a_factor/__init__.py +1 -0
- quantification/default/trader/a_factor/trader.py +27 -0
- quantification/default/trader/simple/__init__.py +1 -0
- quantification/default/trader/simple/trader.py +8 -0
- quantification/default/trigger/__init__.py +1 -0
- quantification/default/trigger/trigger.py +63 -0
- quantification/default/use/__init__.py +1 -0
- quantification/default/use/factors/__init__.py +2 -0
- quantification/default/use/factors/factor.py +205 -0
- quantification/default/use/factors/use.py +38 -0
- quantification-0.1.1.dist-info/METADATA +19 -0
- quantification-0.1.1.dist-info/RECORD +76 -0
- {quantification-0.1.0.dist-info → quantification-0.1.1.dist-info}/WHEEL +1 -1
- quantification-0.1.0.dist-info/METADATA +0 -13
- quantification-0.1.0.dist-info/RECORD +0 -4
@@ -0,0 +1,34 @@
|
|
1
|
+
import tushare as ts
|
2
|
+
|
3
|
+
from quantification.core import (
|
4
|
+
Field,
|
5
|
+
Config,
|
6
|
+
cache_query
|
7
|
+
)
|
8
|
+
|
9
|
+
from .common import TushareSheetDelegate
|
10
|
+
from ..setting import TuShareSetting
|
11
|
+
|
12
|
+
|
13
|
+
class IncomeDelegate(TushareSheetDelegate):
|
14
|
+
pair = [
|
15
|
+
(Field.IS_营业总收入, "total_revenue"),
|
16
|
+
(Field.IS_营业总成本, "total_cogs"),
|
17
|
+
(Field.IS_营业利润, "operate_profit"),
|
18
|
+
(Field.IS_利润总额, "total_profit"),
|
19
|
+
(Field.IS_息税前利润, "ebit"),
|
20
|
+
(Field.IS_息税折旧摊销前利润, "ebitda"),
|
21
|
+
(Field.IS_所得税费用, "income_tax"),
|
22
|
+
(Field.IS_净利润, "n_income"),
|
23
|
+
(Field.IS_归母净利润, "n_income_attr_p"),
|
24
|
+
(Field.IS_综合收益, "t_compr_income"),
|
25
|
+
(Field.IS_归母综合收益, "compr_inc_attr_p"),
|
26
|
+
(Field.IS_销售费用, "sell_exp"),
|
27
|
+
(Field.IS_管理费用, "admin_exp"),
|
28
|
+
(Field.IS_财务费用, "fin_exp"),
|
29
|
+
(Field.IS_研发费用, "rd_exp"),
|
30
|
+
]
|
31
|
+
|
32
|
+
def __init__(self, config: Config, setting: TuShareSetting):
|
33
|
+
super().__init__(config, setting)
|
34
|
+
self.api = cache_query()(ts.pro_api(setting.tushare_token).income)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
from datetime import date, datetime, time
|
2
|
+
|
3
|
+
import pandas as pd
|
4
|
+
import tushare as ts
|
5
|
+
|
6
|
+
from quantification.core import (
|
7
|
+
Field,
|
8
|
+
Config,
|
9
|
+
cache_query,
|
10
|
+
BaseDelegate,
|
11
|
+
)
|
12
|
+
|
13
|
+
from ..setting import TuShareSetting
|
14
|
+
|
15
|
+
|
16
|
+
class IndexDailyDelegate(BaseDelegate[TuShareSetting]):
|
17
|
+
pair = [
|
18
|
+
(Field.IN_开盘点位, "open"),
|
19
|
+
(Field.IN_收盘点位, "close"),
|
20
|
+
(Field.IN_最高点位, "high"),
|
21
|
+
(Field.IN_最低点位, "low")
|
22
|
+
]
|
23
|
+
|
24
|
+
def has_field(self, field: Field, **kwargs):
|
25
|
+
if self.field2str.get(field) is None:
|
26
|
+
return False
|
27
|
+
|
28
|
+
index = kwargs.get("index")
|
29
|
+
assert index is not None, "请传入index='xxx'"
|
30
|
+
assert type(index) == str, f"index必须为字符串, 实际为{type(index)}"
|
31
|
+
|
32
|
+
return True
|
33
|
+
|
34
|
+
def query(self, start_date: date, end_date: date, fields: list[Field], **kwargs) -> pd.DataFrame:
|
35
|
+
data = self.api(
|
36
|
+
ts_code=kwargs.get("index"),
|
37
|
+
start_date=start_date.strftime("%Y%m%d"),
|
38
|
+
end_date=end_date.strftime("%Y%m%d"),
|
39
|
+
)
|
40
|
+
|
41
|
+
data = self.rename_columns(data, "trade_date")
|
42
|
+
data = self.use_date_index(data)
|
43
|
+
|
44
|
+
return data
|
45
|
+
|
46
|
+
def mask(self, data: pd.DataFrame, start_date: date, end_date: date, fields: list[Field], **kwargs) -> pd.DataFrame:
|
47
|
+
mask = pd.DataFrame(index=data.index, columns=data.columns)
|
48
|
+
index = pd.Series(mask.index)
|
49
|
+
|
50
|
+
for field in fields:
|
51
|
+
match field:
|
52
|
+
case Field.IN_开盘点位:
|
53
|
+
mask[field] = list(map(lambda x: datetime.combine(x, time(9, 30, 0)), index))
|
54
|
+
case Field.IN_收盘点位 | Field.IN_最高点位 | Field.IN_最低点位:
|
55
|
+
mask[field] = list(map(lambda x: datetime.combine(x, time(17, 0, 0)), index))
|
56
|
+
|
57
|
+
return mask
|
58
|
+
|
59
|
+
def __init__(self, config: Config, setting: TuShareSetting):
|
60
|
+
super().__init__(config, setting)
|
61
|
+
self.api = cache_query()(ts.pro_api(setting.tushare_token).index_daily)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
from datetime import date, datetime, time
|
2
|
+
|
3
|
+
import pandas as pd
|
4
|
+
import tushare as ts
|
5
|
+
|
6
|
+
from quantification.core import (
|
7
|
+
Field,
|
8
|
+
Stock,
|
9
|
+
cache_query,
|
10
|
+
BaseDelegate,
|
11
|
+
StockExchange as E
|
12
|
+
)
|
13
|
+
|
14
|
+
from ..setting import TuShareSetting
|
15
|
+
|
16
|
+
api = cache_query()(ts.pro_bar)
|
17
|
+
|
18
|
+
|
19
|
+
class ProBarDelegate(BaseDelegate[TuShareSetting]):
|
20
|
+
pair = [
|
21
|
+
(Field.ST_昨收价, "pre_close"),
|
22
|
+
(Field.ST_开盘价, "open"),
|
23
|
+
(Field.ST_最高价, "high"),
|
24
|
+
(Field.ST_最低价, "low"),
|
25
|
+
(Field.ST_收盘价, "close"),
|
26
|
+
(Field.ST_涨跌额, "change"),
|
27
|
+
(Field.ST_涨跌幅, "pct_chg"),
|
28
|
+
(Field.ST_成交量, "vol"),
|
29
|
+
(Field.ST_成交额, "amount")
|
30
|
+
]
|
31
|
+
|
32
|
+
def has_field(self, field: Field, **kwargs):
|
33
|
+
if self.field2str.get(field) is None:
|
34
|
+
return False
|
35
|
+
|
36
|
+
stock = kwargs.get("stock")
|
37
|
+
assert stock is not None, "请传入stock参数, 如stock=Stock['000001']"
|
38
|
+
assert issubclass(stock, Stock), f"stock参数必须为Stock子类, 实际为{type(stock)}"
|
39
|
+
|
40
|
+
if stock.exchange not in [E.SZSE, E.BSE, E.SSE]:
|
41
|
+
return False
|
42
|
+
|
43
|
+
return True
|
44
|
+
|
45
|
+
def query(self, start_date: date, end_date: date, fields: list[Field], **kwargs) -> pd.DataFrame:
|
46
|
+
stock: Stock = kwargs.get("stock")
|
47
|
+
exchange = {
|
48
|
+
E.SSE: "SH",
|
49
|
+
E.SZSE: "SZ",
|
50
|
+
E.BSE: "BJ"
|
51
|
+
}[stock.exchange]
|
52
|
+
|
53
|
+
data = api(
|
54
|
+
ts_code=f"{stock.symbol}.{exchange}",
|
55
|
+
start_date=start_date.strftime("%Y%m%d"),
|
56
|
+
end_date=end_date.strftime("%Y%m%d"),
|
57
|
+
adj=self.config.adjust
|
58
|
+
)
|
59
|
+
|
60
|
+
data = self.rename_columns(data, "trade_date")
|
61
|
+
data = self.use_date_index(data)
|
62
|
+
|
63
|
+
data[Field.ST_成交额] = data[Field.ST_成交额].apply(lambda x: x / 10)
|
64
|
+
|
65
|
+
return data
|
66
|
+
|
67
|
+
def mask(self, data: pd.DataFrame, start_date: date, end_date: date, fields: list[Field], **kwargs) -> pd.DataFrame:
|
68
|
+
mask = pd.DataFrame(index=data.index, columns=data.columns)
|
69
|
+
index = pd.Series(mask.index)
|
70
|
+
|
71
|
+
for field in fields:
|
72
|
+
match field:
|
73
|
+
case Field.ST_昨收价:
|
74
|
+
mask[field] = list(map(lambda x: datetime.combine(x, time(0, 0, 0)), index))
|
75
|
+
case Field.ST_开盘价:
|
76
|
+
mask[field] = list(map(lambda x: datetime.combine(x, time(9, 30, 0)), index))
|
77
|
+
case Field.ST_最高价 | Field.ST_最低价 | Field.ST_收盘价 | Field.ST_涨跌额 | Field.ST_涨跌幅 | Field.ST_成交量 | Field.ST_成交额:
|
78
|
+
mask[field] = list(map(lambda x: datetime.combine(x, time(17, 0, 0)), index))
|
79
|
+
|
80
|
+
return mask
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from quantification.core.data import BaseAPI
|
2
|
+
|
3
|
+
from .setting import TuShareSetting
|
4
|
+
from .delegate import *
|
5
|
+
|
6
|
+
|
7
|
+
class TuShareAPI(BaseAPI[TuShareSetting]):
|
8
|
+
setting_class = TuShareSetting
|
9
|
+
delegate_classes = [
|
10
|
+
ProBarDelegate,
|
11
|
+
IncomeDelegate,
|
12
|
+
CashflowDelegate,
|
13
|
+
IndexDailyDelegate,
|
14
|
+
DailyBasicDelegate,
|
15
|
+
BalanceSheetDelegate,
|
16
|
+
FinanceIndicatorDelegate,
|
17
|
+
]
|
@@ -0,0 +1,96 @@
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
2
|
+
|
3
|
+
from ..env import EnvGetter
|
4
|
+
|
5
|
+
|
6
|
+
class BaseAssetMeta(ABCMeta):
|
7
|
+
def __repr__(self: "BaseAsset"):
|
8
|
+
return self.name()
|
9
|
+
|
10
|
+
__str__ = __repr__
|
11
|
+
|
12
|
+
|
13
|
+
class BaseAsset(EnvGetter, metaclass=BaseAssetMeta):
|
14
|
+
is_closeable = True # 若为True, 当empty时将从Portfolio中移除该资产
|
15
|
+
|
16
|
+
@classmethod
|
17
|
+
@abstractmethod
|
18
|
+
def type(cls):
|
19
|
+
raise NotImplementedError
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
@abstractmethod
|
23
|
+
def name(cls) -> str:
|
24
|
+
raise NotImplementedError
|
25
|
+
|
26
|
+
@property
|
27
|
+
@abstractmethod
|
28
|
+
def amount(self, *args, **kwargs) -> int:
|
29
|
+
"""
|
30
|
+
资产数额
|
31
|
+
"""
|
32
|
+
raise NotImplementedError
|
33
|
+
|
34
|
+
@property
|
35
|
+
@abstractmethod
|
36
|
+
def extra(self, *args, **kwargs) -> dict:
|
37
|
+
"""
|
38
|
+
资产额外信息
|
39
|
+
"""
|
40
|
+
raise NotImplementedError
|
41
|
+
|
42
|
+
@property
|
43
|
+
@abstractmethod
|
44
|
+
def is_empty(self) -> bool:
|
45
|
+
"""
|
46
|
+
若为True, 则会在Portfolio中移除该资产
|
47
|
+
"""
|
48
|
+
raise NotImplementedError
|
49
|
+
|
50
|
+
@abstractmethod
|
51
|
+
def available(self, *args, **kwargs):
|
52
|
+
"""
|
53
|
+
获取该资产可用于出售的部分
|
54
|
+
"""
|
55
|
+
raise NotImplementedError
|
56
|
+
|
57
|
+
@property
|
58
|
+
@abstractmethod
|
59
|
+
def copy(self, *args, **kwargs):
|
60
|
+
"""
|
61
|
+
复制资产对象
|
62
|
+
"""
|
63
|
+
raise NotImplementedError
|
64
|
+
|
65
|
+
@abstractmethod
|
66
|
+
def __add__(self, other: "BaseAsset"):
|
67
|
+
"""
|
68
|
+
购买同一种资产
|
69
|
+
:param other: 同一种资产
|
70
|
+
"""
|
71
|
+
raise NotImplementedError
|
72
|
+
|
73
|
+
@abstractmethod
|
74
|
+
def __sub__(self, other: "BaseAsset"):
|
75
|
+
"""
|
76
|
+
出售该资产的一部分
|
77
|
+
:param other: 同一种资产
|
78
|
+
"""
|
79
|
+
raise NotImplementedError
|
80
|
+
|
81
|
+
@abstractmethod
|
82
|
+
def __eq__(self, other: "BaseAsset"):
|
83
|
+
"""
|
84
|
+
判断是否为同一种资产
|
85
|
+
:param other: 任意资产
|
86
|
+
"""
|
87
|
+
raise NotImplementedError
|
88
|
+
|
89
|
+
@abstractmethod
|
90
|
+
def __repr__(self):
|
91
|
+
raise NotImplementedError
|
92
|
+
|
93
|
+
__str__ = __repr__
|
94
|
+
|
95
|
+
|
96
|
+
__all__ = ["BaseAsset"]
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import TypeVar, Generic, TYPE_CHECKING
|
3
|
+
from datetime import date
|
4
|
+
|
5
|
+
from ..env import EnvGetter
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from .base_asset import BaseAsset
|
9
|
+
from ..data import BaseAPI
|
10
|
+
from ..trader import BaseOrder, BaseStage, Result
|
11
|
+
|
12
|
+
AssetType = TypeVar('AssetType', bound="BaseAsset")
|
13
|
+
|
14
|
+
|
15
|
+
class BaseBroker(Generic[AssetType], ABC, EnvGetter):
|
16
|
+
def __init__(self, api: "BaseAPI", start_date: date, end_date: date, stage: type["BaseStage"]):
|
17
|
+
super().__init__()
|
18
|
+
|
19
|
+
self.api = api
|
20
|
+
self.start_date = start_date
|
21
|
+
self.end_date = end_date
|
22
|
+
self.stage = stage
|
23
|
+
|
24
|
+
@abstractmethod
|
25
|
+
def matchable(self, asset: "BaseAsset") -> bool:
|
26
|
+
raise NotImplementedError
|
27
|
+
|
28
|
+
@abstractmethod
|
29
|
+
def execute_order(self, order: "BaseOrder[AssetType]") -> "Result":
|
30
|
+
raise NotImplementedError
|
31
|
+
|
32
|
+
@abstractmethod
|
33
|
+
def liquidate_asset(self, asset: "BaseAsset", day: date, stage: "BaseStage") -> int:
|
34
|
+
raise NotImplementedError
|
35
|
+
|
36
|
+
def __repr__(self):
|
37
|
+
return f"<{self.__class__.__name__}>"
|
38
|
+
|
39
|
+
__str__ = __repr__
|
40
|
+
|
41
|
+
|
42
|
+
__all__ = ['BaseBroker']
|
@@ -0,0 +1,108 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
from datetime import timedelta, date, time
|
3
|
+
|
4
|
+
from pandas import to_datetime
|
5
|
+
|
6
|
+
from .cash import RMB
|
7
|
+
from .stock import Stock
|
8
|
+
from .stock import StockExchange as E
|
9
|
+
from .base_broker import BaseBroker
|
10
|
+
|
11
|
+
from ..logger import logger
|
12
|
+
from ..data.field import Field
|
13
|
+
from ..data.base_api import BaseAPI
|
14
|
+
from ..trader.order import StockOrder
|
15
|
+
from ..trader.base_order import Result
|
16
|
+
from ..trader.base_stage import BaseStage
|
17
|
+
|
18
|
+
if TYPE_CHECKING:
|
19
|
+
from .base_asset import BaseAsset
|
20
|
+
|
21
|
+
|
22
|
+
class StockBrokerCN(BaseBroker[Stock]):
|
23
|
+
def matchable(self, asset: "BaseAsset") -> bool:
|
24
|
+
if not isinstance(asset, Stock):
|
25
|
+
logger.trace(f"{asset}不是股票, {self}无法处理, 交给下一个Broker")
|
26
|
+
return False
|
27
|
+
|
28
|
+
if not asset.exchange in [E.SSE, E.SZSE, E.BSE]:
|
29
|
+
logger.trace(f"{asset}不属于上交所|深交所|北交所, {self}无法处理, 交给下一个Broker")
|
30
|
+
return False
|
31
|
+
|
32
|
+
return True
|
33
|
+
|
34
|
+
def execute_order(self, order: StockOrder) -> Result:
|
35
|
+
assert self.env is not None, "无法获取env"
|
36
|
+
|
37
|
+
match self.env.time:
|
38
|
+
case time(hour=9, minute=30, second=0):
|
39
|
+
field = Field.ST_开盘价
|
40
|
+
case time(hour=15, minute=0, second=0):
|
41
|
+
field = Field.ST_收盘价
|
42
|
+
case _:
|
43
|
+
raise ValueError("StockBrokerCN只支持在9:30和15:00撮合交易")
|
44
|
+
|
45
|
+
df = self.api.query(
|
46
|
+
self.start_date,
|
47
|
+
self.end_date,
|
48
|
+
[Field.ST_开盘价, Field.ST_收盘价],
|
49
|
+
stock=Stock[order.asset.symbol]
|
50
|
+
)
|
51
|
+
|
52
|
+
price = df.at[to_datetime(self.env.date), field]
|
53
|
+
|
54
|
+
logger.trace(f"{self}撮合交易{order}: 日期{self.env.date} 时间{self.env.time} 价格{price}")
|
55
|
+
|
56
|
+
match order.category:
|
57
|
+
case "buy":
|
58
|
+
return Result(
|
59
|
+
order=order,
|
60
|
+
sold=[RMB(price * order.asset.amount)],
|
61
|
+
brought=[order.asset],
|
62
|
+
)
|
63
|
+
case "sell":
|
64
|
+
return Result(
|
65
|
+
order=order,
|
66
|
+
sold=[order.asset],
|
67
|
+
brought=[RMB(price * order.asset.amount)],
|
68
|
+
)
|
69
|
+
case _:
|
70
|
+
raise ValueError(f"invalid order category: {order.category}")
|
71
|
+
|
72
|
+
def liquidate_asset(self, asset: Stock, day: date, stage: BaseStage) -> RMB:
|
73
|
+
df = self.api.query(
|
74
|
+
self.start_date,
|
75
|
+
self.end_date,
|
76
|
+
[Field.ST_开盘价, Field.ST_收盘价],
|
77
|
+
stock=Stock[asset.symbol]
|
78
|
+
)
|
79
|
+
|
80
|
+
liquidating_value = 0
|
81
|
+
|
82
|
+
if not to_datetime(day) in df.index:
|
83
|
+
for brought_day, brought_share in asset.share_position.items():
|
84
|
+
nearest_day = df.index[df.index.searchsorted(to_datetime(day), side='left')]
|
85
|
+
liquidating_value += df.at[nearest_day, Field.ST_开盘价] * brought_share
|
86
|
+
else:
|
87
|
+
for brought_day, brought_share in asset.share_position.items():
|
88
|
+
if brought_day < day and stage.time <= time(9, 30):
|
89
|
+
liquidating_value += df.at[to_datetime(day), Field.ST_开盘价] * brought_share
|
90
|
+
|
91
|
+
elif brought_day < day and stage.time <= time(15, 0):
|
92
|
+
liquidating_value += df.at[to_datetime(day), Field.ST_收盘价] * brought_share
|
93
|
+
|
94
|
+
elif (brought_day < day and stage.time >= time(15, 0)) or brought_day == day:
|
95
|
+
pos = df.index.get_loc(to_datetime(day)) # 获取位置
|
96
|
+
next_day = df.index[pos + 1] # 下一个位置的日期
|
97
|
+
liquidating_value += df.at[next_day, Field.ST_开盘价] * brought_share
|
98
|
+
|
99
|
+
else:
|
100
|
+
raise ValueError(f"invalid brought day: {brought_day}")
|
101
|
+
|
102
|
+
return liquidating_value
|
103
|
+
|
104
|
+
def __init__(self, api: "BaseAPI", start_date: date, end_date: date, stage: type[BaseStage]):
|
105
|
+
super().__init__(api, start_date - timedelta(days=10), end_date + timedelta(days=10), stage)
|
106
|
+
|
107
|
+
|
108
|
+
__all__ = ["StockBrokerCN"]
|
@@ -0,0 +1,75 @@
|
|
1
|
+
from .base_asset import BaseAsset
|
2
|
+
|
3
|
+
cash_family: dict[str:type["Cash"]] = {}
|
4
|
+
|
5
|
+
|
6
|
+
class Cash(BaseAsset):
|
7
|
+
symbol: str = None
|
8
|
+
is_closeable = False
|
9
|
+
|
10
|
+
@classmethod
|
11
|
+
def type(cls, *args, **kwargs):
|
12
|
+
return "Cash"
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def name(cls, *args, **kwargs):
|
16
|
+
return f"现金{cls.symbol}" if cls.symbol else "全部现金"
|
17
|
+
|
18
|
+
@property
|
19
|
+
def amount(self, *args, **kwargs):
|
20
|
+
return self.value
|
21
|
+
|
22
|
+
@property
|
23
|
+
def extra(self, *args, **kwargs):
|
24
|
+
return {}
|
25
|
+
|
26
|
+
@property
|
27
|
+
def is_empty(self):
|
28
|
+
return self.value == 0
|
29
|
+
|
30
|
+
@property
|
31
|
+
def copy(self):
|
32
|
+
return Cash[self.symbol](self.value)
|
33
|
+
|
34
|
+
def available(self, *args, **kwargs):
|
35
|
+
return Cash[self.symbol](self.value)
|
36
|
+
|
37
|
+
def __add__(self, other: "Cash"):
|
38
|
+
assert self == other, f"只能和Cash相加, 实际类型{type(other)}"
|
39
|
+
|
40
|
+
return Cash[self.symbol](self.value + other.value)
|
41
|
+
|
42
|
+
def __sub__(self, other: "Cash"):
|
43
|
+
assert self == other, f"只能和Cash相减, 实际类型{type(other)}"
|
44
|
+
|
45
|
+
return Cash[self.symbol](self.value - other.value)
|
46
|
+
|
47
|
+
def __eq__(self, other: "Cash"):
|
48
|
+
return isinstance(other, Cash) and self.symbol == other.symbol
|
49
|
+
|
50
|
+
def __repr__(self):
|
51
|
+
return f"{self.name()}({self.value}元)"
|
52
|
+
|
53
|
+
__str__ = __repr__
|
54
|
+
|
55
|
+
def __init__(self, value: float = 0):
|
56
|
+
assert self.symbol is not None, "未指定现金类型, 无法实例化"
|
57
|
+
assert value >= 0, f"现金金额不能为负, 实际为{value}"
|
58
|
+
self.value = value
|
59
|
+
|
60
|
+
def __class_getitem__(cls, symbol: str):
|
61
|
+
if not cash_family.get(symbol):
|
62
|
+
cash_family[symbol] = type(
|
63
|
+
f"Cash{symbol}",
|
64
|
+
(Cash,),
|
65
|
+
{"symbol": symbol},
|
66
|
+
)
|
67
|
+
|
68
|
+
return cash_family[symbol]
|
69
|
+
|
70
|
+
|
71
|
+
RMB = Cash["RMB"]
|
72
|
+
HKD = Cash["HKD"]
|
73
|
+
USD = Cash["USD"]
|
74
|
+
|
75
|
+
__all__ = ["Cash", "RMB", "HKD", "USD"]
|