Skip to content

Commit 4403ac0

Browse files
committed
Add init app command
1 parent 5601aa9 commit 4403ac0

File tree

9 files changed

+326
-1
lines changed

9 files changed

+326
-1
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import click
2+
from .intialize_app import command \
3+
as initialize_app_command
4+
5+
6+
@click.group()
7+
def cli():
8+
"""CLI for Investing Algorithm Framework"""
9+
pass
10+
11+
@click.command()
12+
@click.option('--web', is_flag=True, help="Initialize with web UI support")
13+
@click.option(
14+
'--path', default=None, help="Path to directory to initialize the app in"
15+
)
16+
def init(web, path):
17+
"""
18+
Command-line tool for creating an app skeleton.
19+
20+
Args:
21+
web (bool): Flag to create an app skeleton with web UI support.
22+
path (str): Path to directory to initialize the app in
23+
24+
Returns:
25+
None
26+
"""
27+
initialize_app_command(path=path, web=web)
28+
29+
# Add the init command to the CLI group
30+
cli.add_command(init)
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import os
2+
3+
4+
def create_directory(directory_path):
5+
"""
6+
Creates a new directory.
7+
8+
Args:
9+
directory_path (str): The path to the directory to create.
10+
11+
Returns:
12+
None
13+
"""
14+
15+
if not os.path.exists(directory_path):
16+
os.makedirs(directory_path)
17+
18+
19+
def create_file(file_path):
20+
"""
21+
Creates a new file.
22+
23+
Args:
24+
file_path (str): The path to the file to create.
25+
26+
Returns:
27+
None
28+
"""
29+
30+
if not os.path.exists(file_path):
31+
with open(file_path, "w") as file:
32+
file.write("")
33+
34+
35+
def create_file_from_template(template_path, output_path):
36+
"""
37+
Creates a new file by replacing placeholders in a template file.
38+
39+
Args:
40+
template_path (str): The path to the template file.
41+
output_path (str): The path to the output file.
42+
replacements (dict): A dictionary of placeholder keys and
43+
their replacements.
44+
45+
Returns:
46+
None
47+
"""
48+
49+
# Check if output path already exists
50+
if not os.path.exists(output_path):
51+
with open(template_path, "r") as file:
52+
template = file.read()
53+
54+
with open(output_path, "w") as file:
55+
file.write(template)
56+
57+
58+
59+
60+
def command(path = None, web = False):
61+
"""
62+
Command-line tool for creating an azure function enabled app skeleton.
63+
64+
Args:
65+
add_app_template (bool): Flag to create an app skeleton.
66+
add_requirements_template (bool): Flag to create a
67+
requirements template.
68+
69+
Returns:
70+
None
71+
"""
72+
"""
73+
Function to create an azure function app skeleton.
74+
75+
Args:
76+
create_app_skeleton (bool): Flag to create an app skeleton.
77+
78+
Returns:
79+
None
80+
"""
81+
82+
if path == None:
83+
path = os.getcwd()
84+
else:
85+
# check if directory exists
86+
if not os.path.exists(path) or not os.path.isdir(path):
87+
print(f"Directory {path} does not exist.")
88+
return
89+
90+
# Get the path of this script (command.py)
91+
current_script_path = os.path.abspath(__file__)
92+
93+
# Construct the path to the template file
94+
template_app_file_path = os.path.join(
95+
os.path.dirname(current_script_path),
96+
"templates",
97+
"app.py.template"
98+
)
99+
requirements_path = os.path.join(
100+
os.path.dirname(current_script_path),
101+
"templates",
102+
"requirements.txt.template"
103+
)
104+
strategy_template_path = os.path.join(
105+
os.path.dirname(current_script_path),
106+
"templates",
107+
"strategy.py.template"
108+
)
109+
run_backtest_template_path = os.path.join(
110+
os.path.dirname(current_script_path),
111+
"templates",
112+
"run_backtest.py.template"
113+
)
114+
market_data_providers_template_path = os.path.join(
115+
os.path.dirname(current_script_path),
116+
"templates",
117+
"market_data_providers.py.template"
118+
)
119+
120+
create_file(os.path.join(path, "__init__.py"))
121+
create_file_from_template(
122+
template_app_file_path,
123+
os.path.join(path, "app.py")
124+
)
125+
create_file_from_template(
126+
requirements_path,
127+
os.path.join(path, "requirements.txt")
128+
)
129+
create_file_from_template(
130+
run_backtest_template_path,
131+
os.path.join(path, "run_backtest.py")
132+
)
133+
# Create the main directory
134+
create_directory(os.path.join(path, "strategies"))
135+
strategies_path = os.path.join(path, "strategies")
136+
create_file(os.path.join(strategies_path, "__init__.py"))
137+
create_file_from_template(
138+
strategy_template_path,
139+
os.path.join(strategies_path, "strategy.py")
140+
)
141+
create_file_from_template(
142+
market_data_providers_template_path,
143+
os.path.join(path, "market_data_providers.py")
144+
)
145+
print(
146+
"App initialized successfully. "
147+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import logging.config
2+
from dotenv import load_dotenv
3+
4+
from investing_algorithm_framework import create_app, \
5+
DEFAULT_LOGGING_CONFIG, Algorithm
6+
from strategies.strategy import MyTradingStrategy
7+
8+
load_dotenv()
9+
logging.config.dictConfig(DEFAULT_LOGGING_CONFIG)
10+
11+
app = create_app(web=True)
12+
algorithm = Algorithm(name="MyTradingBot")
13+
algorithm.add_strategy(MyTradingStrategy)
14+
app.add_algorithm(algorithm)
15+
16+
if __name__ == "__main__":
17+
app.run()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import logging.config
2+
from dotenv import load_dotenv
3+
4+
from investing_algorithm_framework import create_app, \
5+
DEFAULT_LOGGING_CONFIG, Algorithm
6+
from strategies.strategy import MyTradingStrategy
7+
8+
load_dotenv()
9+
logging.config.dictConfig(DEFAULT_LOGGING_CONFIG)
10+
11+
app = create_app()
12+
algorithm = Algorithm(name="MyTradingBot")
13+
algorithm.add_strategy(MyTradingStrategy)
14+
app.add_algorithm(algorithm)
15+
16+
if __name__ == "__main__":
17+
app.run()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from investing_algorithm_framework import CCXTOHLCVMarketDataSource
2+
3+
btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
4+
identifier="BTC/EUR-ohlcv",
5+
market="BINANCE",
6+
symbol="BTC/EUR",
7+
time_frame="2h",
8+
window_size=200
9+
)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
investing-algorithm-framework
2+
pyindicators
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from datetime import datetime
2+
from investing_algorithm_framework import BacktestDateRange, \
3+
pretty_print_backtest
4+
5+
from app import app
6+
7+
if __name__ == "__main__":
8+
backtest_date_range = BacktestDateRange(
9+
start_date=datetime(2023, 1, 1),
10+
end_date=datetime(2023, 12, 31),
11+
)
12+
report = app.run_backtest(backtest_date_range=backtest_date_range)
13+
pretty_print_backtest(report)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from investing_algorithm_framework import TimeUnit, TradingStrategy, Context, \
2+
OrderSide
3+
from .market_data_providers import btc_eur_ohlcv_2h
4+
from pyindicators import ema, is_crossover, is_crossunder
5+
6+
7+
class MyTradingStrategy(TradingStrategy):
8+
time_unit = TimeUnit.HOUR
9+
interval = 2
10+
symbol_pairs = ["BTC/EUR"]
11+
market_data_sources = [btc_eur_ohlcv_2h]
12+
13+
def apply_strategy(self, context: Context, market_data):
14+
15+
for pair in self.symbol_pairs:
16+
# Get the market data for the current symbol pair
17+
market_data_indentifier = f"{pair}-ohlcv-2h"
18+
data = market_data[market_data_indentifier]
19+
symbol = pair.split('/')[0]
20+
21+
# Calculate the EMA with a period of 200
22+
data = ema(
23+
data,
24+
period=200,
25+
source_column="close",
26+
result_column="ema_200"
27+
)
28+
29+
# Calculate the EMA with a period of 50
30+
data = ema(
31+
data,
32+
period=50,
33+
source_column="close",
34+
result_column="ema_50"
35+
)
36+
37+
if not context.has_position(symbol):
38+
39+
if self._is_buy_signal(data):
40+
price = data.iloc[-1]["close"]
41+
order = context.create_limit_order(
42+
target_symbol=symbol,
43+
order_side=OrderSide.BUY,
44+
price=price,
45+
percentage_of_portfolio=25,
46+
precision=4,
47+
)
48+
trade = context.get_trade(order_id=order.id)
49+
context.add_stop_loss(
50+
trade=trade,
51+
trade_risk_type="trailing",
52+
percentage=5,
53+
sell_percentage=50
54+
)
55+
context.add_take_profit(
56+
trade=trade,
57+
percentage=5,
58+
trade_risk_type="trailing",
59+
sell_percentage=50
60+
)
61+
context.add_take_profit(
62+
trade=trade,
63+
percentage=10,
64+
trade_risk_type="trailing",
65+
sell_percentage=20
66+
)
67+
68+
elif self._is_sell_signal(data):
69+
open_trades = context.get_open_trades(
70+
target_symbol=symbol
71+
)
72+
73+
for trade in open_trades:
74+
context.close_trade(trade)
75+
76+
def _is_sell_signal(data):
77+
return is_crossunder(
78+
data,
79+
first_column="ema_50",
80+
second_column="ema_200",
81+
number_of_data_points=1
82+
)
83+
84+
def _is_buy_signal(data):
85+
return is_crossover(
86+
data,
87+
first_column="ema_50",
88+
second_column="ema_200",
89+
number_of_data_points=1
90+
)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ tqdm = "^4.66.1"
2525
tabulate = "^0.9.0"
2626
polars = { version = "^0.20.10", extras = ["numpy", "pandas"] }
2727
jupyter = "^1.0.0"
28-
numpy = "^2.1.3"
2928
scipy = "^1.14.1"
3029
tulipy = "^0.4.0"
3130
azure-storage-blob = "^12.24.0"
@@ -48,5 +47,6 @@ requires = ["poetry-core"]
4847
build-backend = "poetry.core.masonry.api"
4948

5049
[tool.poetry.scripts]
50+
investing-algorithm-framework = "investing_algorithm_framework.cli.cli:cli"
5151
deploy_to_azure_function = "investing_algorithm_framework.cli.deploy_to_azure_function:cli"
5252
create_azure_function_app_skeleton = "investing_algorithm_framework.cli.create_azure_function_app_skeleton:cli"

0 commit comments

Comments
 (0)