Daily Risk-Asset Trade Signals
Here’s how I used Kaggle’s schedule feature to build a daily cryptocurrency trade signals webpage.
See the program here: Daily Binance Trade Signals
Introduction
There’s a lot to unpack here, but the essentials are: ccxt, pandas_ta, & Kaggle’s trigger feature. CCXT (https://github.com/ccxt/ccxt) is an exchange API wrapper that supports a stupendous list of brokerages and is incredibly easy to use. Pandas Technical Analysis Library (https://github.com/twopirllc/pandas-ta) serves as our pool of indicators & forecasting algorithms. And the schedule feature within Kaggle’s notebook kernel enables for daily, weekly, or monthly executions of code will be our vehicle for daily automation.
Install & Import Packages
# login to binance
from IPython.display import clear_output
!pip install ccxt
!pip install schedule
!pip install pandas_ta
import ccxt,schedule,warnings,time,ast
warnings.filterwarnings('ignore')
import matplotlib.pyplot as plt
from dateutil.tz import tzlocal
from rich import print, pretty
from datetime import datetime
from random import randint
from random import seed
import pandas_ta as ta
import pandas as pd
import numpy as np
pretty.install()
Login To Binance.us
The following is respective of ccxt’s login protocol for binance.us. (https://docs.ccxt.com/en/latest/exchange-markets.html)
# Instructions on retrieving your own API key pair:
# https://youtu.be/Q4Lt1KulkOQ
ccxt.binanceus({ 'options':{ 'adjustForTimeDifference':True}})
exchange = ccxt.binanceus({
"apiKey": Binance_key,
"secret": Binance_secret,
'enableRateLimit': True})
Calculate Indicators
What I find most valuable about this model is that I can easily customize & fine-tune my buy/sell trade signal parameters. I decided to use the following technical indicators & forecasting algorithms in order to “recommend” (i.e. discern against) going long or going short: Kalman Filter Forecast Model, 14-Day Lower/Upper Bollinger Bands, 9 & 26-Day Senkou Span Ichimoku, Archer Moving Averages Trends (AMAT), & Relative Strength Index (RSI). Although the 14/91/125-Day Exponential Moving Averages (EMA) & Choppiness Index were calculated, they are not currently used as decision parameters for the output below
# indicators + trade signals
def calculate_indicator(symbol):
bars = exchange.fetch_ohlcv(symbol, timeframe='1d', limit=300)
df = pd.DataFrame(bars[:-1], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms').dt.tz_localize(None)
close = df['close'][len(df)-1]
low = df['low'][len(df)-1]
# Construct a Kalman filter
kf = KalmanFilter(transition_matrices = [1], # The value for At. It is a random walk so is set to 1.0
observation_matrices = [1], # The value for Ht.
initial_state_mean = 0, # Any initial value. It will converge to the true state value.
initial_state_covariance = 1, # Sigma value for the Qt in Equation (1) the Gaussian distribution
observation_covariance=1, # Sigma value for the Rt in Equation (2) the Gaussian distribution
transition_covariance=.01) # A small turbulence in the random walk parameter 1.0
# Get the Kalman smoothing
state_means, _ = kf.filter(df['close'].values)
# Call it kf_mean
df['kf_mean'] = np.array(state_means)
kalman = df.kf_mean[len(df)-1]
aboveKalman = low > kalman
# exponential moving averages
ema_14 = df.ta.ema(14, append=True)[-1:].reset_index(drop=True)[0]
ema_91 = df.ta.ema(91, append=True)[-1:].reset_index(drop=True)[0]
ema_125 = df.ta.ema(125, append=True)[-1:].reset_index(drop=True)[0]
ema_crossover = ema_14 > ema_91
# lower/upper 14-day bollinger bands for mean reversion
bbl_14 = df.ta.bbands(length=14, append=True)[['BBL_14_2.0']].tail(1).values[0][0]
bbu_14 = df.ta.bbands(length=14, append=True)[['BBU_14_2.0']].tail(1).values[0][0]
bband_buy = close < bbl_14
bband_sell = close > bbu_14
# ichimoku 9 & 26-day forecasts
# https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.trend.IchimokuIndicator
isa_9 = df.ta.ichimoku()[1]['ISA_9'].tail(1).values[0] # help(ta.ichimoku)
isb_26 = df.ta.ichimoku()[1]['ISB_26'].tail(1).values[0]
# archer ma
# https://github.com/twopirllc/pandas-ta#general
amat = (df.ta.amat()['AMATe_LR_8_21_2'].tail(1).values[0] == 1)
# rsi
rsi = df.ta.rsi()[len(df)-1]
rsi_buy = rsi < 30
rsi_sell = rsi > 70
# choppy
# https://github.com/twopirllc/pandas-ta#trend-18
try:
chop = "{:.2f}".format(df.ta.chop()[len(df.ta.chop())-1])
except RunTimeWarning:
chop = 0
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
# signal
#buy = (close < isa_9) & (close < isb_26) & amat & rsi_buy & bband_buy & aboveKalman
buy = amat & ema_crossover & aboveKalman
#sell = (close > isa_9) & (close > isb_26) & ~amat & rsi_sell & bband_sell & ~aboveKalman
sell = ~amat & ~ema_crossover & ~aboveKalman
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
return df, symbol, close, isa_9, isb_26, chop, rsi, amat, ema_crossover, buy, sell, aboveKalman
Plotter
Here’s a simple plot model to consider the EMAs running against our Close Price within the the given timeframe. In this case, we set the OHLC limit to collect 300 days worth of data.
# plotter
def plot(symbol): # sample
fig, ax = plt.subplots(figsize=(20, 5))
df = calculate_indicator(symbol)[0]
ax.plot(df.close, color = 'black', label = 'closePrice')
ax.plot(df.EMA_14, color = 'orange', label = f'EMA_14')
ax.plot(df.EMA_91, color = 'red', label = f'EMA_91')
ax.plot(df.EMA_125, color = 'purple', label = f'EMA_125')
plt.title(f"{symbol}")
ax.legend(loc = 'upper left')
return plt.show()
Data Preparation
The cell below will collect all binance-tradeable symbols, then iterate calculate_indicator(symbol) against each one in order to produce the final output as a dataframe that includes all the indicators & forecasts we need.
# load symbols + initiate dataframe
results = []
symbols = exchange.fetchTickers().keys()
# iteration
for symbol in symbols:
try:
output = calculate_indicator(symbol)
results.append({'Ticker':output[1],
'Date':today,
'Close':output[2],
'Ichimoku 9-day Forecast':output[3],
'Ichimoku 26-day Forecast':output[4],
'Choppiness (%)':output[5],
'RSI':output[6],
'Archer MA Trends':output[7],
'EMA Crossover':output[8],
'Buy':output[9],
'Sell':output[10]
})
except:
pass
# frame & clean
results = pd.DataFrame(results)
results['Tick'] = [v.split('/')[0] for i,v in results.Ticker.items()]
results['Deno'] = [v.split('/')[1] for i,v in results.Ticker.items()]
# set pandas display to respect number of symbols in results
size = len(results)
pd.set_option('display.max_rows', size)
Top & Bottom 10s
# top 10 recommended buys
top10 = results.sort_values(['Buy','EMA Crossover','Archer MA Trends'],ascending=False).sort_values(['RSI'],ascending=True).drop_duplicates('Tick').head(10).reset_index(drop=True)
# top 10 recommended sells
bottom10 = results.sort_values(['Sell','Archer MA Trends','RSI'],ascending=False).drop_duplicates('Tick').head(10).reset_index(drop=True)
print("Prepped.")
Notice
If any of the results in our top 10 are recommended Buys, I’d want to see those first. But if every result came out False, then there are no recommended long positions.
if any(results.Buy == True):
print("These are recommended long positions, then sorted by crossover, trend, & rsi values.")
if all(results.Buy == False):
print("No recommended long positions. Showing results sorted by crossover, trend, & rsi values.")
I do the same concept for Sells:
if any(results.Sell == True):
print("These are recommended short positions, then sorted by crossover, trend, & rsi values.")
if all(results.Sell == False):
print("No recommended short positions. Showing results sorted by trend & rsi values.")
Recommendations
# plots for top3
[plot(symbol) for i,symbol in top3.Ticker.items()]
Top 3 Buys
# plots for bottom3
[plot(symbol) for i,symbol in bottom3.Ticker.items()]
Bottom 3 Sells
Scheduler
Located at the far right of Kaggle’s notebook panel > Settings, you’ll find a trigger feature that allows for daily, weekly, or monthly executions of the kernel. This provides me with all the data I need in order to get a pulse on current market trends.
In all honesty, I’m more excited about the fact that I have something to look at and revel everyday. Only to find fallacies, ways to be more efficient, or simple changes need to be made so as to give me something fun to do on some idle Saturday morning.
Key Takeaways
With that said, it’s important to note (even for myself) that the signals provided will be completely dependent on the conditions I set within the calculate_indicator() protocol. This gives me frequent opportunities to better tune my forecasting models, clean out some inefficiencies, or add more indicators as I continue to learn about the fluctuations of the crypto market.
On-going & Future Efforts
It would be great to use Kaggle’s scheduler feature in order to place actual trades. Implementing more sophisticated indicators, robust forecasts, safe limit orders, & stop-loss thresholds might someday serve as a comfortable vehicle for automated trading. This protocol can be written to run against other exchanges, of course, like TD Ameritrade & Robinhood which both have open source APIs. This project will be on-going and I expect to continue tuning the notebook’s recommendations. In other words, it will never be perfect.
https://linkedin.com/in/dontadaya
Thank you!
Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing
Also, Read
- Best Cardano Wallets | Bingbon Copy Trading
- Best P2P Crypto Exchanges in India | Shiba Inu Wallets
- Top 8 Crypto Affiliate Programs | eToro vs Coinbase
- Best Ethereum Wallets | Cryptocurrency Bots on Telegram
- Best Exchanges to Trade Leveraged Tokens | Buy Floki
- 3Commas vs. Pionex vs. Cryptohopper | Bingbon Review