-
Notifications
You must be signed in to change notification settings - Fork 1
/
cli.py
230 lines (191 loc) · 8.41 KB
/
cli.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
'''
Here we could make a cli interface file that works similar to 'manage.py' from django.
Meaning, that since this is in the top level directory, it can run any function, unittest,
and would make a centralized point of entry for any of the programs.
Does anyone have objection for this?
This file could use the package pyCLI to automate the command line interface making process.
maybe using 'click' or 'fire'. They seem a bit more friendly than 'argparse'
'''
from trade_bot.helpers.cli_helper import filter_stocks
import click
import os
import sys
from helpers.cli_helper import run_trade, current_stock_price, start_backtest, environ_checker, \
tests, current_coin_price, list_alpaca_assets, find_and_save_pairs, download_bars_data
from pathlib import Path
import logging
from logging.config import fileConfig
# See https://zetcode.com/python/click/ for a good guide to working with click
@click.group()
def cli():
pass
@cli.command(name="trade")
@click.argument('paper', default='Paper')
def trade(paper: str):
"""
Runs the strategies and submits the order.
Can be paper trading or live.
$ cli.py trade [Paper] ==> Paper Trading
$ cli.py trade Live ==> Live Trading
"""
if paper.lower() == "paper":
logger.info(f"Running paper trade")
run_trade(True)
elif paper.lower() == "live":
click.echo("Do you want to confirm paper trading (y/N)?")
if input("--> ") == "y":
...
logger.info(f"Running live trade")
run_trade(False)
else:
click.echo("Aborting Live Trade")
logger.warning(f"Live Trade aborted by user")
else:
click.echo("Unknown command.")
logger.error(f"Unknown command {paper.lower()} in trade function")
@cli.command(name='backtest')
@click.option('-s', '-start', 'start', default='2020-06-01', type=str, show_default=True)
@click.option('-e', '-end', 'end', default='2021-03-22', type=str, show_default=True)
@click.option('-c', '-cach', 'cash', default=1000.00, type=float, show_default=True)
def backtest(start, end, cash):
"""
Runs a back test using historical data with the algorithms in `trading_manager.py`
"""
logger.info("Starting a backtest")
click.echo(start_backtest(start, end, cash))
# @cli.command(name='wel')
# @click.argument('name', default='guest')
# @click.option('-n', default=1, type=int, show_default=True)
# def welcome(name, n):
@cli.command(name="check_environ")
def check_environment():
"""
Check to see if the necessary environment variables are present
"""
logger.info("Checking environment")
click.echo(environ_checker())
@cli.command(name="stock")
@click.argument("symbol", default='A', type=str)
def get_stock_price(symbol):
"""
Get the last price of `symbol`
"""
print("Retrieving Data")
logger.info(f"Retrieving data for symbol {symbol.upper()}")
p = current_stock_price(symbol.upper())
click.echo(p)
@cli.command(name="coin")
@click.argument('coin', type=str)
@click.option('-c', '-curr', 'currency', default='usd', type=str, show_default=True)
def get_coin_price(coin, currency):
"""
Get the last price of 'coin'
:param coin: The name of the cryptocurrency
:param currency: The name of the comparative currency, i.e. usd, yen, eur, btc, etc...
:return: str
"""
print("Retrieving Data")
logger.info(
f"Retrieving data for symbol {coin.lower()} vs {currency.lower()}")
p = current_coin_price(coin.lower(), currency.lower())
click.echo(p)
@cli.command(name="test")
def run_test():
"""
Runs automated test
"""
print("Interpreter Location: ", sys.executable)
logger.info("Running tests")
tests()
@cli.command(name="list_stocks")
@click.option('--s', '--shortable', 'shortable', is_flag=True, default=False, type=bool, show_default=True)
@click.option('--f', '--fractionable', 'fractionable', is_flag=True, default=False, type=bool, show_default=True)
@click.option('--n', '--names', 'show_names', is_flag=True, default=False, type=bool, show_default=True)
def list_stocks(shortable: bool, fractionable: bool, show_names: bool):
"""
Lists Alpaca Assets. Can be filtered in a variety of ways. '--s' shows all the shortable assets.
'--f' shows all assets which can be bought in fractions (not whole shares).
'--n' shows assets symbols with the name of the company.
"""
logger.info("Getting lists of stocks")
click.echo(f" List of Alpaca Assets"
f"{' that are' if shortable or fractionable else ''} "
f"{'shortable' if shortable else ''}"
f"{'fractionable' if fractionable and not shortable else ''}"
f"{' and fractionable:' if fractionable and shortable else ':'}\n")
resp = list_alpaca_assets(
shortable=shortable, fractionable=fractionable, show_names=show_names)
click.echo(f"{resp}\n\n ++ Total: {len(resp)} ++")
@cli.command(name="calc_pairs")
def calc_pairs():
# TODO make this a estimated time
click.echo("Starting up Pairs finding algorithm. This may take a while")
find_and_save_pairs()
@cli.command(name="download_data")
@click.option('-t', '-timespan', 'timespan', default=180, type=int, show_default=True)
@click.option('-s', '-syms', 'symbols', default=None, type=str, show_default=True)
@click.option('--r', '--replace', 'replace_old_data', is_flag=True, default=False, type=bool, show_default=True)
def download_asset_data(timespan: int, symbols: str, replace_old_data: bool) -> None:
"""
Downloads market data, in form of daily bars aggregates, from Alpaca. Necessary for back-testing.
:param timespan: The number of days of data you want. I.E 365 would be the last year of data
:param symbols: A single stock symbol ('TSLA') or a list separated by commas ('TSLA,MSFT,AAPL')
:param replace_old_data: Replace any existing data found in trade_bot/data/bars/
:return: None
"""
click.echo(
"Starting Download into trade_bot/data/bars/ . This will take a while.") # TODO make this a estimated time
if symbols is None:
click.echo(
"Do you want to download all tradeable stock from alpaca? There are about 1400 Stocks.")
ans = input("(y/N) -->")
if ans != 'y':
exit()
elif not isinstance(symbols, str):
click.echo(
"Invalid symbol arg. Please type '-syms TSLA' for one stock or '-syms TSLA,AAPL,MSFT' for multiple")
elif ',' in symbols:
symbols = symbols.split(',')
symbols = list(map(lambda x: x.upper(), symbols))
elif ',' not in symbols:
symbols = [symbols.upper()]
else:
exit()
download_bars_data(timespan=timespan, symbols=symbols,
replace_old_data=replace_old_data)
@cli.command(name="filter")
@click.option('-t', '-timespan', 'timespan', default=180, type=int, show_default=True)
@click.option('-h', '-max', 'maximum', default=50, type=float, show_default=True)
@click.option('-l', '-min', 'minimum', default=0, type=float, show_default=True)
def filter_stock_pipeline(timespan: int, maximum: float, minimum: float) -> None:
"""
Checks the available stocks on alpaca that meet a certain requirement
"""
click.echo(
(f"Starting pipeline filter process . This will take a while."
"\nFinding all stock that have a max of {maximum} and min of {minimum} within the last {timespan} days"))
filter_stocks(timespan=timespan, minimum=maximum, maximum=minimum)
'''
TODO commands to make:
- pull data: gets the most recent data for the last N days for previously defined stocks and stores them in csv files
- run: pulls data (if necessary), runs some strategy, then asks for confirmation before submitting trades.
'''
if __name__ == '__main__':
log_format = (
'[%(asctime)s] %(levelname)-8s %(name)-12s %(message)s')
currDir = Path(__file__).parent.absolute()
if 'logs' not in [file for file in os.listdir(currDir) if os.path.isdir(file)]:
os.mkdir('logs')
config_file_path = currDir / 'logs' / 'tradeBot.log'
logging.basicConfig(
level=logging.WARNING,
format=log_format,
filename=config_file_path,
)
logger = logging.getLogger(__name__)
cli()
with open(config_file_path, 'r') as f:
lines = f.readlines()
if len(lines) > 1000:
click.echo(f"The length of the log file ,{config_file_path}, is longer than 1000 lines. "
f"Consider purging the file.")