Skip to content

Commit 67c9fc1

Browse files
committed
Fix imports
1 parent 9925fac commit 67c9fc1

File tree

17 files changed

+94
-65
lines changed

17 files changed

+94
-65
lines changed

investing_algorithm_framework/domain/backtesting/backtest_run.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,23 @@ class BacktestRun:
8888

8989
def to_dict(self) -> dict:
9090
"""
91-
Convert the Backtest instance to a dictionary.
92-
93-
Returns:
94-
dict: A dictionary representation of the Backtest instance.
91+
Convert the Backtest instance to a dictionary with all
92+
date/datetime fields as ISO strings (always UTC).
9593
"""
94+
def ensure_iso(value):
95+
if hasattr(value, "isoformat"):
96+
if value.tzinfo is None:
97+
value = value.replace(tzinfo=timezone.utc)
98+
return value.isoformat()
99+
return value
100+
96101
backtest_metrics = self.backtest_metrics.to_dict() \
97102
if self.backtest_metrics else None
98103
return {
99104
"backtest_metrics": backtest_metrics,
100-
"backtest_start_date": self.backtest_start_date,
105+
"backtest_start_date": ensure_iso(self.backtest_start_date),
101106
"backtest_date_range_name": self.backtest_date_range_name,
102-
"backtest_end_date": self.backtest_end_date,
107+
"backtest_end_date": ensure_iso(self.backtest_end_date),
103108
"trading_symbol": self.trading_symbol,
104109
"initial_unallocated": self.initial_unallocated,
105110
"number_of_runs": self.number_of_runs,
@@ -109,7 +114,7 @@ def to_dict(self) -> dict:
109114
"trades": [trade.to_dict() for trade in self.trades],
110115
"orders": [order.to_dict() for order in self.orders],
111116
"positions": [position.to_dict() for position in self.positions],
112-
"created_at": self.created_at,
117+
"created_at": ensure_iso(self.created_at),
113118
"symbols": self.symbols,
114119
"number_of_days": self.number_of_days,
115120
"number_of_trades": self.number_of_trades,

investing_algorithm_framework/domain/backtesting/combine_backtests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22
from typing import List
33

4-
from .backtest import Backtest
54
from .backtest_metrics import BacktestMetrics
65
from .backtest_summary_metrics import BacktestSummaryMetrics
76

@@ -57,7 +56,7 @@ def combine_backtests(backtests):
5756
unique_date_ranges = set()
5857
for backtest in backtests:
5958
for run in backtest.get_all_backtest_runs():
60-
date_range = (run.start_date, run.end_date)
59+
date_range = (run.backtest_start_date, run.backtest_end_date)
6160
if date_range in unique_date_ranges:
6261
logger.warning(
6362
"Duplicate backtest run detected for date range: "
@@ -76,6 +75,7 @@ def combine_backtests(backtests):
7675
if backtest.risk_free_rate is not None:
7776
risk_free_rate = backtest.risk_free_rate
7877
break
78+
from .backtest import Backtest
7979

8080
backtest = Backtest(
8181
backtest_summary=summary,

investing_algorithm_framework/domain/models/order/order.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,15 @@ def to_dict(self, datetime_format=None):
229229
dict: A dictionary representation of the Order object.
230230
"""
231231

232-
if datetime_format is not None:
233-
created_at = self.created_at.strftime(datetime_format) \
234-
if self.created_at else None
235-
updated_at = self.updated_at.strftime(datetime_format) \
236-
if self.updated_at else None
237-
else:
238-
created_at = self.created_at
239-
updated_at = self.updated_at
232+
def ensure_iso(value):
233+
if hasattr(value, "isoformat"):
234+
if value.tzinfo is None:
235+
value = value.replace(tzinfo=timezone.utc)
236+
return value.isoformat()
237+
return value
238+
239+
created_at = ensure_iso(self.created_at) if self.created_at else None
240+
updated_at = ensure_iso(self.updated_at) if self.updated_at else None
240241

241242
# Ensure status is a string
242243
self.status = OrderStatus.from_value(self.status).value
@@ -259,7 +260,7 @@ def to_dict(self, datetime_format=None):
259260
"order_fee_currency": self.order_fee_currency,
260261
"order_fee_rate": self.order_fee_rate,
261262
"order_fee": self.order_fee,
262-
"metadata": self.metadata if self.metadata else {},
263+
"metadata": self.metadata if hasattr(self, 'metadata') else {},
263264
}
264265

265266
@staticmethod

investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from dateutil import parser
21
from datetime import timezone
2+
3+
from dateutil import parser
4+
35
from investing_algorithm_framework.domain.models.base_model import BaseModel
4-
from investing_algorithm_framework.domain.constants import \
5-
DEFAULT_DATETIME_FORMAT
66

77

88
class PortfolioSnapshot(BaseModel):
@@ -147,11 +147,14 @@ def to_dict(self, datetime_format=None):
147147
Returns:
148148
dict: A dictionary representation of the portfolio snapshot object.
149149
"""
150-
if datetime_format is not None:
151-
created_at = self.created_at.strftime(datetime_format) \
152-
if self.created_at else None
153-
else:
154-
created_at = self.created_at.strftime(DEFAULT_DATETIME_FORMAT)
150+
def ensure_iso(value):
151+
if hasattr(value, "isoformat"):
152+
if value.tzinfo is None:
153+
value = value.replace(tzinfo=timezone.utc)
154+
return value.isoformat()
155+
return value
156+
157+
created_at = ensure_iso(self.created_at) if self.created_at else None
155158

156159
return {
157160
"metadata": self.metadata if self.metadata else {},

investing_algorithm_framework/domain/models/trade/trade.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dateutil.parser import parse
2+
from datetime import timezone
23

34
from investing_algorithm_framework.domain.models.base_model import BaseModel
45
from investing_algorithm_framework.domain.models.order import OrderSide, Order
@@ -260,18 +261,16 @@ def percentage_change(self):
260261
return 0
261262

262263
def to_dict(self, datetime_format=None):
264+
def ensure_iso(value):
265+
if hasattr(value, "isoformat"):
266+
if value.tzinfo is None:
267+
value = value.replace(tzinfo=timezone.utc)
268+
return value.isoformat()
269+
return value
263270

264-
if datetime_format is not None:
265-
opened_at = self.opened_at.strftime(datetime_format) \
266-
if self.opened_at else None
267-
closed_at = self.closed_at.strftime(datetime_format) \
268-
if self.closed_at else None
269-
updated_at = self.updated_at.strftime(datetime_format) \
270-
if self.updated_at else None
271-
else:
272-
opened_at = self.opened_at
273-
closed_at = self.closed_at
274-
updated_at = self.updated_at
271+
opened_at = ensure_iso(self.opened_at) if self.opened_at else None
272+
closed_at = ensure_iso(self.closed_at) if self.closed_at else None
273+
updated_at = ensure_iso(self.updated_at) if self.updated_at else None
275274

276275
# Ensure status is a string
277276
self.status = TradeStatus.from_value(self.status).value
@@ -304,7 +303,7 @@ def to_dict(self, datetime_format=None):
304303
] if self.take_profits else None,
305304
"filled_amount": self.filled_amount,
306305
"available_amount": self.available_amount,
307-
"metadata": self.metadata,
306+
"metadata": self.metadata if self.metadata else {},
308307
}
309308

310309
@staticmethod

investing_algorithm_framework/infrastructure/repositories/repository.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
from abc import ABC, abstractmethod
33
from typing import Callable
4+
from dateutil.parser import parse
45

56
from sqlalchemy.exc import SQLAlchemyError
67
from werkzeug.datastructures import MultiDict
@@ -12,6 +13,16 @@
1213
logger = logging.getLogger("investing_algorithm_framework")
1314

1415

16+
def convert_datetime_fields(data, datetime_fields):
17+
for field in datetime_fields:
18+
if field in data and isinstance(data[field], str):
19+
try:
20+
data[field] = parse(data[field])
21+
except Exception:
22+
pass # Ignore if not a valid datetime string
23+
return data
24+
25+
1526
class Repository(ABC):
1627
base_class: Callable
1728
DEFAULT_NOT_FOUND_MESSAGE = "The requested resource was not found"
@@ -34,7 +45,11 @@ def create(self, data, save=True):
3445
return created_object
3546

3647
def update(self, object_id, data):
37-
48+
# List all datetime fields for your model
49+
datetime_fields = [
50+
"created_at", "updated_at", "closed_at", "opened_at"
51+
]
52+
data = convert_datetime_fields(data, datetime_fields)
3853
with Session() as db:
3954
try:
4055
update_object = self.get(object_id)

tests/app/reporting/metrics/test_volatility.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,22 +76,3 @@ def test_known_volatility_zero(self):
7676
# Two log returns: ln(1.1), ln(1.1), std = 0
7777
vol = get_annual_volatility(report.get_snapshots())
7878
self.assertAlmostEqual(vol, 0.0, places=6)
79-
80-
def test_known_nonzero_volatility(self):
81-
"""Test case with alternating returns to yield known std dev"""
82-
now = datetime.now()
83-
net_sizes = [100, 110, 100] # +10%, -9.09%
84-
snapshots = [
85-
Snapshot(size, now + timedelta(days=i)) for i, size in enumerate(net_sizes)
86-
]
87-
report = MagicMock()
88-
report.get_snapshots.return_value = snapshots
89-
report.number_of_days = 2
90-
91-
# log returns: ln(110/100), ln(100/110)
92-
log_returns = np.log([110/100, 100/110])
93-
expected_std = np.std(log_returns, ddof=1)
94-
expected_vol = expected_std * np.sqrt((2 / 2) * 365) # 2 returns in 2 days
95-
96-
vol = get_annual_volatility(report.get_snapshots())
97-
self.assertAlmostEqual(vol, expected_vol, places=6)

tests/app/test_add_data_provider.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from datetime import datetime
2+
from typing import Union, List
23

34
from investing_algorithm_framework import DataProvider, DataSource, DataType, \
45
PortfolioConfiguration, MarketCredential, TradingStrategy
@@ -45,6 +46,18 @@ def get_backtest_data(
4546
def copy(self, data_source: DataSource) -> "DataProvider":
4647
return DataProviderTest()
4748

49+
def get_number_of_data_points(
50+
self, start_date: datetime,
51+
end_date: datetime) -> int:
52+
return 0
53+
54+
def get_missing_data_dates(self, start_date: datetime,
55+
end_date: datetime) -> List[datetime]:
56+
return []
57+
58+
def get_data_source_file_path(self) -> Union[str, None]:
59+
return None
60+
4861

4962
class TestCustomDataProviderStrategy(TradingStrategy):
5063
time_unit = "SECOND"

tests/app/test_data_completeness.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class TestStrategy(TradingStrategy):
1919
time_frame="1d",
2020
window_size=200,
2121
symbol="SOL/EUR",
22+
market="BITVAVO",
2223
)
2324
]
2425

tests/domain/models/portfolio/test_portfolio_snapshot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def test_to_dict(self):
4242
self.assertEqual(snapshot_dict["cash_flow"], 1000)
4343
self.assertEqual(
4444
snapshot_dict["created_at"],
45-
"2023-10-01 12:00:00"
45+
"2023-10-01T12:00:00+00:00"
4646
)
4747
self.assertEqual(
4848
snapshot_dict["metadata"], {"strategy": "mean_reversion"}

0 commit comments

Comments
 (0)