Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
111717d
fix: correct dictionary keys in trade_metrics_table.py (#352)
MDUYN Mar 18, 2026
ecf3691
test: add regression tests for trade_metrics_table KeyError (#352)
MDUYN Mar 18, 2026
fbfd30c
Merge pull request #393 from coding-kitties/squad/352-fix-trade-metri…
MDUYN Mar 18, 2026
50358e9
Update dev with squad files
MDUYN Mar 18, 2026
b4dc1c8
chore: merge main into dev (publish workflow fix)
MDUYN Mar 18, 2026
26d49bc
feat: add CCXTTickerDataProvider for TICKER data sources
MDUYN Mar 18, 2026
50ac874
fix: remove network dependency from test_download
MDUYN Mar 18, 2026
be80c86
Merge pull request #395 from coding-kitties/squad/350-add-ccxt-ticker…
MDUYN Mar 18, 2026
c052ced
feat: raise OperationalException when scheduling interval is faster t…
MDUYN Mar 19, 2026
cbfd597
Merge pull request #398 from coding-kitties/feature/warn-scheduling-i…
MDUYN Mar 19, 2026
3da0989
fix: correct portfolio total_net_gain accumulation understating gains…
MDUYN Mar 19, 2026
633f19d
Remove unused var
MDUYN Mar 19, 2026
d08c3a7
feat: add CSVTickerDataProvider and comprehensive CSV data provider t…
MDUYN Mar 19, 2026
424998e
Merge pull request #399 from coding-kitties/fix/portfolio-net-gain-un…
MDUYN Mar 19, 2026
581d1d4
Merge pull request #400 from coding-kitties/fix/csv-ohlcv-based-tests…
MDUYN Mar 19, 2026
bb348db
docs: rewrite Tasks, Trades, and Deployment sections with accurate AP…
MDUYN Mar 20, 2026
3bcca93
fix: docusaurus config, sidebar, and tasks docs improvements
MDUYN Mar 20, 2026
1f6a1c7
Merge pull request #402 from coding-kitties/squad/334-docs-tasks-trad…
MDUYN Mar 20, 2026
ad32cce
release: merge dev into main (exclude squad files)
MDUYN Mar 20, 2026
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
584 changes: 131 additions & 453 deletions docusaurus/docs/Getting Started/deployment.md

Large diffs are not rendered by default.

687 changes: 156 additions & 531 deletions docusaurus/docs/Getting Started/tasks.md

Large diffs are not rendered by default.

587 changes: 189 additions & 398 deletions docusaurus/docs/Getting Started/trades.md

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions docusaurus/docusaurus.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion

const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const {themes} = require('prism-react-renderer');
const lightCodeTheme = themes.github;
const darkCodeTheme = themes.dracula;

/** @type {import('@docusaurus/types').Config} */
const config = {
Expand Down
4 changes: 2 additions & 2 deletions docusaurus/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ const sidebars = {
},
{
type: 'doc',
id: 'Data/market-data-sources',
id: 'Data/data-sources',
},
{
type: 'doc',
id: 'Data/multiple-market-data-sources',
id: 'Data/backtest_data',
},
],
},
Expand Down
6 changes: 5 additions & 1 deletion investing_algorithm_framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
SnapshotInterval, AWS_S3_STATE_BUCKET_NAME, BacktestEvaluationFocus, \
save_backtests_to_directory, BacktestMetrics, DATA_DIRECTORY
from .infrastructure import AzureBlobStorageStateHandler, \
CSVOHLCVDataProvider, CCXTOHLCVDataProvider, PandasOHLCVDataProvider, \
CSVOHLCVDataProvider, CSVTickerDataProvider, \
CCXTOHLCVDataProvider, CCXTTickerDataProvider, \
PandasOHLCVDataProvider, \
AWSS3StorageStateHandler
from .create_app import create_app
from .download_data import download, download_v2, DownloadResult, \
Expand Down Expand Up @@ -109,7 +111,9 @@
'select_backtest_date_ranges',
'DataType',
'CSVOHLCVDataProvider',
'CSVTickerDataProvider',
"CCXTOHLCVDataProvider",
"CCXTTickerDataProvider",
"DataProvider",
"get_annual_volatility",
"get_sortino_ratio",
Expand Down
9 changes: 4 additions & 5 deletions investing_algorithm_framework/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2124,11 +2124,10 @@ def add_strategy(self, strategy, throw_exception=True) -> None:

has_duplicates = False

for i in range(len(self._strategies)):
for j in range(i + 1, len(self._strategies)):
if self._strategies[i].worker_id == strategy.worker_id:
has_duplicates = True
break
for existing_strategy in self._strategies:
if existing_strategy.strategy_id == strategy.strategy_id:
has_duplicates = True
break

if has_duplicates:
raise OperationalException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ def create_html_trade_metrics_table(results, report):
copy_results['Worst Trade'] = f"{worst_trade['net_gain']:.2f} {report.trading_symbol}"
copy_results['Worst Trade Date'] = safe_format_date(worst_trade['opened_at'], format_str=DEFAULT_DATETIME_FORMAT)

copy_results['Trades Average Gain'] = f"{safe_format(copy_results['average_trade_gain'], string_format)} {report.trading_symbol} {copy_results['trades_average_gain_percentage']:.2f}%"
copy_results['Trades Average Loss'] = f"{safe_format(copy_results['average_trade_loss'], string_format)} {report.trading_symbol} {copy_results['trades_average_loss_percentage']:.2f}%"
copy_results['Trades Average Gain'] = f"{safe_format(copy_results['average_trade_gain'], string_format)} {report.trading_symbol} {copy_results['average_trade_gain_percentage']:.2f}%"
copy_results['Trades Average Loss'] = f"{safe_format(copy_results['average_trade_loss'], string_format)} {report.trading_symbol} {copy_results['average_trade_loss_percentage']:.2f}%"
copy_results['Average Trade Duration'] = f"{copy_results['average_trade_duration']:.2f} hours"
copy_results['Number of Trades'] = f"{copy_results['number_of_trades']}"
copy_results['Win Rate'] = f"{copy_results['win_rate']:.2f}%"
Expand Down
28 changes: 27 additions & 1 deletion investing_algorithm_framework/app/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from investing_algorithm_framework.domain import OperationalException, \
Position, PositionSize, TimeUnit, StrategyProfile, Trade, \
DataSource, OrderSide, StopLossRule, TakeProfitRule, Order, \
DataSource, DataType, OrderSide, StopLossRule, TakeProfitRule, Order, \
INDEX_DATETIME
from .context import Context

Expand Down Expand Up @@ -156,6 +156,32 @@ def __init__(
f"Interval not set for strategy instance {self.strategy_id}"
)

# Check if scheduling interval is faster than the smallest
# OHLCV data source timeframe
ohlcv_timeframes = [
ds.time_frame.amount_of_minutes
for ds in self.data_sources
if ds.time_frame is not None
and DataType.OHLCV.equals(ds.data_type)
]

if ohlcv_timeframes:
scheduling_interval = \
self.time_unit.amount_of_minutes * self.interval
smallest_timeframe = min(ohlcv_timeframes)

if scheduling_interval < smallest_timeframe:
raise OperationalException(
f"Strategy '{self.strategy_id}' scheduling interval "
f"({self.interval} {self.time_unit.value.lower()}"
f"{'s' if self.interval > 1 else ''}"
f" = {scheduling_interval} min) is faster than "
f"the smallest OHLCV data source timeframe "
f"({smallest_timeframe} min). The strategy would "
f"run without new data. Increase the scheduling "
f"interval or use a smaller data timeframe."
)

# Initialize stop_losses as a new list per instance
if stop_losses is not None:
self.stop_losses = list(stop_losses)
Expand Down
7 changes: 5 additions & 2 deletions investing_algorithm_framework/infrastructure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
SQLOrderMetadataRepository
from .services import AzureBlobStorageStateHandler, AWSS3StorageStateHandler, \
BacktestService
from .data_providers import CSVOHLCVDataProvider, get_default_data_providers, \
from .data_providers import CSVOHLCVDataProvider, \
CSVTickerDataProvider, get_default_data_providers, \
get_default_ohlcv_data_providers, CCXTOHLCVDataProvider, \
PandasOHLCVDataProvider
CCXTTickerDataProvider, PandasOHLCVDataProvider
from .order_executors import CCXTOrderExecutor, BacktestOrderExecutor
from .portfolio_providers import CCXTPortfolioProvider

Expand Down Expand Up @@ -40,12 +41,14 @@
"SQLTradeStopLossRepository",
"SQLOrderMetadataRepository",
"CSVOHLCVDataProvider",
"CSVTickerDataProvider",
"CCXTOrderExecutor",
"CCXTPortfolioProvider",
"get_default_data_providers",
"get_default_ohlcv_data_providers",
"AWSS3StorageStateHandler",
"CCXTOHLCVDataProvider",
"CCXTTickerDataProvider",
"BacktestOrderExecutor",
"PandasOHLCVDataProvider",
"BacktestService",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .ccxt import CCXTOHLCVDataProvider
from .csv import CSVOHLCVDataProvider
from .ccxt import CCXTOHLCVDataProvider, CCXTTickerDataProvider
from .csv import CSVOHLCVDataProvider, CSVTickerDataProvider
from .pandas import PandasOHLCVDataProvider


Expand All @@ -12,6 +12,7 @@ def get_default_data_providers():
"""
return [
CCXTOHLCVDataProvider(),
CCXTTickerDataProvider(),
]


Expand All @@ -29,7 +30,9 @@ def get_default_ohlcv_data_providers():

__all__ = [
'CSVOHLCVDataProvider',
'CSVTickerDataProvider',
'CCXTOHLCVDataProvider',
'CCXTTickerDataProvider',
'get_default_data_providers',
'get_default_ohlcv_data_providers',
'PandasOHLCVDataProvider',
Expand Down
159 changes: 159 additions & 0 deletions investing_algorithm_framework/infrastructure/data_providers/ccxt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1256,3 +1256,162 @@ def get_data_source_file_path(self) -> Union[str, None]:
locally, otherwise None.
"""
return self.data_file_path


class CCXTTickerDataProvider(DataProvider):
"""
Data provider for ticker data using the CCXT library.

Fetches real-time ticker data (bid, ask, last price, volume, etc.)
for a given symbol and market via CCXT's fetch_ticker API.

In backtest mode, ticker data is derived from OHLCV data
(handled by the DataProviderService fallback), so this provider
only serves live/non-backtest use cases.
"""
data_type = DataType.TICKER
data_provider_identifier = "ccxt_ticker_data_provider"

def __init__(
self,
symbol: str = None,
market: str = None,
data_provider_identifier: str = None,
config=None
):
if data_provider_identifier is None:
data_provider_identifier = self.data_provider_identifier

super().__init__(
symbol=symbol,
market=market,
data_provider_identifier=data_provider_identifier,
config=config
)

def has_data(
self,
data_source: DataSource,
start_date: datetime = None,
end_date: datetime = None,
) -> bool:
data_type = data_source.data_type
market = data_source.market
symbol = data_source.symbol

if not DataType.TICKER.equals(data_type):
return False

if market is None:
market = "binance"

try:
market = market.lower()
exchange_class = getattr(ccxt, market)
exchange = exchange_class()
symbols = list(exchange.load_markets().keys())
return symbol in symbols
except ccxt.NetworkError:
return False
except Exception as e:
logger.error(e)
return False

def prepare_backtest_data(
self,
backtest_start_date,
backtest_end_date,
fill_missing_data: bool = False,
show_progress: bool = False,
) -> None:
# Ticker backtest data is derived from OHLCV by the
# DataProviderService fallback — nothing to prepare here.
pass

def get_backtest_data(
self,
backtest_index_date: datetime,
backtest_start_date: datetime = None,
backtest_end_date: datetime = None,
data_source: DataSource = None,
):
# Backtest ticker data is handled by DataProviderService
# falling back to OHLCV data.
return None

def get_data(
self,
date: datetime = None,
start_date: datetime = None,
end_date: datetime = None,
save: bool = False,
) -> dict:
if self.market is None:
raise OperationalException(
"Market is not set. Please set the market "
"before calling get_data."
)

if self.symbol is None:
raise OperationalException(
"Symbol is not set. Please set the symbol "
"before calling get_data."
)

market_credential = self.get_credential(self.market)
exchange = CCXTOHLCVDataProvider.initialize_exchange(
self.market, market_credential
)
ticker = exchange.fetch_ticker(self.symbol)

return {
"symbol": self.symbol,
"market": self.market,
"datetime": ticker.get("datetime"),
"high": ticker.get("high"),
"low": ticker.get("low"),
"bid": ticker.get("bid"),
"ask": ticker.get("ask"),
"open": ticker.get("open"),
"close": ticker.get("close"),
"last": ticker.get("last"),
"volume": ticker.get("baseVolume"),
}

def copy(self, data_source: DataSource) -> "CCXTTickerDataProvider":
if data_source.market is None or data_source.market == "":
raise OperationalException(
"DataSource has no `market` attribute specified. "
"Please specify the market attribute in the data source "
"specification before using the CCXT ticker data provider."
)

if data_source.symbol is None or data_source.symbol == "":
raise OperationalException(
"DataSource has no `symbol` attribute specified. "
"Please specify the symbol attribute in the data source "
"specification before using the CCXT ticker data provider."
)

return CCXTTickerDataProvider(
symbol=data_source.symbol,
market=data_source.market,
data_provider_identifier=data_source.data_provider_identifier,
config=self.config,
)

def get_number_of_data_points(
self, start_date: datetime, end_date: datetime
) -> int:
# Ticker data is a single point-in-time snapshot
return 1

def get_missing_data_dates(
self, start_date: datetime, end_date: datetime
) -> list:
# No stored data to have gaps in
return []

def get_data_source_file_path(self):
# Ticker data is not file-based
return None
Loading
Loading