Skip to content

Commit

Permalink
add momentum indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Škoda committed Sep 15, 2023
1 parent 9049e61 commit 8314b54
Show file tree
Hide file tree
Showing 2 changed files with 280 additions and 1 deletion.
279 changes: 279 additions & 0 deletions momentum_indicator.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"float:right; width:100px; text-align: center; margin: 10px;\">\n",
"<img src=\"https://crypto-lake.com/assets/img/lake.png\" alt=\"Lake\"/>\n",
"</div>\n",
"\n",
"# Momentum indicator\n",
"\n",
"Backtest of a simple momentum indicator on 1m candle data. The logic was proposed by @BeatzXBT on twitter.\n",
"\n",
"We use [crypto-lake.com](https://crypto-lake.com/#data) sample/free market data, FTRB-USDT market on Ascendex.\n",
"\n",
"Quick links:\n",
"- [edit this notebook online](https://mybinder.org/v2/gh/crypto-lake/analysis-sharing/main?filepath=momentum_indicator.ipynb) using Binder\n",
"- [follow our activity on twitter](https://twitter.com/intent/user?screen_name=crypto_lake_com)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import datetime\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import cufflinks as cf\n",
"import plotly.express as px\n",
"import statsmodels.api as sm\n",
"\n",
"import lakeapi\n",
"\n",
"cf.go_offline()\n",
"\n",
"# This runs on the paid data only at the moment\n",
"# lakeapi.use_sample_data(anonymous_access=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Parameters\n",
"symbol = 'ADA-USDT'\n",
"exchange = 'BINANCE'\n",
"\n",
"# Free sample data contain subset of the below time period\n",
"start = datetime.datetime(2023, 8, 1)\n",
"end = datetime.datetime(2023, 9, 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('Loading candles')\n",
"candles = lakeapi.load_data(\n",
" table = 'candles',\n",
" start = start,\n",
" end = end,\n",
" symbols = [symbol],\n",
" exchanges = [exchange],\n",
" drop_partition_cols = True,\n",
").sort_values('origin_time')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Minimize the data\n",
"df = candles[['origin_time', 'close']]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Logic"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"depths = np.asarray([200, 100, 50, 25, 10]) # in minutes\n",
"n = len(depths)\n",
"# depths *= 10 # try a longer time frame?\n",
"\n",
"# Signal logic\n",
"for depth in depths:\n",
"\tdf[f'ema_{depth}'] = np.log(df['close'] / df['close'].ewm(span=depth).mean()) * 100\n",
"df['trend_val'] = df[[f'ema_{depth}' for depth in depths]].ewm(span=n, axis = 1).mean().sum(axis=1)\n",
"df['pure_sum'] = df[[f'ema_{depth}' for depth in depths]].sum(axis=1)\n",
"\n",
"\n",
"# Future return for evaluation\n",
"df['future_return'] = df['close'].pct_change(10).shift(-10) * 100\n",
"df = df.dropna()\n",
"\n",
"df[::3600].head(20)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Evaluation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.set_index('origin_time')[['close', 'trend_val']].iplot(secondary_y='trend_val')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# df.sample(1000).iplot(kind='scatter', x='trend_val', y='future_return', mode='markers', size=3)\n",
"px.scatter(df.sample(10_000), x='trend_val', y='future_return', trendline='ols')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Optional: install statsmodels for the next cell to run\n",
"!pip install -q statsmodels"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Numerical linreg fit statistics\n",
"mod = sm.OLS(df['trend_val'], df['future_return'])\n",
"res = mod.fit()\n",
"print(res.summary())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusion\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It seems the trend value has a slight negative correlation with future trend on our data. I don't consider the correlation significant enough for this signal to be used in real trading.\n",
"\n",
"I also tried different settings, mostly longer ewm spans, but the results were similar."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"### For reference: the original logic"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# This seems to be equivalent to pandas Series.ewm(span=window).mean()\n",
"def ema(arr_in: np.ndarray, window: int) -> np.ndarray:\n",
" \"\"\"\n",
" Hyper-fast EMA implementation\n",
" \"\"\"\n",
" \n",
" n = arr_in.shape[0]\n",
" ewma = np.empty(n, dtype=float)\n",
" alpha = 2 / float(window + 1)\n",
" w = 1\n",
" ewma_old = arr_in[0]\n",
" ewma[0] = ewma_old\n",
"\n",
" for i in range(1, n):\n",
" w += (1-alpha)**i\n",
" ewma_old = ewma_old*(1-alpha) + arr_in[i]\n",
" ewma[i] = ewma_old / w\n",
" \n",
" return ewma\n",
"\n",
"# This is replicated in the cell below in a batch/more-efficient way\n",
"def trend_feature(klines: np.ndarray, lengths: np.ndarray) -> float:\n",
" \"\"\"\n",
" Make sure lengths are fed in from longest to shortest\n",
" \"\"\"\n",
"\n",
" closes = klines[:, 4]\n",
" curr_price = closes[-1]\n",
" n = len(lengths)\n",
" vals = np.empty(n, dtype=float)\n",
"\n",
" for i in range(n):\n",
" length = lengths[i]\n",
" ema_val = ema(closes[-length:], length)[-1]\n",
"\n",
" # Safety check\n",
" if ema_val == 0:\n",
" vals[i] = vals[i-1] \n",
"\n",
" else:\n",
" vals[i] = np.log(curr_price / ema_val) * 100\n",
"\n",
" trend_val = ema(vals, n)\n",
"\n",
" return np.sum(trend_val)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.6 ('env': venv)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "7291192e6945a3c8ed0f956f4124ad6de62ed7ea9152f7ae8e29637075883be3"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ setuptools==59.6.0
numpy==1.23.4
pandas==1.5.1
cufflinks==0.17.3
lakeapi==0.5.0
lakeapi==0.6.3
notebook==6.5.4
scipy==1.11.1
kaleido==0.2.1
Expand Down

0 comments on commit 8314b54

Please sign in to comment.