chronopype 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Gianluca Pagliara
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.1
2
+ Name: chronopype
3
+ Version: 0.1.0
4
+ Summary: A flexible clock implementation for real-time and backtesting scenarios
5
+ Author: Gianluca Pagliara
6
+ Author-email: pagliara.gianluca@gmail.com
7
+ Requires-Python: >=3.13,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Requires-Dist: eventspype (>=0.1.0,<0.2.0)
11
+ Requires-Dist: pydantic (>=2.10.4,<3.0.0)
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Chrono Pypeline
15
+
16
+ [![CI](https://github.com/gianlucapagliara/chronopype/actions/workflows/ci.yml/badge.svg)](https://github.com/gianlucapagliara/chronopype/actions/workflows/ci.yml)
17
+ [![codecov](https://codecov.io/gh/gianlucapagliara/chronopype/branch/main/graph/badge.svg)](https://codecov.io/gh/gianlucapagliara/chronopype)
18
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
19
+
20
+ A flexible clock implementation for real-time and backtesting scenarios in Python. chronopype provides a robust framework for managing time-based operations with support for both real-time processing and historical data backtesting.
21
+
22
+ ## Features
23
+
24
+ - ๐Ÿ•’ **Flexible Clock System**: Support for both real-time and backtesting modes
25
+ - ๐Ÿ”„ **Processor Framework**: Extensible system for implementing time-based operations
26
+ - ๐ŸŒ **Network-Aware**: Built-in network processor with retry and backoff capabilities
27
+ - โšก **Async Support**: Full async/await support for efficient I/O operations
28
+ - ๐Ÿ› ๏ธ **Easy to Use**: Simple API for managing time-based operations
29
+ - ๐Ÿ“Š **Performance Monitoring**: Built-in performance tracking and statistics
30
+ - ๐Ÿ”’ **Type Safe**: Fully typed with MyPy strict mode
31
+ - ๐Ÿงช **Well Tested**: Comprehensive test suite with high coverage
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ # Using pip
37
+ pip install chronopype
38
+
39
+ # Using poetry
40
+ poetry add chronopype
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ Here's a simple example of using chronopype:
46
+
47
+ ```python
48
+ import asyncio
49
+ from chronopype import ClockConfig
50
+ from chronopype.clocks import RealtimeClock
51
+ from chronopype.processors import TickProcessor
52
+
53
+ class MyProcessor(TickProcessor):
54
+ async def async_tick(self, timestamp: float) -> None:
55
+ print(f"Processing at {timestamp}")
56
+
57
+ async def main():
58
+ # Configure the clock
59
+ config = ClockConfig(
60
+ start_time=time.time(),
61
+ tick_size=1.0 # 1 second ticks
62
+ )
63
+
64
+ # Create and configure the clock
65
+ async with RealtimeClock(config) as clock:
66
+ # Add your processor
67
+ clock.add_processor(MyProcessor())
68
+
69
+ # Run for 10 seconds
70
+ await clock.run_til(config.start_time + 10)
71
+
72
+ if __name__ == "__main__":
73
+ asyncio.run(main())
74
+ ```
75
+
76
+ ## Core Components
77
+
78
+ - **Clocks**: Base implementations for time management
79
+ - `RealtimeClock`: For real-time processing
80
+ - `BacktestClock`: For historical data processing
81
+
82
+ - **Processors**: Framework for implementing time-based operations
83
+ - `TickProcessor`: Base class for all processors
84
+ - `NetworkProcessor`: Network-aware processor with retry capabilities
85
+
86
+ ## Development
87
+
88
+ chronopype uses Poetry for dependency management and packaging:
89
+
90
+ ```bash
91
+ # Install dependencies
92
+ poetry install
93
+
94
+ # Run tests
95
+ poetry run pytest
96
+
97
+ # Run type checks
98
+ poetry run mypy .
99
+
100
+ # Run linting
101
+ poetry run pre-commit run --all-files
102
+ ```
103
+
@@ -0,0 +1,89 @@
1
+ # Chrono Pypeline
2
+
3
+ [![CI](https://github.com/gianlucapagliara/chronopype/actions/workflows/ci.yml/badge.svg)](https://github.com/gianlucapagliara/chronopype/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/gianlucapagliara/chronopype/branch/main/graph/badge.svg)](https://codecov.io/gh/gianlucapagliara/chronopype)
5
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
6
+
7
+ A flexible clock implementation for real-time and backtesting scenarios in Python. chronopype provides a robust framework for managing time-based operations with support for both real-time processing and historical data backtesting.
8
+
9
+ ## Features
10
+
11
+ - ๐Ÿ•’ **Flexible Clock System**: Support for both real-time and backtesting modes
12
+ - ๐Ÿ”„ **Processor Framework**: Extensible system for implementing time-based operations
13
+ - ๐ŸŒ **Network-Aware**: Built-in network processor with retry and backoff capabilities
14
+ - โšก **Async Support**: Full async/await support for efficient I/O operations
15
+ - ๐Ÿ› ๏ธ **Easy to Use**: Simple API for managing time-based operations
16
+ - ๐Ÿ“Š **Performance Monitoring**: Built-in performance tracking and statistics
17
+ - ๐Ÿ”’ **Type Safe**: Fully typed with MyPy strict mode
18
+ - ๐Ÿงช **Well Tested**: Comprehensive test suite with high coverage
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ # Using pip
24
+ pip install chronopype
25
+
26
+ # Using poetry
27
+ poetry add chronopype
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ Here's a simple example of using chronopype:
33
+
34
+ ```python
35
+ import asyncio
36
+ from chronopype import ClockConfig
37
+ from chronopype.clocks import RealtimeClock
38
+ from chronopype.processors import TickProcessor
39
+
40
+ class MyProcessor(TickProcessor):
41
+ async def async_tick(self, timestamp: float) -> None:
42
+ print(f"Processing at {timestamp}")
43
+
44
+ async def main():
45
+ # Configure the clock
46
+ config = ClockConfig(
47
+ start_time=time.time(),
48
+ tick_size=1.0 # 1 second ticks
49
+ )
50
+
51
+ # Create and configure the clock
52
+ async with RealtimeClock(config) as clock:
53
+ # Add your processor
54
+ clock.add_processor(MyProcessor())
55
+
56
+ # Run for 10 seconds
57
+ await clock.run_til(config.start_time + 10)
58
+
59
+ if __name__ == "__main__":
60
+ asyncio.run(main())
61
+ ```
62
+
63
+ ## Core Components
64
+
65
+ - **Clocks**: Base implementations for time management
66
+ - `RealtimeClock`: For real-time processing
67
+ - `BacktestClock`: For historical data processing
68
+
69
+ - **Processors**: Framework for implementing time-based operations
70
+ - `TickProcessor`: Base class for all processors
71
+ - `NetworkProcessor`: Network-aware processor with retry capabilities
72
+
73
+ ## Development
74
+
75
+ chronopype uses Poetry for dependency management and packaging:
76
+
77
+ ```bash
78
+ # Install dependencies
79
+ poetry install
80
+
81
+ # Run tests
82
+ poetry run pytest
83
+
84
+ # Run type checks
85
+ poetry run mypy .
86
+
87
+ # Run linting
88
+ poetry run pre-commit run --all-files
89
+ ```
File without changes
@@ -0,0 +1,32 @@
1
+ """Clock implementations package."""
2
+
3
+ from .backtest import BacktestClock
4
+ from .base import BaseClock
5
+ from .modes import ClockMode
6
+ from .realtime import RealtimeClock
7
+
8
+ # Registry mapping clock modes to their implementations
9
+ CLOCK_REGISTRY: dict[ClockMode, type[BaseClock]] = {
10
+ ClockMode.BACKTEST: BacktestClock,
11
+ ClockMode.REALTIME: RealtimeClock,
12
+ }
13
+
14
+
15
+ def get_clock_class(mode: ClockMode) -> type[BaseClock]:
16
+ """Get the clock class for a given mode.
17
+
18
+ Args:
19
+ mode: The clock mode to get the implementation for.
20
+
21
+ Returns:
22
+ The clock class for the given mode.
23
+
24
+ Raises:
25
+ ValueError: If no implementation exists for the given mode.
26
+ """
27
+ if mode not in CLOCK_REGISTRY:
28
+ raise ValueError(f"No clock implementation found for mode: {mode}")
29
+ return CLOCK_REGISTRY[mode]
30
+
31
+
32
+ __all__ = ["BaseClock", "BacktestClock", "RealtimeClock", "ClockMode"]
@@ -0,0 +1,168 @@
1
+ import asyncio
2
+ from collections.abc import Callable
3
+
4
+ from ..exceptions import ClockError
5
+ from ..models import ClockConfig
6
+ from ..processors.base import TickProcessor
7
+ from .base import BaseClock
8
+ from .modes import ClockMode
9
+
10
+
11
+ class BacktestClock(BaseClock):
12
+ """Clock implementation for backtesting mode."""
13
+
14
+ start_publication = BaseClock.start_publication
15
+ tick_publication = BaseClock.tick_publication
16
+ stop_publication = BaseClock.stop_publication
17
+
18
+ def __init__(
19
+ self,
20
+ config: ClockConfig,
21
+ error_callback: Callable[[TickProcessor, Exception], None] | None = None,
22
+ ) -> None:
23
+ """Initialize a new BacktestClock instance."""
24
+ if config.clock_mode != ClockMode.BACKTEST:
25
+ raise ClockError("BacktestClock requires BACKTEST mode")
26
+ if config.end_time <= 0:
27
+ raise ClockError("end_time must be set for backtest mode")
28
+ super().__init__(config, error_callback)
29
+
30
+ def start(self) -> None:
31
+ """Start the clock."""
32
+ if not self._current_context:
33
+ raise ClockError("Clock must be started in a context")
34
+ self._started = True
35
+
36
+ def stop(self) -> None:
37
+ """Stop the clock."""
38
+ self._started = False
39
+ self._running = False
40
+
41
+ def tick(self) -> None:
42
+ """Process a clock tick."""
43
+ if not self._started:
44
+ raise ClockError("Clock not started")
45
+ self._tick_counter += 1
46
+ self._current_tick += self._config.tick_size
47
+
48
+ async def run(self) -> None:
49
+ """Run the clock until end_time."""
50
+ await self.run_til(self._config.end_time)
51
+
52
+ async def run_til(self, target_time: float) -> None:
53
+ """Run the clock until the target time."""
54
+ if self._task is not None:
55
+ raise ClockError("Clock is already running")
56
+
57
+ if not self._current_context:
58
+ raise ClockError("Clock must be started in a context")
59
+
60
+ if target_time > self._config.end_time:
61
+ raise ClockError("Cannot run past end_time in backtest mode")
62
+
63
+ self._running = True
64
+ self._started = True
65
+ self._task = asyncio.create_task(
66
+ self._run_til_impl(target_time, self._current_context)
67
+ )
68
+
69
+ # Activate all processors
70
+ for processor in self._current_context:
71
+ state = self._processor_states[processor]
72
+ self._processor_states[processor] = state.model_copy(
73
+ update={"is_active": True}
74
+ )
75
+
76
+ try:
77
+ await self._task
78
+ finally:
79
+ self._task = None
80
+
81
+ async def _run_til_impl(
82
+ self, target_time: float, processors: list[TickProcessor]
83
+ ) -> None:
84
+ """Run the clock until a specific timestamp."""
85
+ if not self._running:
86
+ raise ClockError("Clock must be started in a context.")
87
+
88
+ processors = [p for p in processors if self._processor_states[p].is_active]
89
+ if not processors:
90
+ return
91
+
92
+ # Calculate number of ticks needed
93
+ num_ticks = int((target_time - self._current_tick) / self._config.tick_size)
94
+ if num_ticks <= 0:
95
+ return
96
+
97
+ # Execute ticks
98
+ for _ in range(num_ticks):
99
+ self._current_tick += self._config.tick_size
100
+ await self._execute_tick(processors)
101
+ self._tick_counter += 1
102
+
103
+ # Set final timestamp to exactly match target_time
104
+ if (
105
+ abs(self._current_tick - target_time) > 1e-10
106
+ ): # Handle floating point precision
107
+ self._current_tick = target_time
108
+ await self._execute_tick(processors)
109
+ self._tick_counter += 1
110
+
111
+ async def _execute_tick(self, processors: list[TickProcessor]) -> None:
112
+ """Execute a tick for all processors."""
113
+ if self._config.concurrent_processors:
114
+ # Execute processors concurrently
115
+ tasks = []
116
+ for processor in processors:
117
+ task = asyncio.create_task(
118
+ self._execute_processor(processor, self._current_tick)
119
+ )
120
+ tasks.append(task)
121
+
122
+ results = await asyncio.gather(*tasks, return_exceptions=True)
123
+ errors = []
124
+
125
+ # Update states for all processors first
126
+ for processor, result in zip(processors, results, strict=False):
127
+ if isinstance(result, Exception):
128
+ if self._error_callback:
129
+ self._error_callback(processor, result)
130
+ errors.append(result)
131
+ else:
132
+ # Update processor state after successful execution
133
+ state = self._processor_states[processor]
134
+ self._processor_states[processor] = state.model_copy(
135
+ update={"last_timestamp": self._current_tick}
136
+ )
137
+
138
+ # Raise the first error if any occurred
139
+ if errors:
140
+ raise errors[0]
141
+ else:
142
+ # Execute processors sequentially
143
+ for processor in processors:
144
+ try:
145
+ await self._execute_processor(processor, self._current_tick)
146
+ # Update processor state after successful execution
147
+ state = self._processor_states[processor]
148
+ self._processor_states[processor] = state.model_copy(
149
+ update={"last_timestamp": self._current_tick}
150
+ )
151
+ except Exception as e:
152
+ if self._error_callback:
153
+ self._error_callback(processor, e)
154
+ raise e
155
+
156
+ async def fast_forward(self, seconds: float) -> None:
157
+ """Fast forward the clock by a specified number of seconds."""
158
+ if not self._current_context:
159
+ raise ClockError("Fast forward can only be used within a context")
160
+
161
+ if seconds <= 0:
162
+ return
163
+
164
+ target_time = self._current_tick + seconds
165
+ if target_time > self._config.end_time:
166
+ raise ClockError("Cannot fast forward past end_time in backtest mode")
167
+
168
+ await self.run_til(target_time)