Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 76 additions & 4 deletions doc/examples/Quick Start User Guide.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,81 @@
"GOOG.tail()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"External data can be joined to the strategy data frame before it is passed to\n",
"`Backtest`. For example, the snippet below adds a daily sentiment column. If\n",
"`ADANOS_API_KEY` is set, it tries to load Reddit stock sentiment from the\n",
"[Adanos Market Sentiment API](https://api.adanos.org/reddit/stocks/v1/);\n",
"otherwise, it uses a tiny sample series so the tutorial stays runnable offline.\n",
"\n",
"The resulting `Sentiment` column is available in a strategy as\n",
"`self.data.Sentiment[-1]`, just like `self.data.Close[-1]`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import os\n",
"from urllib.error import HTTPError, URLError\n",
"from urllib.parse import urlencode\n",
"from urllib.request import Request, urlopen\n",
"\n",
"import pandas as pd\n",
"\n",
"\n",
"def adanos_daily_sentiment(index, ticker):\n",
" daily_index = pd.DatetimeIndex(index).normalize()\n",
" sample = pd.Series(\n",
" [.15, -.05, .22, .08, .18],\n",
" index=pd.to_datetime([\n",
" '2013-02-25',\n",
" '2013-02-26',\n",
" '2013-02-27',\n",
" '2013-02-28',\n",
" '2013-03-01',\n",
" ]),\n",
" name='Sentiment',\n",
" )\n",
"\n",
" api_key = os.environ.get('ADANOS_API_KEY')\n",
" if api_key:\n",
" params = urlencode({\n",
" 'from': daily_index.min().date().isoformat(),\n",
" 'to': daily_index.max().date().isoformat(),\n",
" })\n",
" request = Request(\n",
" f'https://api.adanos.org/reddit/stocks/v1/stock/{ticker}?{params}',\n",
" headers={'X-API-Key': api_key},\n",
" )\n",
" try:\n",
" with urlopen(request, timeout=10) as response:\n",
" payload = json.load(response)\n",
" rows = payload.get('daily_trend') or []\n",
" sentiment = pd.Series(\n",
" {pd.Timestamp(row['date']): float(row.get('sentiment_score') or 0)\n",
" for row in rows},\n",
" name='Sentiment',\n",
" )\n",
" if not sentiment.empty:\n",
" sample = sentiment\n",
" except (HTTPError, URLError, OSError, ValueError, KeyError):\n",
" pass\n",
"\n",
" return sample.reindex(daily_index).ffill().fillna(0).to_numpy()\n",
"\n",
"\n",
"sentiment_data = GOOG.copy()\n",
"sentiment_data['Sentiment'] = adanos_daily_sentiment(sentiment_data.index, 'GOOG')\n",
"sentiment_data[['Close', 'Sentiment']].tail()"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -470,9 +545,6 @@
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"\n",
"\n",
"def SMA(values, n):\n",
" \"\"\"\n",
" Return simple moving average of `values`, at\n",
Expand Down Expand Up @@ -652,7 +724,7 @@
"source": [
"from backtesting import Backtest\n",
"\n",
"bt = Backtest(GOOG, SmaCross, cash=10_000, commission=.002)\n",
"bt = Backtest(sentiment_data, SmaCross, cash=10_000, commission=.002)\n",
"stats = bt.run()\n",
"stats"
]
Expand Down
71 changes: 67 additions & 4 deletions doc/examples/Quick Start User Guide.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,72 @@

GOOG.tail()

# %% [markdown]
# External data can be joined to the strategy data frame before it is passed to
# `Backtest`. For example, the snippet below adds a daily sentiment column. If
# `ADANOS_API_KEY` is set, it tries to load Reddit stock sentiment from the
# [Adanos Market Sentiment API](https://api.adanos.org/reddit/stocks/v1/);
# otherwise, it uses a tiny sample series so the tutorial stays runnable offline.
#
# The resulting `Sentiment` column is available in a strategy as
# `self.data.Sentiment[-1]`, just like `self.data.Close[-1]`.

# %%
import json
import os
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

import pandas as pd


def adanos_daily_sentiment(index, ticker):
daily_index = pd.DatetimeIndex(index).normalize()
sample = pd.Series(
[.15, -.05, .22, .08, .18],
index=pd.to_datetime([
'2013-02-25',
'2013-02-26',
'2013-02-27',
'2013-02-28',
'2013-03-01',
]),
name='Sentiment',
)

api_key = os.environ.get('ADANOS_API_KEY')
if api_key:
params = urlencode({
'from': daily_index.min().date().isoformat(),
'to': daily_index.max().date().isoformat(),
})
request = Request(
f'https://api.adanos.org/reddit/stocks/v1/stock/{ticker}?{params}',
headers={'X-API-Key': api_key},
)
try:
with urlopen(request, timeout=10) as response:
payload = json.load(response)
rows = payload.get('daily_trend') or []
sentiment = pd.Series(
{pd.Timestamp(row['date']): float(row.get('sentiment_score') or 0)
for row in rows},
name='Sentiment',
)
if not sentiment.empty:
sample = sentiment
except (HTTPError, URLError, OSError, ValueError, KeyError):
pass

return sample.reindex(daily_index).ffill().fillna(0).to_numpy()


sentiment_data = GOOG.copy()
sentiment_data['Sentiment'] = adanos_daily_sentiment(sentiment_data.index, 'GOOG')
sentiment_data[['Close', 'Sentiment']].tail()


# %% [markdown]
# ## Strategy
#
Expand All @@ -58,9 +124,6 @@
# but for this example, we can define a simple helper moving average function ourselves:

# %%
import pandas as pd


def SMA(values, n):
"""
Return simple moving average of `values`, at
Expand Down Expand Up @@ -160,7 +223,7 @@ def next(self):
# %%
from backtesting import Backtest

bt = Backtest(GOOG, SmaCross, cash=10_000, commission=.002)
bt = Backtest(sentiment_data, SmaCross, cash=10_000, commission=.002)
stats = bt.run()
stats

Expand Down