Skip to content

Commit

Permalink
Release/0.15.1 (#58)
Browse files Browse the repository at this point in the history
* DOC: Update documentation
  • Loading branch information
simaki authored Jun 25, 2021
1 parent f08c7d8 commit afa3b68
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 48 deletions.
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@
[![dl](https://img.shields.io/pypi/dm/epymetheus)](https://pypi.org/project/epymetheus)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

![wealth](examples/fig/wealth.png)
[Documentation](https://epymetheus.github.io/epymetheus/)

***Epymetheus*** is a multi-asset backtesting framework. It features an intuitive user API that lets analysts try out their trade strategies right away.

## Introduction
![wealth](examples/fig/wealth.png)

***Epymetheus*** is a multi-asset backtesting framework.
## Installation

It features an intuitive user API that lets analysts try out their trade strategies right away.
```sh
$ pip install epymetheus
```

### Features
## Features

1. **Intuitive and Pythonic API**
- *Epymetheus* designs Pythonic API that lets you code your idea intuitively without any fuss.
Expand All @@ -33,7 +37,7 @@ It features an intuitive user API that lets analysts try out their trade strateg
4. **Full Test Coverage**:
- Epymetheus is thoroughly tested by [continuous integration](https://github.com/epymetheus/epymetheus/actions?query=workflow%3ACI) with 100% code coverage.

### Integrations
## Integrations

Your trading strategy may incorporate various libraries out there, for instance,

Expand All @@ -42,12 +46,6 @@ Your trading strategy may incorporate various libraries out there, for instance,
* **Technical Indicators** - [TA-Lib](https://github.com/mrjbq7/ta-lib), etc.
* **Hyperparameter Optimization** - [Optuna](https://github.com/optuna/optuna) (Example follows), etc.

## Installation

```sh
$ pip install epymetheus
```

## How to use

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/epymetheus/epymetheus/blob/master/examples/readme/readme.ipynb)
Expand Down
92 changes: 69 additions & 23 deletions epymetheus/strategy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
from functools import partial
from time import time
from typing import TypeVar

import numpy as np
import pandas as pd
Expand All @@ -15,24 +16,44 @@
from ..trade import Trade
from ..trade import check_trade

T = TypeVar("T", bound="Strategy")

def create_strategy(fn, **params):

def create_strategy(fn, **params) -> T:
"""Create a :class:`Strategy` from a function.
A function `fn` takes more than one parameters.
The first parameter should be `universe`, which is a `pandas.DataFrame`
of historical prices. That is, its indices represents timestamp, columns
stands for the names of assets, and each element in the frame stores a price.
The following parameters are user-defined ones parameterizing the strategy.
These parameters should be useful when one want to try various configurations
such as trade volume, profit-taking threshold, loss-cut threshold, and so forth.
The function `fn` is supposed to yield arbitrary numbers of :class:`Trade`
depending on the `universe` and other parameters. These trades will be executed
for the `universe` and evaluated accordingly.
Args:
fn (callable): Function that returns iterable of :class:`Trade`
from universe and parameters.
**params: Parameter values.
fn (callable): A function that returns iterable of :class:`Trade`
from a `universe` and parameters.
**params: names and values of the parameters.
Returns:
:class:`Strategy`
Examples:
The following strategy trades the first asset in a given `universe`.
The parameter `my_param` controls the volume to trade.
>>> import epymetheus as ep
>>>
>>> def fn(universe, my_param):
... yield my_param * ep.trade("AAPL")
... asset = universe.columns[0]
... yield my_param * ep.trade(asset)
>>>
>>> strategy = ep.create_strategy(fn, my_param=2.0)
>>> universe = ...
>>> universe = pd.DataFrame({"AAPL": [100, 101], "AMZN": [200, 201]})
>>> strategy(universe)
[trade(['AAPL'], lot=[2.])]
"""
Expand All @@ -42,36 +63,61 @@ def create_strategy(fn, **params):
class Strategy(abc.ABC):
"""Base class of trading strategy.
There are two ways to create a :class:`Strategy`:
- Use :func:`create_strategy`: This should be easier for simple strategies.
See :func:`create_strategy`.
- Subclass :class:`Strategy`: See below.
One can create a strategy by subclassing :class:`Strategy` and
overriding a method `logic`.
The method `logic` takes arbitrary numbers of user-defined parameters
parameterizing the strategy.
These parameters should be useful when one want to try various configurations
such as trade volume, profit-taking threshold, loss-cut threshold, and so forth.
The method `logic` is supposed to yield arbitrary numbers of :class:`Trade`
depending on the `universe` and other parameters. These trades will be executed
for the `universe` and evaluated accordingly.
Examples:
Initialize by subclassing
The following strategy trades the first asset in a given `universe`.
The parameter `my_param` controls the volume to trade.
>>> import pandas as pd
>>> import epymetheus as ep
>>> from epymetheus import Strategy
>>>
>>> class MyStrategy(Strategy):
... def __init__(self, param):
... self.param = param
>>> class MyStrategy(ep.Strategy):
... def __init__(self, my_param):
... self.my_param = my_param
...
... def logic(self, universe):
... return [self.param * ep.trade("A")]
... def logic(self, universe: pd.DataFrame):
... asset = universe.columns[0]
... yield self.my_param * ep.trade(asset)
...
>>> strategy = MyStrategy(param=2.0)
>>> universe = ...
>>> strategy = MyStrategy(my_param=2.0)
>>> universe = pd.DataFrame({"AAPL": [100, 101], "AMZN": [200, 201]})
>>> strategy(universe)
[trade(['A'], lot=[2.])]
[trade(['AAPL'], lot=[2.])]
The method :func:`run` runs the strategy on a given universe.
>>> strategy = MyStrategy(my_param=2.0).run(universe, verbose=False)
>>> strategy.trades
[trade(['AAPL'], lot=[2.])]
"""

@classmethod
def _create_strategy(cls, f, **params):
def _create_strategy(cls, fn, **params) -> T:
self = cls()
self._f = f
self._fn = fn
self._params = params
return self

def __call__(self, universe, to_list=True):
if hasattr(self, "_f"):
setattr(self, "logic", partial(self._f, **self.get_params()))
if hasattr(self, "_fn"):
setattr(self, "logic", partial(self._fn, **self.get_params()))
trades = self.logic(universe)
trades = list(trades) if to_list else trades
return trades
Expand All @@ -90,7 +136,7 @@ def logic(self, universe):
iterable[Trade]
"""

def run(self, universe, verbose=True, check_trades=False):
def run(self: T, universe, verbose=True, check_trades=False) -> T:
"""Run a backtesting of strategy.
Args:
Expand Down Expand Up @@ -373,8 +419,8 @@ def __repr__(self):
>>> repr(strategy)
'MyStrategy'
"""
if hasattr(self, "_f"):
fname = self._f.__name__
if hasattr(self, "_fn"):
fname = self._fn.__name__
param = ", ".join(f"{k}={v}" for k, v in self.get_params().items())
param = f", {param}" if param != "" else ""
return f"strategy({fname}{param})"
Expand Down
41 changes: 28 additions & 13 deletions epymetheus/trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@


def trade(asset, entry=None, exit=None, take=None, stop=None, lot=1.0, **kwargs):
"""Create `Trade`.
"""Create a :class:`Trade`.
Args:
asset (str or array of str): Name of assets.
entry (object, optional): Datetime of entry.
exit (object, optional): Datetime of exit.
asset (str or array-like): Name of assets to trade.
If an array-like (e.g. `list` or `numpy.ndarray`) of objects is given,
a trade transacts multiple assets at once.
entry (object, optional): Timestamp to open a trade.
If not provided, the first timestamp in a given `universe`.
exit (object, optional): Timestamp to exit a trade.
If not provided, the last timestamp in a given `universe`.
take (float > 0, optional): Threshold of profit-take.
stop (float < 0, optional): Threshold of stop-loss.
lot (float or np.array, default 1.0): Lot to trade in unit of share.
lot (float or array-like, default 1.0): The amount of trade.
If an array-like (e.g. `list` or `numpy.ndarray`) of objects is given,
each element in an array represents the amount of each asset.
Returns:
epymetheus.Trade
:class:`Trade`
Examples:
Expand Down Expand Up @@ -61,12 +67,18 @@ class Trade:
"""A `epymetheus.Trade` represents a single trade.
Args:
asset (np.ndarray): Name of assets.
entry (object, optional): Datetime of entry.
exit (object, optional): Datetime of exit.
take (float, optional): Threshold of profit-take.
stop (float, optional): Threshold of stop-loss.
lot (float or np.ndarray, default 1.0): Lot to trade in unit of share.
asset (str or array-like): Name of assets to trade.
If an array-like (e.g. `list` or `numpy.ndarray`) of objects is given,
a trade transacts multiple assets at once.
entry (object, optional): Timestamp to open a trade.
If not provided, the first timestamp in a given `universe`.
exit (object, optional): Timestamp to exit a trade.
If not provided, the last timestamp in a given `universe`.
take (float > 0, optional): Threshold of profit-take.
stop (float < 0, optional): Threshold of stop-loss.
lot (float or array-like, default 1.0): The amount of trade.
If an array-like (e.g. `list` or `numpy.ndarray`) of objects is given,
each element in an array represents the amount of each asset.
Attributes:
close (object): Datetime to close the trade.
Expand Down Expand Up @@ -406,7 +418,7 @@ def check_trade(
check_lot: bool = True,
check_take: bool = True,
check_stop: bool = True,
):
) -> None:
"""Validation for `Trade`.
Args:
Expand All @@ -418,6 +430,9 @@ def check_trade(
check_take (bool, default=True):
check_stop (bool, default=True):
Returns:
None
Raises:
ValueError: If something is wrong
Expand Down

0 comments on commit afa3b68

Please sign in to comment.