Skip to content

Commit

Permalink
Merge pull request #43 from 10-academy-w-9/recommendation-strategy
Browse files Browse the repository at this point in the history
Recommendation strategy integrated
  • Loading branch information
AbYT101 committed Jun 26, 2024
2 parents 39c09a7 + 83be8e1 commit d07d109
Show file tree
Hide file tree
Showing 14 changed files with 849 additions and 151 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Local files
/data
/mlruns

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
Empty file removed __init__.py
Empty file.
4 changes: 3 additions & 1 deletion app/models/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Backtest(db.Model):
start_date = db.Column(db.Date)
end_date = db.Column(db.Date)
inital_cash = db.Column(db.Integer)
fee = db.Column(db.Integer)
fee = db.Column(db.Float)
# status = db.Column(db.String(50))
created_at = db.Column(db.DateTime, default=db.func.current_timestamp())

Expand All @@ -29,12 +29,14 @@ class Result(db.Model):
__tablename__ = 'results'
id = db.Column(db.Integer, primary_key=True)
backtest_id = db.Column(db.Integer, db.ForeignKey('backtests.id'), nullable=False)
strategy = db.Column(db.String(255))
total_return = db.Column(db.Numeric(10, 2))
number_of_trades = db.Column(db.Integer)
winning_trades = db.Column(db.Integer)
losing_trades = db.Column(db.Integer)
max_drawdown = db.Column(db.Numeric(10, 2))
sharpe_ratio = db.Column(db.Numeric(10, 2))
is_best = db.Column(db.Boolean, default=False, nullable=True)

class Metric(db.Model):
__tablename__ = 'metrics'
Expand Down
19 changes: 11 additions & 8 deletions app/routes/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from app.models.backtest import Backtest, Result
from app import db
from flask_jwt_extended import jwt_required
from app.services.backtest_service import run_backtest_by_id
from app.services.backtest_service import run_backtest_by_id, run_and_evaluate_backtest
from app.services.kafka_service import kafka_service
from flask_cors import CORS, cross_origin

Expand Down Expand Up @@ -34,10 +34,11 @@ def run_backtest():
db.session.commit()


# Publish backtest to Kafka for processing
kafka_service.produce('backtest_scenes', {
"backtest_id": new_backtest.id
})
# # Publish backtest to Kafka for processing
# kafka_service.produce('backtest_scenes', {
# "backtest_id": new_backtest.id
# })
run_and_evaluate_backtest(backtest_id=new_backtest.id, symbol=symbol, initial_cash= inital_cash, fee=fee, start_date=start_date,end_date= end_date)

return jsonify({"msg": "Backtest created and published to Kafka", "backtest_id": new_backtest.id}), 201

Expand All @@ -55,15 +56,15 @@ def get_backtests():
'symbol': backtest.symbol,
'start_date': backtest.start_date.strftime('%Y-%m-%d'),
'end_date': backtest.end_date.strftime('%Y-%m-%d'),
'initial_cash': backtest.initial_cash,
'inital_cash': backtest.inital_cash,
'fee': backtest.fee,
'created_at': backtest.created_at.strftime('%Y-%m-%d %H:%M:%S')
})
return jsonify({'backtests': backtest_list}), 200

@bp.route('/backtests/<int:backtest_id>/results', methods=['GET'])
@jwt_required()
@cross_origin(origin='*')
@cross_origin(origins='*')
def get_backtest_results(backtest_id):
results = Result.query.filter_by(backtest_id=backtest_id).all()
if not results:
Expand All @@ -73,12 +74,14 @@ def get_backtest_results(backtest_id):
for result in results:
result_list.append({
'id': result.id,
'strategy': result.strategy,
'total_return': float(result.total_return),
'number_of_trades': result.number_of_trades,
'winning_trades': result.winning_trades,
'losing_trades': result.losing_trades,
'max_drawdown': float(result.max_drawdown),
'sharpe_ratio': float(result.sharpe_ratio)
'sharpe_ratio': float(result.sharpe_ratio),
'is_best': result.is_best
})

return jsonify({'results': result_list}), 200
92 changes: 48 additions & 44 deletions app/services/backtest_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,25 @@ def run_backtest_by_id(backtest_id):


run_and_evaluate_backtest(backtest_id=backtest_id, symbol=backtest.symbol, initial_cash=backtest.inital_cash, fee=backtest.fee, start_date=backtest.start_date, end_date = backtest.end_date)
# for res in results:
# result = Result(**res)
# db.session.add(result)
# # Log metrics to MLflow
# metrics = {
# "total_return": res.total_return,
# "number_of_trades": res.number_of_trades,
# "winning_trades": res.winning_trades,
# "losing_trades": res.losing_trades,
# "max_drawdown": res.max_drawdown,
# "sharpe_ratio": res.sharpe_ratio
# }
# mlflow_service.log_metrics(run_name=f"Backtest_{backtest_id}", metrics=metrics)

# Publish result to Kafka
# kafka_service.produce('backtest_results', {
# "backtest_id": backtest_id,
# "metrics": metrics
# })
# db.session.commit()


def score_backtest(result, min_return, max_return, min_sharpe, max_sharpe, min_drawdown, max_drawdown):
weights = {
'total_return': 0.4,
'sharpe_ratio': 0.4,
'max_drawdown': 0.2,
}

normalized_return = (result['total_return'] - min_return) / (max_return - min_return)
normalized_sharpe = (result['sharpe_ratio'] - min_sharpe) / (max_sharpe - min_sharpe)
normalized_drawdown = (max_drawdown - result['max_drawdown']) / (max_drawdown - min_drawdown)

score = (
weights['total_return'] * normalized_return +
weights['sharpe_ratio'] * normalized_sharpe +
weights['max_drawdown'] * normalized_drawdown
)
return score

def run_and_evaluate_backtest(backtest_id, symbol, initial_cash, fee, start_date, end_date):
strategies = [
Expand All @@ -44,51 +41,58 @@ def run_and_evaluate_backtest(backtest_id, symbol, initial_cash, fee, start_date
]

results = []
result_objects = []
for strategy in strategies:
print(strategy, symbol, initial_cash, fee, start_date, end_date)
result = run_backtest(strategy, symbol, initial_cash, fee, start_date, end_date)
result.backtest_id = backtest_id
result = Result(**result)
db.session.add(result)
result['backtest_id'] = backtest_id
result['strategy'] = strategy.__name__

result_obj = Result(**result)
db.session.add(result_obj)
result_objects.append(result_obj)

# Log metrics to MLflow
metrics = {
"total_return": result.total_return,
"number_of_trades": result.number_of_trades,
"winning_trades": result.winning_trades,
"losing_trades": result.losing_trades,
"max_drawdown": result.max_drawdown,
"sharpe_ratio": result.sharpe_ratio
"total_return": result['total_return'],
"number_of_trades": result['number_of_trades'],
"winning_trades": result['winning_trades'],
"losing_trades": result['losing_trades'],
"max_drawdown": result['max_drawdown'],
"sharpe_ratio": result['sharpe_ratio']
}
mlflow_service.log_metrics(run_name=f"Backtest_{backtest_id}", metrics=metrics)

# Publish result to Kafka
# kafka_service.produce('backtest_results', {
# "backtest_id": backtest_id,
# "metrics": metrics
# })

# publish results to Kafka
kafka_service.produce('backtest_results', {
"backtest_id": backtest_id,
"metrics": metrics
})

db.session.commit()

results.append(result)

# Determine the min and max values for normalization
min_return = min(result['total_return'] for result in results)
max_return = max(result['total_return'] for result in results)
min_sharpe = min(result['sharpe_ratio'] for result in results)
max_sharpe = max(result['sharpe_ratio'] for result in results)
min_drawdown = min(result['max_drawdown'] for result in results)
max_drawdown = max(result['max_drawdown'] for result in results)

# Score each strategy
scores = [score_backtest(result) for result in results]
scores = [score_backtest(result, min_return, max_return, min_sharpe, max_sharpe, min_drawdown, max_drawdown) for result in results]

# Select the best strategy
best_strategy_index = scores.index(max(scores))
best_strategy = strategies[best_strategy_index]

for idx, result_obj in enumerate(result_objects):
result_obj.is_best = (idx == best_strategy_index)

db.session.commit()

print("Best Strategy:")
print(best_strategy.__name__)
print(strategies[best_strategy_index].__name__)
print("Score:")
print(scores[best_strategy_index])
print("Metrics:")
print(results[best_strategy_index])
return results

return results

18 changes: 15 additions & 3 deletions app/services/mlflow_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@
import mlflow.sklearn

class MLflowService:
def __init__(self, tracking_uri):
def __init__(self, tracking_uri, experiment_name):
mlflow.set_tracking_uri(tracking_uri)
self.experiment_name = experiment_name
self.experiment_id = self.get_or_create_experiment_id(experiment_name)

def get_or_create_experiment_id(self, experiment_name):
experiment = mlflow.get_experiment_by_name(experiment_name)
if experiment is None:
experiment_id = mlflow.create_experiment(experiment_name)
else:
experiment_id = experiment.experiment_id
return experiment_id

def log_metrics(self, run_name, metrics):
with mlflow.start_run(run_name=run_name):
with mlflow.start_run(experiment_id=self.experiment_id, run_name=run_name):
for key, value in metrics.items():
print(key, value)
mlflow.log_metric(key, value)

mlflow_service = MLflowService(tracking_uri='http://localhost:5050')
# Initialize the MLflowService with the desired experiment name
mlflow_service = MLflowService(tracking_uri='http://localhost:5050', experiment_name='Backtest_Results')
Loading

0 comments on commit d07d109

Please sign in to comment.