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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ templates/*.html
celerybeat-schedule
.dbdata/*
*.log

1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
15 changes: 8 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
FROM python:3.7
FROM python:3.11

WORKDIR /usr/src/app

ENV PYTHONPATH /usr/src/app

RUN apt update
RUN apt upgrade -y
RUN apt install build-essential -y
RUN apt update && \
apt upgrade -y && \
apt install build-essential -y

RUN python -m pip install --upgrade pip

COPY . .

# Build requirements for facebook prophet
RUN python -m pip install pystan==2.19.1.1 numpy==1.21.2
# Install the rest of the requirements
RUN python -m pip install -r requirements.txt -r requirements-dev.txt
RUN pip install pipenv && \
pipenv install --system --deploy --ignore-pipfile && \
pipenv clean
47 changes: 47 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
requests = ">=2.31.0"
finviz = ">=1.4.6"
pystan = ">=3.7.0"
numpy = ">=1.25.1"
prophet = ">=1.1.4"
plotly = ">=5.15.0"
pandas = ">=2.0.3"
pandas-datareader = ">=0.10.0"
matplotlib = ">=3.7.2"
cython = ">=0.29.36"
pybind11 = ">=2.11.0"
lunarcalendar = ">=0.0.9"
holidays = ">=0.28"
jupyterlab = ">=4.0.3"
pytrends = ">=4.9.2"
nest-asyncio = ">=1.5.6"
tabulate = ">=0.9.0"
slackbot = ">=1.0.5"
psutil = ">=5.9.5"
boto3 = ">=1.28.3"
redis = ">=4.6.0"
alpha-vantage = ">=2.3.1"
sqlalchemy-mixins = ">=2.0.3"
sqlalchemy = ">=2.0.19"
mysqlclient = ">=2.2.0"
pyyaml = ">=6.0"
slack-sdk = ">=3.21.3"
yfinance = ">=0.2.24"
hy = ">=0.27.0"

[dev-packages]
isort = "*"
flake8 = "*"
bandit = "*"
black = "*"
mypy = "*"
lock = "*"


[requires]
python_version = "3.11"
2,926 changes: 2,926 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions algo_bot/cache.hy
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(import [algo_bot.settings [AlgoBotSettings get_settings]])
(import [algo_bot [utils]])
(import algo_bot.settings [AlgoBotSettings get_settings])
(import algo_bot [utils])
(import redis)
(import pickle)

Expand All @@ -24,6 +24,6 @@
; Weird None check for dataframe not a problem in python with `data is not None` shurg.
(if (= (type data) (type None))
(do
(setv data (func #*args #**kwargs))
(setv data (func #* args #** kwargs))
(write :key cache-key :value data)))
data)))
1 change: 0 additions & 1 deletion algo_bot/charting.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import matplotlib.pyplot as plt
import plotly.graph_objects as go
from algo_bot import utils


def candlestick_plot(df):
Expand Down
24 changes: 11 additions & 13 deletions algo_bot/clients/finviz_client.hy
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
(import asyncio nest_asyncio finviz)
(import [algo-bot.utils [event-loop]])
(import [pandas :as pd])
(import [finviz.screener [Screener]])
(import algo-bot.utils [event-loop])
(import pandas :as pd)
(import finviz.screener [Screener])


(with-decorator event-loop
(defn screener [&optional [filters ["exch_nasd" "sh_avgvol_o200" "sh_price_u5" "ta_change_u10" "geo_usa"]] [order "-volume"]]
(setv stock-list (Screener :filters filters :order order))
(setv df (pd.DataFrame stock-list.data))
(df.set_index "Ticker" :drop False)
df))
(defn [event-loop] screener [&optional [filters ["exch_nasd" "sh_avgvol_o200" "sh_price_u5" "ta_change_u10" "geo_usa"]] [order "-volume"]]
(setv stock-list (Screener :filters filters :order order))
(setv df (pd.DataFrame stock-list.data))
(df.set_index "Ticker" :drop False)
df)

; Finviz table function generator
(defn finviz-table [table]
(with-decorator event-loop
(fn [ticker]
(setv s (Screener :table table :tickers [ticker]))
(pd.DataFrame s.data))))
(fn [event-loop] [ticker]
(setv s (Screener :table table :tickers [ticker]))
(pd.DataFrame s.data)))

(setv finviz-performance (finviz-table "Performance"))
(setv finviz-overview (finviz-table "Overview"))
Expand Down
2 changes: 1 addition & 1 deletion algo_bot/clients/trends.hy
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(import [pytrends.request [TrendReq]])
(import pytrends.request [TrendReq])

(defn interest-over-time [kw-list]
(do
Expand Down
13 changes: 0 additions & 13 deletions algo_bot/commands/all.py
Original file line number Diff line number Diff line change
@@ -1,13 +0,0 @@
from algo_bot.commands import (
finviz,
help,
news,
positioning,
screener,
strategies,
technical,
ticker,
user,
watchlist,
simulation,
)
8 changes: 4 additions & 4 deletions algo_bot/commands/finviz.hy
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
(import re)
(import [pandas :as pd])
(import [slackbot.bot [respond-to]])
(import [algo-bot [utils]])
(import [algo-bot.clients [finviz-client]])
(import pandas :as pd)
(import slackbot.bot [respond-to])
(import algo-bot [utils])
(import algo-bot.clients [finviz-client])

(with-decorator (respond-to "^screener (.*)" re.IGNORECASE)
(defn screener [message filters]
Expand Down
6 changes: 3 additions & 3 deletions algo_bot/commands/help.hy
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(import re)
(import [slackbot.bot [respond-to]])
(import [algo-bot [utils]])
(import [algo-bot.slackbot-settings [BOT_ENV]])
(import slackbot.bot [respond-to])
(import algo-bot [utils])
(import algo-bot.slackbot-settings [BOT_ENV])

(defn _respond [message response]
(utils.reply-webapi message (utils.wrap-ticks response)))
Expand Down
40 changes: 19 additions & 21 deletions algo_bot/commands/news.hy
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
(import re)
(import [pandas :as pd])
(import [slackbot.bot [respond-to]])
(import [algo-bot [charting utils]])
(import [algo-bot.clients [finviz-client trends]])
(import pandas :as pd)
(import slackbot.bot [respond-to])
(import algo-bot [charting utils])
(import algo-bot.clients [finviz-client trends])

(with-decorator (respond-to "^news (.*)" re.IGNORECASE)
(defn news [message ticker]
(for [url (get-news-urls ticker)]
(utils.reply-webapi message f"\n{url}"))))
(defn [(respond-to "^news (.*)" re.IGNORECASE)] news [message ticker]
(for [url (get-news-urls ticker)]
(utils.reply-webapi message f"\n{url}")))


(with-decorator (respond-to "^interest-over-time (.*)" re.IGNORECASE)
(defn interest-over-time [message kws]
(utils.processing message)
(setv kw-list (kws.split ","))
(setv interest (trends.interest-over-time kw-list))
(setv df (pd.DataFrame interest))
(setv fig (charting.line-chart-trends df))
(setv filename (+ (.join "_" (kws.split ",")) "_interest_over_time.html"))
(utils.store-graph fig filename)
(utils.send-webapi message
""
:title f"Interest Over Time: {kws}"
:title-link (utils.html-url filename))))
(defn [(respond-to "^interest-over-time (.*)" re.IGNORECASE)] interest-over-time [message kws]
(utils.processing message)
(setv kw-list (kws.split ","))
(setv interest (trends.interest-over-time kw-list))
(setv df (pd.DataFrame interest))
(setv fig (charting.line-chart-trends df))
(setv filename (+ (.join "_" (kws.split ",")) "_interest_over_time.html"))
(utils.store-graph fig filename)
(utils.send-webapi message
""
:title f"Interest Over Time: {kws}"
:title-link (utils.html-url filename)))

(defn get-news-urls [ticker]
(setv urls [])
Expand Down
95 changes: 7 additions & 88 deletions algo_bot/commands/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def run_turtle_simulation(message, params):
df = dailyAdjustedRaw.sort_values(by="date", ascending=False).reset_index().rename(columns=COLUMN_RENAME).drop(["7. dividend amount", "8. split coefficient"], axis=1, inplace=False)
df = df.head(20).sort_values(by="Date", ascending=True).reset_index().drop(["index"], axis=1, inplace=False)
signals = turtleSignals(df)
messages, states_buy, states_sell, total_gains, invest = turtle_buy_stock(df.Close, signals['signal'], df, initial_money=params.balance, max_buy=params.limit, max_sell=params.limit)
messages, states_buy, states_sell, total_gains, invest = buy_stock(df.Close, signals['signal'], df, initial_money=params.balance, max_buy=params.limit, max_sell=params.limit)
# Reply the simulation
utils.reply_webapi(message, utils.wrap_ticks("\n".join(messages)))
# Send the chart
Expand Down Expand Up @@ -49,7 +49,7 @@ def run_moving_average_simulation(message, params):
df = dailyAdjustedRaw.sort_values(by="date", ascending=False).reset_index().rename(columns=COLUMN_RENAME).drop(["7. dividend amount", "8. split coefficient"], axis=1, inplace=False)
df = df.head(90).sort_values(by="Date", ascending=True).reset_index().drop(["index"], axis=1, inplace=False)
signals = movingAverageSignals(df)
messages, states_buy, states_sell, total_gains, invest = moving_average_buy_stock(df.Close, signals['positions'], df, initial_money=params.balance, max_buy=params.limit, max_sell=params.limit)
messages, states_buy, states_sell, total_gains, invest = buy_stock(df.Close, signals['positions'], df, initial_money=params.balance, max_buy=params.limit, max_sell=params.limit)
# Reply the simulation
utils.reply_webapi(message, utils.wrap_ticks("\n".join(messages)))
# Send the chart
Expand All @@ -75,95 +75,15 @@ def movingAverageSignals(df):
long_window = int(0.05 * len(df))
signals = pd.DataFrame(index=df.index)
signals['signal'] = 0.0

signals['short_ma'] = df['Close'].rolling(window=short_window, min_periods=1, center=False).mean()
signals['long_ma'] = df['Close'].rolling(window=long_window, min_periods=1, center=False).mean()

signals['signal'][short_window:] = np.where(signals['short_ma'][short_window:]
> signals['long_ma'][short_window:], 1.0, 0.0)
signals['positions'] = signals['signal'].diff()

return signals


def moving_average_buy_stock(
real_movement,
signal,
df,
initial_money=10000,
max_buy=1,
max_sell=1,
):
"""
real_movement = actual movement in the real world
delay = how much interval you want to delay to change our decision from buy to sell, vice versa
initial_state = 1 is buy, 0 is sell
initial_money = 1000, ignore what kind of currency
max_buy = max quantity for share to buy
max_sell = max quantity for share to sell
"""
starting_money = initial_money
states_sell = []
states_buy = []
current_inventory = 0
messages = []

def buy(i, initial_money, current_inventory):
shares = initial_money // real_movement[i]
if shares < 1:
messages.append(
'Date: %s day %d: total balances %f, not enough money to buy a unit price %f'
% (df.Date[i], i, initial_money, real_movement[i])
)
else:
if shares > max_buy:
buy_units = max_buy
else:
buy_units = shares
initial_money -= buy_units * real_movement[i]
current_inventory += buy_units
messages.append(
'Date: %s day %d: buy %d units at price %f, total balance %f'
% (df.Date[i], i, buy_units, buy_units * real_movement[i], initial_money)
)
states_buy.append(0)
return initial_money, current_inventory

for i in range(real_movement.shape[0] - int(0.025 * len(df))):
state = signal[i]
if state == 1:
initial_money, current_inventory = buy(
i, initial_money, current_inventory
)
states_buy.append(i)
elif state == -1:
if current_inventory == 0:
messages.append('Date: %s day %d: cannot sell anything, inventory 0' % (df.Date[i], i))
else:
if current_inventory > max_sell:
sell_units = max_sell
else:
sell_units = current_inventory
current_inventory -= sell_units
total_sell = sell_units * real_movement[i]
initial_money += total_sell
try:
invest = (
(real_movement[i] - real_movement[states_buy[-1]])
/ real_movement[states_buy[-1]]
) * 100
except:
invest = 0
messages.append(
'Date: %s day %d, sell %d units at price %f, investment %f %%, total balance %f,'
% (df.Date[i], i, sell_units, total_sell, invest, initial_money)
)
states_sell.append(i)
invest = ((initial_money - starting_money) / starting_money) * 100
total_gains = initial_money - starting_money
return messages, states_buy, states_sell, total_gains, invest


def turtleSignals(df):
count = int(np.ceil(len(df) * 0.1))
signals = pd.DataFrame(index=df.index)
Expand All @@ -176,7 +96,7 @@ def turtleSignals(df):
return signals


def turtle_buy_stock(
def buy_stock(
real_movement,
signal,
df,
Expand All @@ -202,7 +122,7 @@ def buy(i, initial_money, current_inventory):
shares = initial_money // real_movement[i]
if shares < 1:
messages.append(
'DATE: %s day %d: total balances %f, not enough money to buy a unit price %f'
'Date: %s day %d: total balances %f, not enough money to buy a unit price %f'
% (df.Date[i], i, initial_money, real_movement[i])
)
else:
Expand All @@ -213,7 +133,7 @@ def buy(i, initial_money, current_inventory):
initial_money -= buy_units * real_movement[i]
current_inventory += buy_units
messages.append(
'DATE: %s day %d: buy %d units at price %f, total balance %f'
'Date: %s day %d: buy %d units at price %f, total balance %f'
% (df.Date[i], i, buy_units, buy_units * real_movement[i], initial_money)
)
states_buy.append(0)
Expand All @@ -228,7 +148,7 @@ def buy(i, initial_money, current_inventory):
states_buy.append(i)
elif state == -1:
if current_inventory == 0:
messages.append('DATE: %s day %d: cannot sell anything, inventory 0' % (df.Date[i], i))
messages.append('Date: %s day %d: cannot sell anything, inventory 0' % (df.Date[i], i))
else:
if current_inventory > max_sell:
sell_units = max_sell
Expand All @@ -245,11 +165,10 @@ def buy(i, initial_money, current_inventory):
except:
invest = 0
messages.append(
'DATE: %s day %d, sell %d units at price %f, investment %f %%, total balance %f,'
'Date: %s day %d, sell %d units at price %f, investment %f %%, total balance %f,'
% (df.Date[i], i, sell_units, total_sell, invest, initial_money)
)
states_sell.append(i)

invest = ((initial_money - starting_money) / starting_money) * 100
total_gains = initial_money - starting_money
return messages, states_buy, states_sell, total_gains, invest
Loading