diff --git a/.flexci/config.pbtxt b/.flexci/config.pbtxt index 6360c655..b2b706ca 100644 --- a/.flexci/config.pbtxt +++ b/.flexci/config.pbtxt @@ -4,7 +4,7 @@ configs { requirement { cpu: 2 gpu: 1 - memory: 20 + memory: 30 disk: 10 } time_limit: { @@ -20,7 +20,7 @@ configs { requirement { cpu: 2 gpu: 1 - memory: 20 + memory: 30 disk: 10 } time_limit: { @@ -36,7 +36,7 @@ configs { requirement { cpu: 2 gpu: 1 - memory: 20 + memory: 30 disk: 10 } time_limit: { diff --git a/Makefile b/Makefile index 4ee0861c..fd39583e 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ test-cov: $(RUN) pytest --cov=$(PROJECT_NAME) --cov-report=xml -m "not gpu" .PHONY: lint -lint: lint-pysen +lint: lint-black lint-isort flake8 mypy .PHONY: lint-black lint-black: @@ -40,27 +40,19 @@ mypy: .PHONY: flake8 flake8: - $(RUN) flake8 $(PROJECT_NAME) + $(RUN) pflake8 $(PROJECT_NAME) .PHONY: format -format: format-pysen +format: format-black format-isort .PHONY: format-black format-black: - $(RUN) black --quiet --skip-magic-trailing-comma . + $(RUN) black --quiet . .PHONY: format-isort format-isort: $(RUN) isort --force-single-line-imports --quiet . -.PHONY: format-pysen -format-pysen: - $(RUN) pysen run format - -.PHONY: lint-pysen -lint-pysen: - $(RUN) pysen run lint - .PHONY: doc doc: @cd docs && make html diff --git a/examples/example_hedging_variance_swap.py b/examples/example_hedging_variance_swap.py index 55cc6e8b..0cc2bea3 100644 --- a/examples/example_hedging_variance_swap.py +++ b/examples/example_hedging_variance_swap.py @@ -26,7 +26,7 @@ total_vega = torch.zeros_like(spot) for option, strike in zip(options_list, strikes_list): lm = (spot / strike).log() - vega = BlackScholes(option).vega(lm, t, v) / (strike**2) + vega = BlackScholes(option).vega(lm, t, v) / (strike ** 2) total_vega += vega if option.call: # 2 is for call and put diff --git a/lint.py b/lint.py deleted file mode 100644 index 647b5c90..00000000 --- a/lint.py +++ /dev/null @@ -1,25 +0,0 @@ -import pathlib -from typing import Optional -from typing import Sequence - -import pysen -from pysen import IsortSetting -from pysen.component import ComponentBase -from pysen.manifest import Manifest -from pysen.manifest import ManifestBase - - -def build( - components: Sequence[ComponentBase], src_path: Optional[pathlib.Path] -) -> ManifestBase: - isort_setting: IsortSetting = pysen.IsortSetting.default() - isort_setting.force_single_line = True - isort_setting.known_first_party = {"pfhedge"} - - isort = pysen.Isort(setting=isort_setting.to_black_compatible()) - - others = [ - component for component in components if not isinstance(component, pysen.Isort) - ] - - return Manifest([isort, *others]) diff --git a/pfhedge/features/_base.py b/pfhedge/features/_base.py index ef4a16dc..e13497d0 100644 --- a/pfhedge/features/_base.py +++ b/pfhedge/features/_base.py @@ -74,7 +74,7 @@ def is_state_dependent(self) -> bool: # If a feature uses the state of a hedger, it is state dependent. return getattr(self, "hedger") is not None - def __str__(self): + def __str__(self) -> str: return self.name # TODO(simaki) Remove later diff --git a/pfhedge/features/_getter.py b/pfhedge/features/_getter.py index 66995316..9f64ceb3 100644 --- a/pfhedge/features/_getter.py +++ b/pfhedge/features/_getter.py @@ -14,7 +14,7 @@ class FeatureFactory: _features: Dict[str, Type[Feature]] # singleton - def __new__(cls, *args, **kwargs): + def __new__(cls, *args: Any, **kwargs: Any) -> "FeatureFactory": if not hasattr(cls, "_instance"): cls._instance = super().__new__(cls) cls._instance._features = OrderedDict() diff --git a/pfhedge/features/features.py b/pfhedge/features/features.py index 69935b73..e6b0d59d 100644 --- a/pfhedge/features/features.py +++ b/pfhedge/features/features.py @@ -134,7 +134,7 @@ def __str__(self) -> str: def get(self, time_step: Optional[int] = None) -> Tensor: index = [time_step] if isinstance(time_step, int) else ... - output = self.derivative.ul().spot[:, index].unsqueeze(-1) + output = self.derivative.ul().spot[:, index].unsqueeze(-1) # type: ignore if self.log: output.log_() return output @@ -147,7 +147,7 @@ class UnderlierLogSpot(UnderlierSpot): ``'underlier_log_spot'`` """ - def __init__(self): + def __init__(self) -> None: super().__init__(log=True) @@ -167,7 +167,7 @@ def __str__(self) -> str: def get(self, time_step: Optional[int] = None) -> Tensor: index = [time_step] if isinstance(time_step, int) else ... - output = self.derivative.spot[:, index].unsqueeze(-1) + output = self.derivative.spot[:, index].unsqueeze(-1) # type: ignore if self.log: output.log_() return output @@ -186,7 +186,7 @@ class Volatility(StateIndependentFeature): def get(self, time_step: Optional[int] = None) -> Tensor: index = [time_step] if isinstance(time_step, int) else ... - return self.derivative.ul().volatility[:, index].unsqueeze(-1) + return self.derivative.ul().volatility[:, index].unsqueeze(-1) # type: ignore class Variance(StateIndependentFeature): @@ -200,7 +200,7 @@ class Variance(StateIndependentFeature): def get(self, time_step: Optional[int]) -> Tensor: index = [time_step] if isinstance(time_step, int) else ... - return self.derivative.ul().variance[:, index].unsqueeze(-1) + return self.derivative.ul().variance[:, index].unsqueeze(-1) # type: ignore class PrevHedge(Feature): @@ -305,7 +305,7 @@ class Zeros(StateIndependentFeature): def get(self, time_step: Optional[int] = None) -> Tensor: index = [time_step] if time_step is not None else ... - return torch.zeros_like(self.derivative.ul().spot[..., index]).unsqueeze(-1) + return torch.zeros_like(self.derivative.ul().spot[..., index]).unsqueeze(-1) # type: ignore class Ones(StateIndependentFeature): @@ -335,7 +335,7 @@ class Ones(StateIndependentFeature): def get(self, time_step: Optional[int] = None) -> Tensor: index = [time_step] if time_step is not None else ... - return torch.ones_like(self.derivative.ul().spot[..., index]).unsqueeze(-1) + return torch.ones_like(self.derivative.ul().spot[..., index]).unsqueeze(-1) # type: ignore class Empty(StateIndependentFeature): @@ -365,7 +365,7 @@ class Empty(StateIndependentFeature): def get(self, time_step: Optional[int] = None) -> Tensor: index = [time_step] if time_step is not None else ... - return torch.empty_like(self.derivative.ul().spot[..., index]).unsqueeze(-1) + return torch.empty_like(self.derivative.ul().spot[..., index]).unsqueeze(-1) # type: ignore class MaxMoneyness(StateIndependentFeature): diff --git a/pfhedge/instruments/derivative/base.py b/pfhedge/instruments/derivative/base.py index 18785da3..4477c507 100644 --- a/pfhedge/instruments/derivative/base.py +++ b/pfhedge/instruments/derivative/base.py @@ -305,7 +305,7 @@ def moneyness(self, time_step: Optional[int] = None, log: bool = False) -> Tenso torch.Tensor """ index = ... if time_step is None else [time_step] - output = self.underlier.spot[..., index] / self.strike + output = self.underlier.spot[..., index] / self.strike # type: ignore if log: output = output.log() return output @@ -389,7 +389,7 @@ def max_log_moneyness(self, time_step: Optional[int] = None) -> Tensor: class BaseOption(BaseDerivative, OptionMixin): """(deprecated) Base class for options.""" - def __init__(self): + def __init__(self) -> None: super().__init__() raise DeprecationWarning( "BaseOption is deprecated. Inherit `BaseDerivative` and `OptionMixin` instead." diff --git a/pfhedge/nn/modules/bs/american_binary.py b/pfhedge/nn/modules/bs/american_binary.py index 0eec3d0b..066a4953 100644 --- a/pfhedge/nn/modules/bs/american_binary.py +++ b/pfhedge/nn/modules/bs/american_binary.py @@ -82,7 +82,9 @@ def __init__( self.derivative = derivative @classmethod - def from_derivative(cls, derivative): + def from_derivative( + cls, derivative: AmericanBinaryOption + ) -> "BSAmericanBinaryOption": """Initialize a module from a derivative. Args: diff --git a/pfhedge/nn/modules/bs/black_scholes.py b/pfhedge/nn/modules/bs/black_scholes.py index c78c386d..9fafd4dc 100644 --- a/pfhedge/nn/modules/bs/black_scholes.py +++ b/pfhedge/nn/modules/bs/black_scholes.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from typing import Any from typing import Callable from typing import Dict from typing import Iterator @@ -17,7 +18,7 @@ class BlackScholesModuleFactory: _modules: Dict[str, Type[Module]] # singleton - def __new__(cls, *args, **kwargs): + def __new__(cls, *args: Any, **kwargs: Any) -> "BlackScholesModuleFactory": if not hasattr(cls, "_instance"): cls._instance = super().__new__(cls) cls._instance._modules = OrderedDict() @@ -97,5 +98,5 @@ class BlackScholes(Module): vega: Callable[..., Tensor] # vega(self, ...) -> Tensor theta: Callable[..., Tensor] # theta(self, ...) -> Tensor - def __new__(cls, derivative): - return BlackScholesModuleFactory().get_class_from_derivative(derivative) + def __new__(cls, derivative: "Derivative") -> "BlackScholes": + return BlackScholesModuleFactory().get_class_from_derivative(derivative) # type: ignore diff --git a/pfhedge/nn/modules/bs/european.py b/pfhedge/nn/modules/bs/european.py index 1c9fb002..c38b52d6 100644 --- a/pfhedge/nn/modules/bs/european.py +++ b/pfhedge/nn/modules/bs/european.py @@ -73,7 +73,7 @@ def __init__( self.derivative = derivative @classmethod - def from_derivative(cls, derivative): + def from_derivative(cls, derivative: EuropeanOption) -> "BSEuropeanOption": """Initialize a module from a derivative. Args: diff --git a/pfhedge/nn/modules/bs/european_binary.py b/pfhedge/nn/modules/bs/european_binary.py index 350deb7b..459c51a5 100644 --- a/pfhedge/nn/modules/bs/european_binary.py +++ b/pfhedge/nn/modules/bs/european_binary.py @@ -75,7 +75,9 @@ def __init__( self.derivative = derivative @classmethod - def from_derivative(cls, derivative): + def from_derivative( + cls, derivative: EuropeanBinaryOption + ) -> "BSEuropeanBinaryOption": """Initialize a module from a derivative. Args: diff --git a/pfhedge/nn/modules/bs/lookback.py b/pfhedge/nn/modules/bs/lookback.py index a64429f5..b82992be 100644 --- a/pfhedge/nn/modules/bs/lookback.py +++ b/pfhedge/nn/modules/bs/lookback.py @@ -83,7 +83,7 @@ def __init__( self.derivative = derivative @classmethod - def from_derivative(cls, derivative): + def from_derivative(cls, derivative: LookbackOption) -> "BSLookbackOption": """Initialize a module from a derivative. Args: diff --git a/pfhedge/nn/modules/hedger.py b/pfhedge/nn/modules/hedger.py index 75883212..9cd1fc88 100644 --- a/pfhedge/nn/modules/hedger.py +++ b/pfhedge/nn/modules/hedger.py @@ -475,7 +475,7 @@ def compute_loss( """ with torch.set_grad_enabled(enable_grad): - def _get_loss(): + def _get_loss() -> Tensor: derivative.simulate(n_paths=n_paths, init_state=init_state) portfolio = self.compute_portfolio(derivative, hedge=hedge) return self.criterion(portfolio, derivative.payoff()) @@ -666,7 +666,7 @@ def price( """ with torch.set_grad_enabled(enable_grad): - def _get_price(): + def _get_price() -> Tensor: derivative.simulate(n_paths=n_paths, init_state=init_state) portfolio = self.compute_portfolio(derivative, hedge) # Negative because selling diff --git a/pfhedge/nn/modules/loss.py b/pfhedge/nn/modules/loss.py index 99b2f11e..7e1eb63e 100644 --- a/pfhedge/nn/modules/loss.py +++ b/pfhedge/nn/modules/loss.py @@ -1,4 +1,5 @@ from abc import ABC +from abc import abstractmethod from typing import Callable import torch @@ -20,6 +21,7 @@ class HedgeLoss(Module, ABC): """Base class for hedging criteria.""" + @abstractmethod def forward(self, input: Tensor, target: TensorOrScalar = 0.0) -> Tensor: """Returns the loss of the profit-loss distribution. @@ -39,6 +41,7 @@ def forward(self, input: Tensor, target: TensorOrScalar = 0.0) -> Tensor: Returns: torch.Tensor """ + pass def cash(self, input: Tensor, target: TensorOrScalar = 0.0) -> Tensor: """Returns the cash amount which is as preferable as diff --git a/pfhedge/stochastic/rough_bergomi.py b/pfhedge/stochastic/rough_bergomi.py index 9176e440..5fa694a0 100644 --- a/pfhedge/stochastic/rough_bergomi.py +++ b/pfhedge/stochastic/rough_bergomi.py @@ -122,9 +122,7 @@ def discrete_TBSS_fn(k: torch.Tensor, a: TensorOrScalar) -> torch.Tensor: _gamma = torch.cat([torch.zeros(2, dtype=dtype, device=device), _gamma], dim=0) _Xi = dW1[:, :, 0] _GXi_convolve = torch.nn.functional.conv1d( - _gamma.flip(0)[None, None, :], - _Xi[:, None, :], - padding=_Xi.size(1) - 1, + _gamma.flip(0)[None, None, :], _Xi[:, None, :], padding=_Xi.size(1) - 1 )[0, :, :] _Y2 = _GXi_convolve[:, -n_steps:].flip(1) Y = torch.sqrt(2 * alpha_tensor + 1) * (_Y1 + _Y2) diff --git a/poetry.toml b/poetry.toml deleted file mode 100644 index bb621ad4..00000000 --- a/poetry.toml +++ /dev/null @@ -1,4 +0,0 @@ -[installer] -# TODO(masanorihirano): Remove this after the bugs happenning after poetry version 1.4.1 are fixed in some packages -# issue #609, avoiding invalid hash (c.f., https://github.com/python-poetry/poetry/issues/7691). -modern-installation = false diff --git a/pyproject.toml b/pyproject.toml index d5a9c78a..20e17934 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,25 +7,25 @@ license = "MIT" repository = "https://github.com/pfnet-research/pfhedge" [tool.poetry.dependencies] -python = "^3.8,<3.12" +python = "^3.8.1,<3.12" torch = ">=1.9.0,<3.0.0" tqdm = "^4.62.3" [tool.poetry.dev-dependencies] pytest = "^6.2.5" black = "21.9b0" -isort = "^5.9.3" -mypy = "0.920" +isort = "5.9.3" pytest-cov = "^3.0.0" Sphinx = "^4.2.0" sphinx-autobuild = "^2021.3.14" sphinx-copybutton = "^0.4.0" furo = "^2021.9.22" codecov = "^2.1.12" -pysen = "^0.10.3" click = "8.0.4" flake8 = "4.0.1" scipy = "^1.10.1" +mypy = "^1.11.1" +pyproject-flake8 = "4.0.1" [build-system] requires = ["poetry-core>=1.0.0"] @@ -34,39 +34,53 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] markers = ["gpu: tests using gpu"] -[tool.pysen] -version = "0.10" -builder = "lint.py" - -[tool.pysen.lint] -enable_black = true -enable_flake8 = true -enable_isort = true -enable_mypy = true -mypy_preset = "entry" -line_length = 88 -py_version = "py37" -#isort_known_first_party = ["pfhedge"] - -[[tool.pysen.lint.mypy_targets]] - paths = ["pfhedge"] - -[tool.pysen.lint.source] - includes = ["."] - excludes = ["examples/", "docs/"] - -[tool.black] # automatically generated by pysen -# pysen ignores and overwrites any modifications +[tool.black] line-length = 88 -target-version = ["py37"] +exclude = '/(__pycache__|__init__\.py|\.git|\.cache|\.mypy_cache|docs|build|dist|\.venv)/' -[tool.isort] # automatically generated by pysen -# pysen ignores and overwrites any modifications +[tool.isort] +profile = 'black' ensure_newline_before_comments = true force_grid_wrap = 0 force_single_line = true include_trailing_comma = true known_first_party = ["pfhedge"] +skip = [ + ".cache", + ".git", + "__pycache__", + "docs", + "build", + "dist", + ".venv"] line_length = 88 multi_line_output = 3 use_parentheses = true + +[tool.mypy] +disallow_untyped_defs = true +ignore_missing_imports = true +exclude = [ + ".cache", + ".git", + "__pycache__", + "docs", + "build", + "dist", + ".venv"] + +[tool.flake8] +ignore = "E203,E231,E501,W503" +max-line-length = 88 +exclude = [ + ".cache", + ".git", + "__pycache__", + "docs", + "build", + "dist", + ".venv", + "__init__.py", + "qfeval", + ] +select = "B,B950,C,E,F,W" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 387ff191..00000000 --- a/setup.cfg +++ /dev/null @@ -1,30 +0,0 @@ -[flake8] -ignore = E203,E231,E501,W503 -max-line-length = 88 -select = B,B950,C,E,F,W - -[mypy] -# automatically generated by pysen -# pysen ignores and overwrites any modifications -check_untyped_defs = True -disallow_any_decorated = False -disallow_any_generics = False -disallow_any_unimported = False -disallow_incomplete_defs = True -disallow_subclassing_any = True -disallow_untyped_calls = False -disallow_untyped_decorators = False -disallow_untyped_defs = False -ignore_errors = False -ignore_missing_imports = True -no_implicit_optional = True -python_version = 3.7 -show_error_codes = True -strict_equality = True -strict_optional = True -warn_redundant_casts = True -warn_return_any = False -warn_unreachable = True -warn_unused_configs = True -warn_unused_ignores = False - diff --git a/tests/features/test_features.py b/tests/features/test_features.py index 5dda2af2..147a7a8f 100644 --- a/tests/features/test_features.py +++ b/tests/features/test_features.py @@ -27,13 +27,7 @@ class _TestFeature: - def assert_same_dtype( - self, - feature, - derivative, - dtype, - device: str = "cpu", - ): + def assert_same_dtype(self, feature, derivative, dtype, device: str = "cpu"): derivative.to(dtype=dtype, device=device).simulate() f = feature.of(derivative) assert f.get(0).dtype == dtype diff --git a/tests/instruments/derivative/test_european.py b/tests/instruments/derivative/test_european.py index a6cfebbe..8e1df8f7 100644 --- a/tests/instruments/derivative/test_european.py +++ b/tests/instruments/derivative/test_european.py @@ -35,13 +35,7 @@ def test_payoff_gpu(self): @pytest.mark.parametrize("n_paths", [100]) @pytest.mark.parametrize("init_spot", [1.0, 1.1, 0.9]) def test_put_call_parity( - self, - volatility, - strike, - maturity, - n_paths, - init_spot, - device: str = "cpu", + self, volatility, strike, maturity, n_paths, init_spot, device: str = "cpu" ): stock = BrownianStock(volatility).to(device) co = EuropeanOption(stock, strike=strike, maturity=maturity, call=True).to( diff --git a/tests/instruments/derivative/test_european_binary.py b/tests/instruments/derivative/test_european_binary.py index 9089e102..5a6fb3b9 100644 --- a/tests/instruments/derivative/test_european_binary.py +++ b/tests/instruments/derivative/test_european_binary.py @@ -37,13 +37,7 @@ def test_payoff_gpu(self): @pytest.mark.parametrize("n_paths", [100]) @pytest.mark.parametrize("init_spot", [1.0, 1.1, 0.9]) def test_parity( - self, - volatility, - strike, - maturity, - n_paths, - init_spot, - device: str = "cpu", + self, volatility, strike, maturity, n_paths, init_spot, device: str = "cpu" ): """ Test put-call parity. diff --git a/tests/nn/modules/test_mlp.py b/tests/nn/modules/test_mlp.py index f8fbe59f..ce5ffd4d 100644 --- a/tests/nn/modules/test_mlp.py +++ b/tests/nn/modules/test_mlp.py @@ -32,13 +32,7 @@ def test_n_layers_gpu(self, n_layers): @pytest.mark.parametrize("n_units", [2, 8, 32]) @pytest.mark.parametrize("in_features", [1, 2]) @pytest.mark.parametrize("out_features", [1, 2]) - def test_n_units( - self, - n_units, - in_features, - out_features, - device: str = "cpu", - ): + def test_n_units(self, n_units, in_features, out_features, device: str = "cpu"): n_layers = 4 m = MultiLayerPerceptron( n_layers=n_layers, n_units=n_units, out_features=out_features @@ -61,12 +55,7 @@ def test_n_units_gpu(self, n_units, in_features, out_features): @pytest.mark.parametrize("activation", [torch.nn.ELU(), torch.nn.CELU()]) @pytest.mark.parametrize("out_activation", [torch.nn.ELU(), torch.nn.CELU()]) - def test_activation( - self, - activation, - out_activation, - device: str = "cpu", - ): + def test_activation(self, activation, out_activation, device: str = "cpu"): n_layers = 4 m = MultiLayerPerceptron( n_layers=n_layers, activation=activation, out_activation=out_activation