Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and tidy up #96

Merged
merged 39 commits into from
Mar 10, 2024
Merged
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5fffde6
Add handling for empty trades in position_histories_dict
pboachie Feb 8, 2024
816f6a0
style: lint with black
kieran-mackle Feb 9, 2024
d6f13a8
Merge branch 'pboachie-development' into development
kieran-mackle Feb 9, 2024
d8056cf
style: made docstrings more readable
kieran-mackle Feb 28, 2024
112a91d
fix(DataStream): use generalised data fetch methods of AutoData
kieran-mackle Feb 28, 2024
5527195
fix(AutoPlot): fixed plotting nav timezone bug
kieran-mackle Feb 29, 2024
6198dea
feat(Strategy): added strategy base class
kieran-mackle Feb 29, 2024
e55ecbf
feat: added ability for bots to kill instance
kieran-mackle Feb 29, 2024
8b08175
feat(Broker): added broker base class
kieran-mackle Feb 29, 2024
43b26e4
style(AutoBot): add type hints for inherited attributes
kieran-mackle Mar 1, 2024
67721e6
style(AutoTrader): add type hints and improve docstrings
kieran-mackle Mar 1, 2024
06f8172
feat(Broker): ccxt broker will translate native TP/SL to ccxt params
kieran-mackle Mar 1, 2024
ed1d052
fix(Broker): use _safe_add method to update ccxt_params
kieran-mackle Mar 2, 2024
1574388
feat(utilities.py): added logger function
kieran-mackle Mar 2, 2024
f67c67d
feat: using logging throughout
kieran-mackle Mar 2, 2024
876365a
fix: log handling and ccxt instrument precisions
kieran-mackle Mar 2, 2024
576327f
docs(README.md): added to news section
kieran-mackle Mar 2, 2024
4552d32
Merge remote-tracking branch 'origin/main' into development
kieran-mackle Mar 2, 2024
10b971e
style(AutoTrader): remove newline character in log message
kieran-mackle Mar 2, 2024
a01a210
fix(AutoTrader): logging arguments
kieran-mackle Mar 3, 2024
566f9ee
fix(AutoTrader): handling of logger kwargs
kieran-mackle Mar 3, 2024
ff91c4b
style(AutoTrader): only print banner when stdout is true
kieran-mackle Mar 3, 2024
b70d39b
style: adjust logging verbosity when running backtest mode
kieran-mackle Mar 3, 2024
05f0534
style: reviewed code and made comments for improvements
kieran-mackle Mar 4, 2024
402fb64
refactor(data): renamed data to package_data for clarity
kieran-mackle Mar 6, 2024
d1a7612
refactor: major refactor of entire project
kieran-mackle Mar 6, 2024
4e07f33
build(setup.py): change minimum python version to 3.11
kieran-mackle Mar 6, 2024
34de57b
refactor: simplified broker module strucutre
kieran-mackle Mar 7, 2024
aae4f74
feat(utilities.py): added ccxt-download data stream object
kieran-mackle Mar 7, 2024
1eb06b5
fix: pass logging config to brokers
kieran-mackle Mar 9, 2024
08665c2
docs: updated documentation
kieran-mackle Mar 9, 2024
3dbffaa
feat(Strategy): add method for including indicators to plot
kieran-mackle Mar 9, 2024
7da4dce
feat: option to instantiate ccxt broker as read only
kieran-mackle Mar 9, 2024
f4f67aa
style: minor tidy ups
kieran-mackle Mar 9, 2024
4bfbb85
docs(README.md): update
kieran-mackle Mar 9, 2024
a430f34
style(ccxt.py): update api method to snake case
kieran-mackle Mar 10, 2024
c86e2d3
fix(utilities.py): ccxt global config indexing case
kieran-mackle Mar 10, 2024
e121094
ci: update github actions to use python3.11
kieran-mackle Mar 10, 2024
7204322
ci(tests): remove outdated tests
kieran-mackle Mar 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
docs: updated documentation
kieran-mackle committed Mar 9, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 08665c2b2073c1cf307680b012ca9f09d858966c
17 changes: 0 additions & 17 deletions docs/source/broker/broker-interface.md
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ Orders, Trades and Positions <trading>
Virtual Broker <virtual-broker>
Oanda <oanda>
Interactive Brokers <ib>
dYdX <dydx>
CCXT <ccxt>
```

@@ -62,14 +61,6 @@ instance. This allows you to access all methods offered by an exchange,
outside of the unified methods listed in the table above.


## Accessing the Broker Instance
To access the broker instance from your strategy, set `INCLUDE_BROKER` to
True in your [strategy configuration](strategy-config). Doing so, the
broker instance and broker utilities will be passed as named arguments
`broker` and `broker_utils` to your strategy's `__init__` method.



## Module Structure
Each new broker API is contained within its own submodule of the
`autotrader.brokers` module. This submodule must contain two more
@@ -117,14 +108,6 @@ As of AutoTrader `v0.6.0`, [Interactive Brokers](ib-module-docs) is also
supported.


### DYDX Cryto Exchange

`broker='dydx'`

[dYdX](https://dydx.exchange/) is a decentralised cryptocurrency derivatives
exchange.


### CCXT

`broker='ccxt:<exchange name>'`
2 changes: 1 addition & 1 deletion docs/source/broker/ccxt.md
Original file line number Diff line number Diff line change
@@ -90,7 +90,7 @@ CCXT:EXCHANGE:

## API Reference
```{eval-rst}
.. autoclass:: autotrader.brokers.ccxt.broker.Broker
.. autoclass:: autotrader.brokers.ccxt.Broker
:members:
:private-members:
```
47 changes: 0 additions & 47 deletions docs/source/broker/dydx.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/source/broker/ib.md
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@

## API Reference
```{eval-rst}
.. autoclass:: autotrader.brokers.ib.broker.Broker
.. autoclass:: autotrader.brokers.ib.Broker
:members:
:private-members:
```
4 changes: 1 addition & 3 deletions docs/source/broker/oanda.md
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
`broker='oanda'`



## Supported Features

| Feature | Supported? | Alternative |
@@ -42,11 +41,10 @@ OANDA:
````



## API Reference

```{eval-rst}
.. autoclass:: autotrader.brokers.oanda.broker.Broker
.. autoclass:: autotrader.brokers.oanda.Broker
:members:
:private-members:
```
11 changes: 6 additions & 5 deletions docs/source/broker/trading.md
Original file line number Diff line number Diff line change
@@ -75,8 +75,12 @@ need not be. Read more about stop-limit orders
[here](https://www.investopedia.com/terms/s/stop-limitorder.asp).

```python
stop_limit_order = Order(direction=1, order_type='stop-limit',
order_limit_price=1.2312, order_stop_price=1.2300)
stop_limit_order = Order(
direction=1,
order_type='stop-limit',
order_limit_price=1.2312,
order_stop_price=1.2300
)
```


@@ -132,7 +136,6 @@ are treated as `market` order type (liquidity consuming) when calculating tradin
```



(isolated-position-object)=
## Isolated Positions
```{eval-rst}
@@ -141,8 +144,6 @@ are treated as `market` order type (liquidity consuming) when calculating tradin
```




(position-object)=
## Positions
```{eval-rst}
2 changes: 1 addition & 1 deletion docs/source/broker/virtual-broker.md
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ and used to simulate execution.
## API Reference

```{eval-rst}
.. autoclass:: autotrader.brokers.virtual.broker.Broker
.. autoclass:: autotrader.brokers.virtual.Broker
:members:
:private-members:
```
89 changes: 0 additions & 89 deletions docs/source/core/AutoData.md

This file was deleted.

5 changes: 1 addition & 4 deletions docs/source/core/core-modules.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@
:hidden:

AutoTrader <AutoTrader>
AutoData <AutoData>
AutoPlot <AutoPlot>
AutoBot <AutoBot>
Utilities <utilities>
@@ -23,7 +22,6 @@ The table below provides a summary of the modules available.
| Module | Description |
| :----: | ----------- |
| [AutoTrader](autotrader-docs) | The primary API, used for all trading purposes. |
| [AutoData](autodata-docs) | The data retrieval API, used by AutoTrader and for manual use. |
| [AutoPlot](autoplot-docs) | The automated plotting tool, used by AutoTrader and for manual use. |
| [AutoBot](autobot-docs) | A trading bot, used to manage data and run strategies. |
| [Utilities](utilities-module) | A collection of tools and utilities to make everything work. |
@@ -60,8 +58,7 @@ watchlist. There is a second configuration file, the [global configuration](glob
conditionally. If you are live trading, you will need to create a global configuration
file to provide brokerage account details. You will also need to do this if you wish to use a broker to obtain price data.
If you will are only backtesting, you do not need
to provide a global configuration file. In this case, [AutoData](autodata-docs) will revert to using the Yahoo Finance
API for price data.
to provide a global configuration file.


### AutoTrader
12 changes: 1 addition & 11 deletions docs/source/getting-started.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ This page has all the information required to download and install AutoTrader.

AutoTrader can be installed in two ways; through
[PyPI](https://pypi.org/project/autotrader/) or by cloning the repository
directly.
directly.


### PyPI Install
@@ -46,7 +46,6 @@ optionally install more dependencies, depending on where you plan to trade or wh
data from.

Options include:
- dydx: to include dYdX dependencies
- ccxt: to include CCXT dependencies
- oanda: : to include Oanda v20 dependencies
- ib: to include Interactive Broker dependencies
@@ -59,15 +58,6 @@ example:
pip install autotrader[ccxt,yfinance]
```

### A note on dYdX Dependency
The package dependencies of the
[dYdX V3 Python interface](https://pypi.org/project/dydx-v3-python/) are
very tightly defined. For example, if you try to install AutoTrader with
both the `ccxt` and `dydx` dependencies, pip will not be able to resolve
the conflicts. As such, it is recommended that you maintain separate
environments for your trading.


## Demo Repository
To make getting started with AutoTrader even easier, download the demo repository from
[here](https://github.com/kieran-mackle/autotrader-demo). This repo contains example run files, strategies and configuration
12 changes: 6 additions & 6 deletions docs/source/index.md
Original file line number Diff line number Diff line change
@@ -31,19 +31,19 @@ to offer. Otherwise, head on over to the


## Supported Brokers and Exchanges
With AutoTrader `v0.7.0`, you can access over 100 cryptocurrency exchanges thanks to the integration
of [CCXT](https://github.com/ccxt/ccxt). The table below summarises the connection to supported brokers.
AutoTrader supports integrations with the following brokers.

| Broker | Asset classes | Integration status | Docs page |
| -------- | ------------- | ------------------ | --------- |
| [Oanda](https://www.oanda.com/) | Forex CFDs | Complete | [link](oanda-module-docs)|
| [CCXT](https://github.com/ccxt/ccxt) | Cryptocurrencies | Complete | [link](ccxt-module-docs) |
| [Interactive Brokers](https://www.interactivebrokers.com/en/home.php) | Many | In progress | [link](ib-module-docs) |
| [dYdX](https://dydx.exchange/) | Cryptocurrencies | Complete | [link](dydx-module-docs) |
| [CCXT](https://github.com/ccxt/ccxt) | Cryptocurrencies | In progress | [link](ccxt-module-docs) |
<!-- | [dYdX](https://dydx.exchange/) | Cryptocurrencies | Complete | [link](dydx-module-docs) | -->


## Latest Changes
AutoTrader `v0.7.0` has been released! Make sure to check out the [changelog](changelog) when upgrading
AutoTrader has gone through a full refactor to simplify the way things run.
Make sure to check out the [changelog](changelog) when upgrading
for details on the breaking changes and latest features.

## Index
@@ -60,7 +60,7 @@ Feature Showcase <features/features>
```

```{toctree}
:maxdepth: 2
:maxdepth: 1
:caption: Using AutoTrader
:hidden:

17 changes: 2 additions & 15 deletions docs/source/tutorials/building-strategy.md
Original file line number Diff line number Diff line change
@@ -57,9 +57,6 @@ NAME: 'Simple Macd Strategy' # strategy name
MODULE: 'macd' # strategy module
CLASS: 'SimpleMACD' # strategy class
INTERVAL: '1h' # stategy timeframe
PERIOD: 300 # candles required by strategy
SIZING: 'risk' # sizing method
RISK_PC: 1.5 # risk per trade (%)
PARAMETERS: # strategy parameters
ema_period: 200
MACD_fast: 12
@@ -78,19 +75,9 @@ alone, as specified by the `WATCHLIST` key. Note that the format
of the instruments provided here must match your data feed (in this case,
Yahoo Finance, which denotes FX with '=X').

It is worth noting that we are taking advantage of AutoTrader's automatic
position size calculation, by defining the `SIZING: 'risk'` and `RISK_PC: 1.5`
keys. These keys tell AutoTrader to use a risk-based approach to position
sizing. As such, when an order is submitted from the strategy, AutoTrader
will use the current price and stop-loss price to calculate the appropriate
position size, capping the maximium loss to the percentage defined by
`RISK_PC`. In this case, any single trade can only ever lose 1.5% of the
account.

We also define the `INTERVAL: '1h'` key, meaning that our strategy will run
on the 1-hour timeframe. This value is used when retrieving price data
through [AutoData](autodata-docs). This is discussed more in the next
section.
on the 1-hour timeframe. This value is used when retrieving price data.
This is discussed more in the next section.

```{tip}
You can find a template strategy configuration file in the
206 changes: 117 additions & 89 deletions docs/source/tutorials/condensed-walkthrough.md
Original file line number Diff line number Diff line change
@@ -54,9 +54,6 @@ NAME: 'Simple Macd Strategy' # strategy name
MODULE: 'macd' # strategy module
CLASS: 'SimpleMACD' # strategy class
INTERVAL: '1h' # stategy timeframe
PERIOD: 300 # candles required by strategy
SIZING: 'risk' # sizing method
RISK_PC: 1.5 # risk per trade (%)
PARAMETERS: # strategy parameters
ema_period: 200
MACD_fast: 12
@@ -94,8 +91,8 @@ return.

A long order can be created by specifying `direction=1` when creating the
`Order`, whereas a short order can be created by specifying `direction=-1`.
If there is no trading signal this update, you can create an
[empty order](empty-order) with just `Order()`. We also define our exit targets
If there is no trading signal this update, you can just return `None`.
We also define our exit targets
by the `stop_loss` and `take_profit` arguments. The strategy below uses
the `generate_exit_levels` helper method to calculate these prices.

@@ -109,97 +106,131 @@ provided in the Github repository.


```py
# macd.py
import pandas as pd
from finta import TA
from autotrader import Order, indicators
from datetime import datetime
from autotrader.strategy import Strategy
import autotrader.indicators as indicators
from autotrader.brokers.trading import Order
from autotrader.brokers.broker import Broker
class SimpleMACD:
class SimpleMACD(Strategy):
"""Simple MACD Strategy
Rules
------
1. Trade in direction of trend, as per 200EMA.
2. Entry signal on MACD cross below/above zero line.
3. Set stop loss at recent price swing.
4. Target 1.5 take profit.
"""
def __init__(self, parameters, data, instrument):
"""Define all indicators used in the strategy.
"""
self.name = "Simple MACD Trend Strategy"
def __init__(
self, parameters: dict, instrument: str, broker: Broker, *args, **kwargs
) -> None:
"""Define all indicators used in the strategy."""
self.name = "MACD Trend Strategy"
self.params = parameters
self.broker = broker
self.instrument = instrument
# Initial feature generation (for plotting only)
self.generate_features(data)
def create_plotting_indicators(self, data: pd.DataFrame):
# Construct indicators dict for plotting
self.indicators = {'MACD (12/26/9)': {'type': 'MACD',
'macd': self.MACD.MACD,
'signal': self.MACD.SIGNAL},
'EMA (200)': {'type': 'MA',
'data': self.ema}
}
def generate_features(self, data):
"""Updates MACD indicators and saves them to the class attributes."""
# Save data for other functions
self.data = data
ema, MACD, MACD_CO, MACD_CO_vals, swings = self.generate_features(data)
self.indicators = {
"MACD (12/26/9)": {
"type": "MACD",
"macd": MACD.MACD,
"signal": MACD.SIGNAL,
"histogram": MACD.MACD - MACD.SIGNAL,
},
"EMA (200)": {"type": "MA", "data": ema},
}
def generate_features(self, data: pd.DataFrame):
# 200EMA
self.ema = TA.EMA(self.data, self.params['ema_period'])
ema = TA.EMA(data, self.params["ema_period"])
# MACD
self.MACD = TA.MACD(self.data, self.params['MACD_fast'],
self.params['MACD_slow'], self.params['MACD_smoothing'])
self.MACD_CO = indicators.crossover(self.MACD.MACD, self.MACD.SIGNAL)
self.MACD_CO_vals = indicators.cross_values(self.MACD.MACD,
self.MACD.SIGNAL,
self.MACD_CO)
MACD = TA.MACD(
data,
self.params["MACD_fast"],
self.params["MACD_slow"],
self.params["MACD_smoothing"],
)
MACD_CO = indicators.crossover(MACD.MACD, MACD.SIGNAL)
MACD_CO_vals = indicators.cross_values(MACD.MACD, MACD.SIGNAL, MACD_CO)
# Price swings
self.swings = indicators.find_swings(self.data)
def generate_signal(self, data):
swings = indicators.find_swings(data)
return ema, MACD, MACD_CO, MACD_CO_vals, swings
def generate_signal(self, dt: datetime):
"""Define strategy to determine entry signals."""
# Feature calculation
self.generate_features(data)
# Look for entry signals (index -1 for the latest data)
if self.data.Close.values[-1] > self.ema[-1] and \
self.MACD_CO[-1] == 1 and \
self.MACD_CO_vals[-1] < 0:
# Long entry signal detected! Calculate SL and TP prices
stop, take = self.generate_exit_levels(signal=1)
new_order = Order(direction=1, stop_loss=stop, take_profit=take)
elif self.data.Close.values[-1] < self.ema[-1] and \
self.MACD_CO[-1] == -1 and \
self.MACD_CO_vals[-1] > 0:
# Short entry signal detected! Calculate SL and TP prices
stop, take = self.generate_exit_levels(signal=-1)
new_order = Order(direction=-1, stop_loss=stop, take_profit=take)
# Get OHLCV data
data = self.broker.get_candles(self.instrument, granularity="1h", count=300)
if len(data) < 300:
# This was previously a check in AT
return None
# Generate indicators
ema, MACD, MACD_CO, MACD_CO_vals, swings = self.generate_features(data)
# Create orders
if (
data["Close"].values[-1] > ema.iloc[-1]
and MACD_CO.iloc[-1] == 1
and MACD_CO_vals.iloc[-1] < 0
):
exit_dict = self.generate_exit_levels(signal=1, data=data, swings=swings)
new_order = Order(
direction=1,
size=1,
stop_loss=exit_dict["stop_loss"],
take_profit=exit_dict["take_profit"],
)
elif (
data["Close"].values[-1] < ema.iloc[-1]
and MACD_CO.iloc[-1] == -1
and MACD_CO_vals.iloc[-1] > 0
):
exit_dict = self.generate_exit_levels(signal=-1, data=data, swings=swings)
new_order = Order(
direction=-1,
size=1,
stop_loss=exit_dict["stop_loss"],
take_profit=exit_dict["take_profit"],
)
else:
# No trading signal, return a blank Order
new_order = Order()
new_order = None
return new_order
def generate_exit_levels(self, signal):
"""Function to determine stop loss and take profit prices."""
RR = self.params['RR']
if signal == 1:
# Long signal
stop = self.swings.Lows[-1]
take = self.data.Close[-1] + RR*(self.data.Close[-1] - stop)
def generate_exit_levels(
self, signal: int, data: pd.DataFrame, swings: pd.DataFrame
):
"""Function to determine stop loss and take profit levels."""
stop_type = "limit"
RR = self.params["RR"]
if signal == 0:
stop = None
take = None
else:
# Short signal
stop = self.swings.Highs[-1]
take = self.data.Close[-1] - RR*(stop - self.data.Close[-1])
return stop, take
if signal == 1:
stop = swings["Lows"].iloc[-1]
take = data["Close"].iloc[-1] + RR * (data["Close"].iloc[-1] - stop)
else:
stop = swings["Highs"].iloc[-1]
take = data["Close"].iloc[-1] - RR * (stop - data["Close"].iloc[-1])
exit_dict = {"stop_loss": stop, "stop_type": stop_type, "take_profit": take}
return exit_dict
```


@@ -212,16 +243,17 @@ deploy your bot. This is all achieved in the example below.


```python
# runfile.py
# run.py
from autotrader import AutoTrader
# Create AutoTrader instance, configure it, and run backtest
at = AutoTrader()
at.configure(show_plot=True, verbosity=1, feed='yahoo',
mode='continuous', update_interval='1h')
at.add_strategy('macd')
at.backtest(start = '1/1/2021', end = '1/1/2022')
at.virtual_account_config(leverage=30)
at.configure(verbosity=1, show_plot=True, feed="yahoo")
at.add_strategy("macd")
at.backtest(start="1/6/2023", end="1/2/2024", localize_to_utc=True)
at.virtual_account_config(initial_balance=1000, leverage=30)
at.run()
```

Let's dive into this a bit more:
@@ -250,7 +282,6 @@ Simply run this file, and AutoTrader will do its thing.




### Backtest Results
With a verbosity of 1, you will see an output similar to that shown below.
As you can see, there is a detailed breakdown of trades taken during the
@@ -328,7 +359,8 @@ Average loss: -$15.86
## Going Live
Taking a strategy live is as easy as changing a few lines in your runfile.
Say you would like to trade your strategy on the cryptocurrency exchange
[dYdX](https://dydx.exchange/). Then, all you need to do is specify this
[Bybit](https://www.bybit.com/invite?ref=7NDOBW).
Then, all you need to do is specify this
as the `broker` in the `configure` method, as shown below. You will
just need to make sure you have provided the relevant API keys in your
`keys.yaml` file to connect to your exchange.
@@ -337,16 +369,15 @@ just need to make sure you have provided the relevant API keys in your
from autotrader import AutoTrader
at = AutoTrader()
at.configure(verbosity=1, broker='dydx',
mode='continuous', update_interval='1h')
at.configure(verbosity=1, broker='ccxt:bybit')
at.add_strategy('macd')
at.run()
```


What if you wanted to paper trade your strategy before putting real money into
it? Simply configure a virtual trading account and specify the exchange as
`dydx` (or whatever `broker` you specify in `configure`) and then you will
`ccxt:bybit` (or whatever `broker` you specify in `configure`) and then you will
be paper trading! Doing this, AutoTrader's virtual broker mirrors the real-time
orderbook of the exchange specified, making execution of orders as accurate as
possible.
@@ -355,11 +386,8 @@ possible.
from autotrader import AutoTrader

at = AutoTrader()
at.configure(verbosity=1, broker='dydx',
mode='continuous', update_interval='1h')
at.configure(verbosity=1, broker='ccxt:bybit')
at.add_strategy('macd')
at.virtual_account_config(leverage=30, exchange='dydx')
at.virtual_account_config(leverage=30, exchange='ccxt:bybit')
at.run()
```


217 changes: 124 additions & 93 deletions docs/source/tutorials/misc/single-file-strat.md
Original file line number Diff line number Diff line change
@@ -16,131 +16,162 @@ when we run this module, the strategy will run!


```python
import os
import pandas as pd
from finta import TA
from autotrader import indicators
from autotrader import Order
from datetime import datetime
from autotrader.strategy import Strategy
import autotrader.indicators as indicators
from autotrader.brokers.trading import Order
from autotrader.brokers.broker import Broker


class SimpleMACD:
class SimpleMACD(Strategy):
"""Simple MACD Strategy
Rules
------
1. Trade in direction of trend, as per 200EMA.
2. Entry signal on MACD cross below/above zero line.
3. Set stop loss at recent price swing.
4. Target 1.5 take profit.
"""

def __init__(self, params, data, instrument):
"""Define all indicators used in the strategy.
"""

def __init__(
self, parameters: dict, instrument: str, broker: Broker, *args, **kwargs
) -> None:
"""Define all indicators used in the strategy."""
self.name = "MACD Trend Strategy"
self.data = data
self.params = params
self.params = parameters
self.broker = broker
self.instrument = instrument


def create_plotting_indicators(self, data: pd.DataFrame):
# Construct indicators dict for plotting
ema, MACD, MACD_CO, MACD_CO_vals, swings = self.generate_features(data)
self.indicators = {
"MACD (12/26/9)": {
"type": "MACD",
"macd": MACD.MACD,
"signal": MACD.SIGNAL,
"histogram": MACD.MACD - MACD.SIGNAL,
},
"EMA (200)": {"type": "MA", "data": ema},
}

def generate_features(self, data: pd.DataFrame):
# 200EMA
self.ema = TA.EMA(data, params['ema_period'])
ema = TA.EMA(data, self.params["ema_period"])

# MACD
self.MACD = TA.MACD(data, self.params['MACD_fast'],
self.params['MACD_slow'], self.params['MACD_smoothing'])
self.MACD_CO = indicators.crossover(self.MACD.MACD, self.MACD.SIGNAL)
self.MACD_CO_vals = indicators.cross_values(self.MACD.MACD,
self.MACD.SIGNAL,
self.MACD_CO)

MACD = TA.MACD(
data,
self.params["MACD_fast"],
self.params["MACD_slow"],
self.params["MACD_smoothing"],
)
MACD_CO = indicators.crossover(MACD.MACD, MACD.SIGNAL)
MACD_CO_vals = indicators.cross_values(MACD.MACD, MACD.SIGNAL, MACD_CO)

# Price swings
self.swings = indicators.find_swings(data)
swings = indicators.find_swings(data)

# Construct indicators dict for plotting
self.indicators = {'MACD (12/26/9)': {'type': 'MACD',
'macd': self.MACD.MACD,
'signal': self.MACD.SIGNAL},
'EMA (200)': {'type': 'MA',
'data': self.ema}}


def generate_signal(self, i, **kwargs):
"""Define strategy to determine entry signals.
"""

if self.data.Close.values[i] > self.ema[i] and \
self.MACD_CO[i] == 1 and \
self.MACD_CO_vals[i] < 0:
exit_dict = self.generate_exit_levels(signal=1, i=i)
new_order = Order(direction=1,
stop_loss=exit_dict['stop_loss'],
take_profit=exit_dict['take_profit'])

elif self.data.Close.values[i] < self.ema[i] and \
self.MACD_CO[i] == -1 and \
self.MACD_CO_vals[i] > 0:
exit_dict = self.generate_exit_levels(signal=-1, i=i)
new_order = Order(direction=-1,
stop_loss=exit_dict['stop_loss'],
take_profit=exit_dict['take_profit'])
return ema, MACD, MACD_CO, MACD_CO_vals, swings

def generate_signal(self, dt: datetime):
"""Define strategy to determine entry signals."""
# Get OHLCV data
data = self.broker.get_candles(self.instrument, granularity="1h", count=300)
if len(data) < 300:
# This was previously a check in AT
return None

# Generate indicators
ema, MACD, MACD_CO, MACD_CO_vals, swings = self.generate_features(data)

# Create orders
if (
data["Close"].values[-1] > ema.iloc[-1]
and MACD_CO.iloc[-1] == 1
and MACD_CO_vals.iloc[-1] < 0
):
exit_dict = self.generate_exit_levels(signal=1, data=data, swings=swings)
new_order = Order(
direction=1,
size=1,
stop_loss=exit_dict["stop_loss"],
take_profit=exit_dict["take_profit"],
)

elif (
data["Close"].values[-1] < ema.iloc[-1]
and MACD_CO.iloc[-1] == -1
and MACD_CO_vals.iloc[-1] > 0
):
exit_dict = self.generate_exit_levels(signal=-1, data=data, swings=swings)
new_order = Order(
direction=-1,
size=1,
stop_loss=exit_dict["stop_loss"],
take_profit=exit_dict["take_profit"],
)

else:
new_order = Order()
new_order = None

return new_order

def generate_exit_levels(self, signal, i):
"""Function to determine stop loss and take profit levels.
"""
stop_type = 'limit'
RR = self.params['RR']

def generate_exit_levels(
self, signal: int, data: pd.DataFrame, swings: pd.DataFrame
):
"""Function to determine stop loss and take profit levels."""
stop_type = "limit"
RR = self.params["RR"]

if signal == 0:
stop = None
take = None
else:
if signal == 1:
stop = self.swings.Lows[i]
take = self.data.Close[i] + RR*(self.data.Close[i] - stop)
stop = swings["Lows"].iloc[-1]
take = data["Close"].iloc[-1] + RR * (data["Close"].iloc[-1] - stop)
else:
stop = self.swings.Highs[i]
take = self.data.Close[i] - RR*(stop - self.data.Close[i])

exit_dict = {'stop_loss': stop,
'stop_type': stop_type,
'take_profit': take}

stop = swings["Highs"].iloc[-1]
take = data["Close"].iloc[-1] - RR * (stop - data["Close"].iloc[-1])

exit_dict = {"stop_loss": stop, "stop_type": stop_type, "take_profit": take}

return exit_dict


if __name__ == "__main__":
from autotrader import AutoTrader

config = {'NAME': 'MACD Strategy',
'MODULE': 'macd_strategy',
'CLASS': 'SimpleMACD',
'INTERVAL': 'H4',
'PERIOD': 300,
'RISK_PC': 1.5,
'SIZING': 'risk',
'PARAMETERS': {'ema_period': 200,
'MACD_fast': 5,
'MACD_slow': 19,
'MACD_smoothing': 9,
'RR': 1.5},
'WATCHLIST': ['EUR_USD'],}


config = {
"NAME": "MACD Strategy",
"MODULE": "macd_strategy",
"CLASS": "SimpleMACD",
"INTERVAL": "H4",
"PARAMETERS": {
"ema_period": 200,
"MACD_fast": 5,
"MACD_slow": 19,
"MACD_smoothing": 9,
"RR": 1.5,
},
"WATCHLIST": ["EUR_USD"],
}

at = AutoTrader()
at.configure(verbosity=1, show_plot=False)
at.add_strategy(config_dict=config, strategy=SimpleMACD)
at.plot_settings(show_cancelled=False)
at.add_data({'EUR_USD': 'EUR_USD_H4.csv'},
data_directory=os.path.join(os.getcwd(), 'data'))
at.backtest(start = '1/1/2015',
end = '1/3/2022',
initial_balance=1000,
leverage=30,
spread=0.5,
commission=0.005)
at.backtest(
start="1/1/2015",
end="1/3/2022",
initial_balance=1000,
leverage=30,
spread=0.5,
commission=0.005,
)
at.run()
```
6 changes: 3 additions & 3 deletions docs/source/tutorials/overview.md
Original file line number Diff line number Diff line change
@@ -99,13 +99,13 @@ your_trading_project/


(trading-environments)=
# Trading Environments
## Trading Environments
There are two trading environments: `paper` and `live`. The environment
being used can be specified in the [`configure`](autotrader-configure)
method, but it will be overwritten to `paper` any time you call the
[virtual account configuration](autotrader-virtual-account-config) method.

## Paper Trading
### Paper Trading
`environment="paper"`

Paper trading can fall into one of two categories:
@@ -127,7 +127,7 @@ activate this functionality, simply configure a virtual trading account via the
[virtual account configuration](autotrader-virtual-account-config) method.


## Live Trading
### Live Trading
When you are ready to deploy your strategy with real money, set the `environment`
argument in the [`configure`](autotrader-configure) method to `live`. This will
switch all of the API pointers to the live endpoints, and fire your orders to
52 changes: 15 additions & 37 deletions docs/source/userfiles/strategy-configuration.md
Original file line number Diff line number Diff line change
@@ -31,37 +31,16 @@ the [strategy](trading-strategy), via the `parameters` argument.
|`MODULE`| A string containing the prefix name of the strategy module, without the *.py* suffix. |Yes, unless the strategy is passed directly via `add_strategy`| |
|`CLASS`| A string containing the class name within the strategy module. |Yes| |
|`INTERVAL`| The granularity of the data used by the strategy.|Yes | |
|`PERIOD`| The number of candles to fetch when live trading (eg. a value of 300 will fetch the latest 300 candles), or a timedelta string (eg. '30d').|Yes | |
|`PARAMETERS`| A dictionary containing custom strategy parameters (see below).|Yes| |
|`WATCHLIST`| A list containing the instruments to be traded in the strategy, in the [format required](autodata-docs) by the [data feed](autotrader-configure). |Yes| |
|`SIZING`| The method to use when calculating position size. Can be either 'risk' or an integer value corresponding to the number of units to trade. If using the 'risk' option, position size will be calculated based on trading account balance and the value assigned to `RISK_PC`.|No| None |
|`RISK_PC`| The percentage of the account balance to risk when determining position risk-based size.|No| None |
| `PORTFOLIO` | A boolean flag for if the strategy is a portfolio-based strategy, requiring fata for all instrumenets in the watchlists to run. |No| False |
|`INCLUDE_BROKER`| A boolean flag to indicate if the broker interface and broker utilities should be passed to the strategy's `__init__` method. Read more [here](strategy-broker-access). |No| False |
|`INCLUDE_STREAM`| A boolean flag to indicate if the [data stream](utils-datastream) object should be passed to the strategy's `__init__` method. |No| False |


### Data Interval
The `INTERVAL` key is a string used to define the granularity of the data used by
your strategy. For example, '1m' for minutely data, or '4h' for 4-hourly data.
This is used to infer the timestep to take when backtesting (and livetrading), but
also to convert the `PERIOD` to an integer value if necessary.
This is used to infer the timestep to take when backtesting (and livetrading).

If you would like to build a strategy which uses multiple timeframes, simply
specify the timeframes with comma separation in the `INTERVAL` key. For example,
to have access to 15-minute and 4-hour data, you would specify something
like `INTERVAL: 'M15,H1'`. In this case, the `data` object passed into the
strategy will be a dictionary, with keys defined by each granularity specified
in `INTERVAL` and the associated data.


### Data Period
The `PERIOD` key is used to determine how many rows of data (OHLC) is
required by your strategy. This could refer to the lookback period, either
as an integer value of the number of rows, or as a string such as '30d',
indicating 30 days worth of data is required. If an integer value is provided,
for example 300, then the latest 300 rows of data will be passed to your
strategy.


### Strategy Parameters
@@ -84,9 +63,6 @@ NAME: 'Strategy Name'
MODULE: 'modulename'
CLASS: 'StrategyClassName'
INTERVAL: 'M15'
PERIOD: 300
RISK_PC: 1 # 1%
SIZING: 'risk'
PARAMETERS:
ema_period: 200
rsi_period: 14
@@ -101,17 +77,19 @@ WATCHLIST: ['EUR_USD']
````
````{tab} Dictionary Form
```python
strategy_config = {'NAME': 'Strategy Name',
'MODULE': 'modulename',
'CLASS': 'StrategyClassName',
'INTERVAL': 'M15',
'PERIOD': 300,
'RISK_PC': 1,
'SIZING': 'risk',
'PARAMETERS': {'ema_period': 200,
'rsi_period': 14,
'RR': 2,
'stop_buffer': 10},
'WATCHLIST': ['EUR_USD',]}
strategy_config = {
'NAME': 'Strategy Name',
'MODULE': 'modulename',
'CLASS': 'StrategyClassName',
'INTERVAL': 'M15',
'PERIOD': 300,
'PARAMETERS': {
'ema_period': 200,
'rsi_period': 14,
'RR': 2,
'stop_buffer': 10
},
'WATCHLIST': ['EUR_USD',]
}
```
````