py10x-universe 0.1.3__py3-none-any.whl
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.
- core_10x/__init__.py +42 -0
- core_10x/backbone/__init__.py +0 -0
- core_10x/backbone/backbone_store.py +59 -0
- core_10x/backbone/backbone_traitable.py +30 -0
- core_10x/backbone/backbone_user.py +66 -0
- core_10x/backbone/bound_data_domain.py +49 -0
- core_10x/backbone/namespace.py +101 -0
- core_10x/backbone/vault.py +38 -0
- core_10x/code_samples/__init__.py +0 -0
- core_10x/code_samples/_package_manifest.py +3 -0
- core_10x/code_samples/directories.py +181 -0
- core_10x/code_samples/person.py +76 -0
- core_10x/concrete_traits.py +356 -0
- core_10x/conftest.py +12 -0
- core_10x/curve.py +321 -0
- core_10x/data_domain.py +48 -0
- core_10x/data_domain_binder.py +45 -0
- core_10x/directory.py +250 -0
- core_10x/entity.py +8 -0
- core_10x/entity_filter.py +5 -0
- core_10x/environment_variables.py +147 -0
- core_10x/exec_control.py +84 -0
- core_10x/experimental/__init__.py +0 -0
- core_10x/experimental/data_protocol_ex.py +34 -0
- core_10x/global_cache.py +121 -0
- core_10x/manual_tests/__init__.py +0 -0
- core_10x/manual_tests/calendar_test.py +35 -0
- core_10x/manual_tests/ctor_update_bug.py +58 -0
- core_10x/manual_tests/debug_graph_on.py +17 -0
- core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
- core_10x/manual_tests/enum_bits_test.py +17 -0
- core_10x/manual_tests/env_vars_trivial_test.py +12 -0
- core_10x/manual_tests/existing_traitable.py +33 -0
- core_10x/manual_tests/k10x_test1.py +13 -0
- core_10x/manual_tests/named_constant_test.py +121 -0
- core_10x/manual_tests/nucleus_trivial_test.py +42 -0
- core_10x/manual_tests/polars_test.py +14 -0
- core_10x/manual_tests/py_class_test.py +4 -0
- core_10x/manual_tests/rc_test.py +42 -0
- core_10x/manual_tests/rdate_test.py +12 -0
- core_10x/manual_tests/reference_serialization_bug.py +19 -0
- core_10x/manual_tests/resource_trivial_test.py +10 -0
- core_10x/manual_tests/store_uri_test.py +6 -0
- core_10x/manual_tests/trait_definition_test.py +19 -0
- core_10x/manual_tests/trait_filter_test.py +15 -0
- core_10x/manual_tests/trait_flag_modification_test.py +42 -0
- core_10x/manual_tests/trait_modification_bug.py +26 -0
- core_10x/manual_tests/traitable_as_of_test.py +82 -0
- core_10x/manual_tests/traitable_heir_test.py +39 -0
- core_10x/manual_tests/traitable_history_test.py +41 -0
- core_10x/manual_tests/traitable_serialization_test.py +54 -0
- core_10x/manual_tests/traitable_trivial_test.py +71 -0
- core_10x/manual_tests/trivial_graph_test.py +16 -0
- core_10x/manual_tests/ts_class_association_test.py +64 -0
- core_10x/manual_tests/ts_trivial_test.py +35 -0
- core_10x/named_constant.py +425 -0
- core_10x/nucleus.py +81 -0
- core_10x/package_manifest.py +85 -0
- core_10x/package_refactoring.py +153 -0
- core_10x/py_class.py +431 -0
- core_10x/rc.py +155 -0
- core_10x/rdate.py +339 -0
- core_10x/resource.py +189 -0
- core_10x/roman_number.py +67 -0
- core_10x/testlib/__init__.py +0 -0
- core_10x/testlib/test_store.py +240 -0
- core_10x/testlib/traitable_history_tests.py +787 -0
- core_10x/testlib/ts_tests.py +280 -0
- core_10x/trait.py +377 -0
- core_10x/trait_definition.py +176 -0
- core_10x/trait_filter.py +205 -0
- core_10x/trait_method_error.py +36 -0
- core_10x/traitable.py +1082 -0
- core_10x/traitable_cli.py +153 -0
- core_10x/traitable_heir.py +33 -0
- core_10x/traitable_id.py +31 -0
- core_10x/ts_store.py +172 -0
- core_10x/ts_store_type.py +26 -0
- core_10x/ts_union.py +147 -0
- core_10x/ui_hint.py +153 -0
- core_10x/unit_tests/test_concrete_traits.py +156 -0
- core_10x/unit_tests/test_converters.py +51 -0
- core_10x/unit_tests/test_curve.py +157 -0
- core_10x/unit_tests/test_directory.py +54 -0
- core_10x/unit_tests/test_documentation.py +172 -0
- core_10x/unit_tests/test_environment_variables.py +15 -0
- core_10x/unit_tests/test_filters.py +239 -0
- core_10x/unit_tests/test_graph.py +348 -0
- core_10x/unit_tests/test_named_constant.py +98 -0
- core_10x/unit_tests/test_rc.py +11 -0
- core_10x/unit_tests/test_rdate.py +484 -0
- core_10x/unit_tests/test_trait_method_error.py +80 -0
- core_10x/unit_tests/test_trait_modification.py +19 -0
- core_10x/unit_tests/test_traitable.py +959 -0
- core_10x/unit_tests/test_traitable_history.py +1 -0
- core_10x/unit_tests/test_ts_store.py +1 -0
- core_10x/unit_tests/test_ts_union.py +369 -0
- core_10x/unit_tests/test_ui_nodes.py +81 -0
- core_10x/unit_tests/test_xxcalendar.py +471 -0
- core_10x/vault/__init__.py +0 -0
- core_10x/vault/sec_keys.py +133 -0
- core_10x/vault/security_keys_old.py +168 -0
- core_10x/vault/vault.py +56 -0
- core_10x/vault/vault_traitable.py +56 -0
- core_10x/vault/vault_user.py +70 -0
- core_10x/xdate_time.py +136 -0
- core_10x/xnone.py +71 -0
- core_10x/xxcalendar.py +228 -0
- infra_10x/__init__.py +0 -0
- infra_10x/manual_tests/__init__.py +0 -0
- infra_10x/manual_tests/test_misc.py +16 -0
- infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
- infra_10x/mongodb_admin.py +111 -0
- infra_10x/mongodb_store.py +346 -0
- infra_10x/mongodb_utils.py +129 -0
- infra_10x/unit_tests/conftest.py +13 -0
- infra_10x/unit_tests/test_mongo_db.py +36 -0
- infra_10x/unit_tests/test_mongo_history.py +1 -0
- py10x_universe-0.1.3.dist-info/METADATA +406 -0
- py10x_universe-0.1.3.dist-info/RECORD +214 -0
- py10x_universe-0.1.3.dist-info/WHEEL +4 -0
- py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
- ui_10x/__init__.py +0 -0
- ui_10x/apps/__init__.py +0 -0
- ui_10x/apps/collection_editor_app.py +100 -0
- ui_10x/choice.py +212 -0
- ui_10x/collection_editor.py +135 -0
- ui_10x/concrete_trait_widgets.py +220 -0
- ui_10x/conftest.py +8 -0
- ui_10x/entity_stocker.py +173 -0
- ui_10x/examples/__init__.py +0 -0
- ui_10x/examples/_guess_word_data.py +14076 -0
- ui_10x/examples/collection_editor.py +17 -0
- ui_10x/examples/date_selector.py +14 -0
- ui_10x/examples/entity_stocker.py +18 -0
- ui_10x/examples/guess_word.py +392 -0
- ui_10x/examples/message_box.py +20 -0
- ui_10x/examples/multi_choice.py +17 -0
- ui_10x/examples/py_data_browser.py +66 -0
- ui_10x/examples/radiobox.py +29 -0
- ui_10x/examples/single_choice.py +31 -0
- ui_10x/examples/style_sheet.py +47 -0
- ui_10x/examples/trivial_entity_editor.py +18 -0
- ui_10x/platform.py +20 -0
- ui_10x/platform_interface.py +517 -0
- ui_10x/py_data_browser.py +249 -0
- ui_10x/qt6/__init__.py +0 -0
- ui_10x/qt6/conftest.py +8 -0
- ui_10x/qt6/manual_tests/__init__.py +0 -0
- ui_10x/qt6/manual_tests/basic_test.py +35 -0
- ui_10x/qt6/platform_implementation.py +275 -0
- ui_10x/qt6/utils.py +665 -0
- ui_10x/rio/__init__.py +0 -0
- ui_10x/rio/apps/examples/examples/__init__.py +22 -0
- ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
- ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
- ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
- ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
- ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
- ui_10x/rio/apps/examples/rio.toml +14 -0
- ui_10x/rio/component_builder.py +497 -0
- ui_10x/rio/components/__init__.py +9 -0
- ui_10x/rio/components/group_box.py +31 -0
- ui_10x/rio/components/labeled_checkbox.py +18 -0
- ui_10x/rio/components/line_edit.py +37 -0
- ui_10x/rio/components/radio_button.py +32 -0
- ui_10x/rio/components/separator.py +24 -0
- ui_10x/rio/components/splitter.py +121 -0
- ui_10x/rio/components/tree_view.py +75 -0
- ui_10x/rio/conftest.py +35 -0
- ui_10x/rio/internals/__init__.py +0 -0
- ui_10x/rio/internals/app.py +192 -0
- ui_10x/rio/manual_tests/__init__.py +0 -0
- ui_10x/rio/manual_tests/basic_test.py +24 -0
- ui_10x/rio/manual_tests/splitter.py +27 -0
- ui_10x/rio/platform_implementation.py +91 -0
- ui_10x/rio/style_sheet.py +53 -0
- ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
- ui_10x/rio/unit_tests/test_internals.py +630 -0
- ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
- ui_10x/rio/widgets/__init__.py +46 -0
- ui_10x/rio/widgets/application.py +109 -0
- ui_10x/rio/widgets/button.py +48 -0
- ui_10x/rio/widgets/button_group.py +60 -0
- ui_10x/rio/widgets/calendar.py +23 -0
- ui_10x/rio/widgets/checkbox.py +24 -0
- ui_10x/rio/widgets/dialog.py +137 -0
- ui_10x/rio/widgets/group_box.py +27 -0
- ui_10x/rio/widgets/layout.py +34 -0
- ui_10x/rio/widgets/line_edit.py +37 -0
- ui_10x/rio/widgets/list.py +105 -0
- ui_10x/rio/widgets/message_box.py +70 -0
- ui_10x/rio/widgets/scroll_area.py +31 -0
- ui_10x/rio/widgets/spacer.py +6 -0
- ui_10x/rio/widgets/splitter.py +45 -0
- ui_10x/rio/widgets/text_edit.py +28 -0
- ui_10x/rio/widgets/tree.py +89 -0
- ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
- ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
- ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
- ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
- ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
- ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
- ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
- ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
- ui_10x/table_header_view.py +305 -0
- ui_10x/table_view.py +174 -0
- ui_10x/trait_editor.py +189 -0
- ui_10x/trait_widget.py +131 -0
- ui_10x/traitable_editor.py +200 -0
- ui_10x/traitable_view.py +131 -0
- ui_10x/unit_tests/conftest.py +8 -0
- ui_10x/unit_tests/test_platform.py +9 -0
- ui_10x/utils.py +661 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from core_10x.rdate import (
|
|
7
|
+
BIZDAY_ROLL_RULE,
|
|
8
|
+
PROPAGATE_DATES,
|
|
9
|
+
TENOR_FREQUENCY,
|
|
10
|
+
RDate,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestRDate:
|
|
15
|
+
"""Unit tests for RDate class."""
|
|
16
|
+
|
|
17
|
+
def test_init_with_symbol(self):
|
|
18
|
+
"""Test RDate constructor with symbol strings."""
|
|
19
|
+
# Test basic symbols
|
|
20
|
+
rd = RDate('3M')
|
|
21
|
+
assert rd.freq == TENOR_FREQUENCY.MONTH
|
|
22
|
+
assert rd.count == 3
|
|
23
|
+
|
|
24
|
+
rd = RDate('1Y')
|
|
25
|
+
assert rd.freq == TENOR_FREQUENCY.YEAR
|
|
26
|
+
assert rd.count == 1
|
|
27
|
+
|
|
28
|
+
rd = RDate('6Q')
|
|
29
|
+
assert rd.freq == TENOR_FREQUENCY.QUARTER
|
|
30
|
+
assert rd.count == 6
|
|
31
|
+
|
|
32
|
+
rd = RDate('2W')
|
|
33
|
+
assert rd.freq == TENOR_FREQUENCY.WEEK
|
|
34
|
+
assert rd.count == 2
|
|
35
|
+
|
|
36
|
+
rd = RDate('5C')
|
|
37
|
+
assert rd.freq == TENOR_FREQUENCY.CALDAY
|
|
38
|
+
assert rd.count == 5
|
|
39
|
+
|
|
40
|
+
# Test negative values
|
|
41
|
+
rd = RDate('-2M')
|
|
42
|
+
assert rd.freq == TENOR_FREQUENCY.MONTH
|
|
43
|
+
assert rd.count == -2
|
|
44
|
+
|
|
45
|
+
def test_init_with_freq_and_count(self):
|
|
46
|
+
"""Test RDate constructor with frequency and count parameters."""
|
|
47
|
+
rd = RDate(freq=TENOR_FREQUENCY.MONTH, count=3)
|
|
48
|
+
assert rd.freq == TENOR_FREQUENCY.MONTH
|
|
49
|
+
assert rd.count == 3
|
|
50
|
+
|
|
51
|
+
# Test default count
|
|
52
|
+
rd = RDate(freq=TENOR_FREQUENCY.YEAR)
|
|
53
|
+
assert rd.freq == TENOR_FREQUENCY.YEAR
|
|
54
|
+
assert rd.count == 1
|
|
55
|
+
|
|
56
|
+
def test_init_invalid_symbol(self):
|
|
57
|
+
"""Test RDate constructor with invalid symbol raises AttributeError."""
|
|
58
|
+
with pytest.raises(AttributeError, match="Invalid tenor symbol '3X'"):
|
|
59
|
+
RDate('3X')
|
|
60
|
+
|
|
61
|
+
def test_init_missing_freq(self):
|
|
62
|
+
"""Test RDate constructor without freq parameter raises AssertionError."""
|
|
63
|
+
with pytest.raises(AssertionError, match='freq must be a valid TENOR_FREQUENCY'):
|
|
64
|
+
RDate(count=3)
|
|
65
|
+
|
|
66
|
+
def test_symbol(self):
|
|
67
|
+
"""Test symbol property returns correct string representation."""
|
|
68
|
+
rd = RDate('3M')
|
|
69
|
+
assert rd.symbol() == '3M'
|
|
70
|
+
|
|
71
|
+
rd = RDate('1Y')
|
|
72
|
+
assert rd.symbol() == '1Y'
|
|
73
|
+
|
|
74
|
+
rd = RDate('-2Q')
|
|
75
|
+
assert rd.symbol() == '-2Q'
|
|
76
|
+
|
|
77
|
+
def test_to_str(self):
|
|
78
|
+
"""Test to_str method returns symbol."""
|
|
79
|
+
rd = RDate('5C')
|
|
80
|
+
assert rd.to_str() == '5C'
|
|
81
|
+
|
|
82
|
+
def test_serialize_deserialize(self):
|
|
83
|
+
"""Test serialization and deserialization."""
|
|
84
|
+
rd = RDate('3M')
|
|
85
|
+
serialized = rd.serialize(embed=True)
|
|
86
|
+
assert serialized == '3M'
|
|
87
|
+
|
|
88
|
+
deserialized = RDate.deserialize(serialized)
|
|
89
|
+
assert deserialized.freq == rd.freq
|
|
90
|
+
assert deserialized.count == rd.count
|
|
91
|
+
|
|
92
|
+
def test_from_str(self):
|
|
93
|
+
"""Test from_str class method."""
|
|
94
|
+
rd = RDate.from_str('2Y')
|
|
95
|
+
assert rd.freq == TENOR_FREQUENCY.YEAR
|
|
96
|
+
assert rd.count == 2
|
|
97
|
+
|
|
98
|
+
def test_from_any_xstr(self):
|
|
99
|
+
"""Test from_any_xstr class method."""
|
|
100
|
+
# Test with RDate instance
|
|
101
|
+
rd1 = RDate('3M')
|
|
102
|
+
rd2 = RDate.from_any_xstr(rd1)
|
|
103
|
+
assert rd2.freq == rd1.freq
|
|
104
|
+
assert rd2.count == rd1.count
|
|
105
|
+
|
|
106
|
+
# Test with tuple
|
|
107
|
+
rd3 = RDate.from_any_xstr((TENOR_FREQUENCY.YEAR, 5))
|
|
108
|
+
assert rd3.freq == TENOR_FREQUENCY.YEAR
|
|
109
|
+
assert rd3.count == 5
|
|
110
|
+
|
|
111
|
+
# Test invalid type
|
|
112
|
+
with pytest.raises(AssertionError, match='unexpected type'):
|
|
113
|
+
RDate.from_any_xstr('invalid')
|
|
114
|
+
|
|
115
|
+
def test_same_values(self):
|
|
116
|
+
"""Test same_values class method."""
|
|
117
|
+
rd1 = RDate('3M')
|
|
118
|
+
rd2 = RDate('3M')
|
|
119
|
+
rd3 = RDate('6M')
|
|
120
|
+
|
|
121
|
+
assert RDate.same_values(rd1, rd2) is True
|
|
122
|
+
assert RDate.same_values(rd1, rd3) is False
|
|
123
|
+
|
|
124
|
+
def test_apply_basic(self):
|
|
125
|
+
"""Test apply method with basic calendar operations."""
|
|
126
|
+
rd = RDate('3M')
|
|
127
|
+
start_date = date(2023, 1, 15)
|
|
128
|
+
|
|
129
|
+
# Create a mock calendar that treats all days as business days
|
|
130
|
+
class MockCalendar:
|
|
131
|
+
def is_bizday(self, d):
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
def next_bizday(self, d):
|
|
135
|
+
return d
|
|
136
|
+
|
|
137
|
+
def prev_bizday(self, d):
|
|
138
|
+
return d
|
|
139
|
+
|
|
140
|
+
def advance_bizdays(self, d, count):
|
|
141
|
+
from datetime import timedelta
|
|
142
|
+
|
|
143
|
+
return d + timedelta(days=count)
|
|
144
|
+
|
|
145
|
+
cal = MockCalendar()
|
|
146
|
+
|
|
147
|
+
# Test with NO_ROLL rule
|
|
148
|
+
result = rd.apply(start_date, cal, BIZDAY_ROLL_RULE.NO_ROLL)
|
|
149
|
+
expected = date(2023, 4, 15) # 3 months later
|
|
150
|
+
assert result == expected
|
|
151
|
+
|
|
152
|
+
def test_apply_bizday(self):
|
|
153
|
+
"""Test apply method with business day frequency."""
|
|
154
|
+
rd = RDate(freq=TENOR_FREQUENCY.BIZDAY, count=5)
|
|
155
|
+
|
|
156
|
+
class MockCalendar:
|
|
157
|
+
def is_bizday(self, d):
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
def next_bizday(self, d):
|
|
161
|
+
return d
|
|
162
|
+
|
|
163
|
+
def prev_bizday(self, d):
|
|
164
|
+
return d
|
|
165
|
+
|
|
166
|
+
def advance_bizdays(self, d, count):
|
|
167
|
+
from datetime import timedelta
|
|
168
|
+
|
|
169
|
+
return d + timedelta(days=count)
|
|
170
|
+
|
|
171
|
+
cal = MockCalendar()
|
|
172
|
+
|
|
173
|
+
start_date = date(2023, 1, 15)
|
|
174
|
+
result = rd.apply(start_date, cal, BIZDAY_ROLL_RULE.NO_ROLL)
|
|
175
|
+
expected = date(2023, 1, 20) # 5 business days later
|
|
176
|
+
assert result == expected
|
|
177
|
+
|
|
178
|
+
def test_apply_rule(self):
|
|
179
|
+
"""Test apply_rule class method."""
|
|
180
|
+
start_date = date(2023, 1, 15)
|
|
181
|
+
|
|
182
|
+
class MockCalendar:
|
|
183
|
+
def is_bizday(self, d):
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
def next_bizday(self, d):
|
|
187
|
+
return d
|
|
188
|
+
|
|
189
|
+
def prev_bizday(self, d):
|
|
190
|
+
return d
|
|
191
|
+
|
|
192
|
+
def advance_bizdays(self, d, count):
|
|
193
|
+
from datetime import timedelta
|
|
194
|
+
|
|
195
|
+
return d + timedelta(days=count)
|
|
196
|
+
|
|
197
|
+
cal = MockCalendar()
|
|
198
|
+
|
|
199
|
+
# Apply multiple tenors: 3M then 1Y
|
|
200
|
+
result = RDate.apply_rule(start_date, cal, BIZDAY_ROLL_RULE.NO_ROLL, '3M,1Y')
|
|
201
|
+
expected = date(2024, 4, 15) # 3M from Jan 15 = Apr 15, then 1Y = Apr 15 2024
|
|
202
|
+
assert result == expected
|
|
203
|
+
|
|
204
|
+
def test_conversion_freq_multiplier(self):
|
|
205
|
+
"""Test conversion_freq_multiplier method."""
|
|
206
|
+
# Same frequency
|
|
207
|
+
rd1 = RDate('3M')
|
|
208
|
+
rd2 = RDate('6M')
|
|
209
|
+
assert rd1.conversion_freq_multiplier(rd2.freq) == 1.0
|
|
210
|
+
|
|
211
|
+
# Different frequencies
|
|
212
|
+
rd_year = RDate('1Y')
|
|
213
|
+
rd_month = RDate('12M')
|
|
214
|
+
assert rd_year.conversion_freq_multiplier(rd_month.freq) == 12
|
|
215
|
+
|
|
216
|
+
rd_quarter = RDate('1Q')
|
|
217
|
+
assert rd_year.conversion_freq_multiplier(rd_quarter.freq) == 4
|
|
218
|
+
|
|
219
|
+
# Invalid conversions
|
|
220
|
+
rd_bizday = RDate(freq=TENOR_FREQUENCY.BIZDAY, count=1)
|
|
221
|
+
with pytest.raises(ValueError, match='cannot convert'):
|
|
222
|
+
rd_year.conversion_freq_multiplier(rd_bizday.freq)
|
|
223
|
+
|
|
224
|
+
def test_equate_freq(self):
|
|
225
|
+
"""Test equate_freq method."""
|
|
226
|
+
# Same frequency
|
|
227
|
+
rd1 = RDate('3M')
|
|
228
|
+
rd2 = RDate('6M')
|
|
229
|
+
eq1, eq2 = rd1.equate_freq(rd2)
|
|
230
|
+
assert eq1.freq == eq2.freq == TENOR_FREQUENCY.MONTH
|
|
231
|
+
assert eq1.count == 3
|
|
232
|
+
assert eq2.count == 6
|
|
233
|
+
|
|
234
|
+
# Different frequencies - convert to common frequency
|
|
235
|
+
rd_year = RDate('1Y')
|
|
236
|
+
rd_month = RDate('12M')
|
|
237
|
+
eq1, eq2 = rd_year.equate_freq(rd_month)
|
|
238
|
+
assert eq1.freq == eq2.freq == TENOR_FREQUENCY.MONTH
|
|
239
|
+
assert eq1.count == 12 # 1Y = 12M
|
|
240
|
+
assert eq2.count == 12
|
|
241
|
+
|
|
242
|
+
def test_multadd(self):
|
|
243
|
+
"""Test multadd method."""
|
|
244
|
+
rd = RDate('6M')
|
|
245
|
+
|
|
246
|
+
# Multiply by scalar
|
|
247
|
+
result = rd.multadd(2.0, 0)
|
|
248
|
+
assert result.freq == TENOR_FREQUENCY.MONTH
|
|
249
|
+
assert result.count == 12
|
|
250
|
+
|
|
251
|
+
# Add another RDate
|
|
252
|
+
rd2 = RDate('3M')
|
|
253
|
+
result = rd.multadd(1.0, rd2)
|
|
254
|
+
assert result.freq == TENOR_FREQUENCY.MONTH
|
|
255
|
+
assert result.count == 9
|
|
256
|
+
|
|
257
|
+
# Invalid addition
|
|
258
|
+
with pytest.raises(ValueError, match='cannot add a non-zero number'):
|
|
259
|
+
rd.multadd(1.0, 5)
|
|
260
|
+
|
|
261
|
+
# Invalid type
|
|
262
|
+
with pytest.raises(ValueError, match='cannot calc a linear combination'):
|
|
263
|
+
rd.multadd(1.0, [])
|
|
264
|
+
|
|
265
|
+
def test_mathematical_operations(self):
|
|
266
|
+
"""Test mathematical operations (__mul__, __rmul__, __truediv__, __add__)."""
|
|
267
|
+
rd = RDate('6M')
|
|
268
|
+
|
|
269
|
+
# Multiplication
|
|
270
|
+
result = rd * 2
|
|
271
|
+
assert result.freq == TENOR_FREQUENCY.MONTH
|
|
272
|
+
assert result.count == 12
|
|
273
|
+
|
|
274
|
+
result = 3 * rd
|
|
275
|
+
assert result.freq == TENOR_FREQUENCY.MONTH
|
|
276
|
+
assert result.count == 18
|
|
277
|
+
|
|
278
|
+
# Division by scalar
|
|
279
|
+
result = rd / 2
|
|
280
|
+
assert result.freq == TENOR_FREQUENCY.MONTH
|
|
281
|
+
assert result.count == 3
|
|
282
|
+
|
|
283
|
+
# Division by another RDate
|
|
284
|
+
rd2 = RDate('3M')
|
|
285
|
+
result = rd / rd2
|
|
286
|
+
assert result == 2.0
|
|
287
|
+
|
|
288
|
+
# Addition
|
|
289
|
+
result = rd + rd2
|
|
290
|
+
assert result.freq == TENOR_FREQUENCY.MONTH
|
|
291
|
+
assert result.count == 9
|
|
292
|
+
|
|
293
|
+
def test_from_tenors(self):
|
|
294
|
+
"""Test from_tenors class method."""
|
|
295
|
+
tenors = RDate.from_tenors('3M,1Y,6Q')
|
|
296
|
+
assert len(tenors) == 3
|
|
297
|
+
assert tenors[0].symbol() == '3M'
|
|
298
|
+
assert tenors[1].symbol() == '1Y'
|
|
299
|
+
assert tenors[2].symbol() == '6Q'
|
|
300
|
+
|
|
301
|
+
# Test with custom delimiter
|
|
302
|
+
tenors = RDate.from_tenors('3M;1Y;6Q', delim=';')
|
|
303
|
+
assert len(tenors) == 3
|
|
304
|
+
|
|
305
|
+
def test_add_bizdays(self):
|
|
306
|
+
"""Test add_bizdays class method."""
|
|
307
|
+
start_date = date(2023, 1, 15)
|
|
308
|
+
|
|
309
|
+
class MockCalendar:
|
|
310
|
+
def is_bizday(self, d):
|
|
311
|
+
return True
|
|
312
|
+
|
|
313
|
+
def next_bizday(self, d):
|
|
314
|
+
return d
|
|
315
|
+
|
|
316
|
+
def prev_bizday(self, d):
|
|
317
|
+
return d
|
|
318
|
+
|
|
319
|
+
def advance_bizdays(self, d, count):
|
|
320
|
+
from datetime import timedelta
|
|
321
|
+
|
|
322
|
+
return d + timedelta(days=count)
|
|
323
|
+
|
|
324
|
+
cal = MockCalendar()
|
|
325
|
+
|
|
326
|
+
result = RDate.add_bizdays(start_date, 5, cal, BIZDAY_ROLL_RULE.NO_ROLL)
|
|
327
|
+
expected = date(2023, 1, 20)
|
|
328
|
+
assert result == expected
|
|
329
|
+
|
|
330
|
+
def test_roll_to_bizday(self):
|
|
331
|
+
"""Test roll_to_bizday class method."""
|
|
332
|
+
test_date = date(2023, 1, 15)
|
|
333
|
+
|
|
334
|
+
class MockCalendar:
|
|
335
|
+
def is_bizday(self, d):
|
|
336
|
+
return True
|
|
337
|
+
|
|
338
|
+
def next_bizday(self, d):
|
|
339
|
+
return d
|
|
340
|
+
|
|
341
|
+
def prev_bizday(self, d):
|
|
342
|
+
return d
|
|
343
|
+
|
|
344
|
+
cal = MockCalendar()
|
|
345
|
+
|
|
346
|
+
result = RDate.roll_to_bizday(test_date, cal, BIZDAY_ROLL_RULE.NO_ROLL)
|
|
347
|
+
assert result == test_date # NO_ROLL returns the date as-is
|
|
348
|
+
|
|
349
|
+
def test_relop(self):
|
|
350
|
+
"""Test relop class method."""
|
|
351
|
+
rd1 = RDate('3M')
|
|
352
|
+
rd2 = RDate('6M')
|
|
353
|
+
test_date = date(2023, 1, 15)
|
|
354
|
+
|
|
355
|
+
class MockCalendar:
|
|
356
|
+
def is_bizday(self, d):
|
|
357
|
+
return True
|
|
358
|
+
|
|
359
|
+
def next_bizday(self, d):
|
|
360
|
+
return d
|
|
361
|
+
|
|
362
|
+
def prev_bizday(self, d):
|
|
363
|
+
return d
|
|
364
|
+
|
|
365
|
+
cal = MockCalendar()
|
|
366
|
+
|
|
367
|
+
# Test less than
|
|
368
|
+
result = RDate.relop(RDate.RELOP.LT, rd1, rd2, test_date, cal, BIZDAY_ROLL_RULE.NO_ROLL)
|
|
369
|
+
# 3M from Jan 15 = Apr 15, 6M from Jan 15 = Jul 15, so Apr 15 < Jul 15
|
|
370
|
+
assert result is True
|
|
371
|
+
|
|
372
|
+
# Test equal
|
|
373
|
+
rd3 = RDate('3M')
|
|
374
|
+
result = RDate.relop(RDate.RELOP.EQ, rd1, rd3, test_date, cal, BIZDAY_ROLL_RULE.NO_ROLL)
|
|
375
|
+
assert result is True
|
|
376
|
+
|
|
377
|
+
def test_dates_schedule_forward(self):
|
|
378
|
+
"""Test dates_schedule method with forward propagation."""
|
|
379
|
+
rd = RDate('3M')
|
|
380
|
+
start_date = date(2023, 1, 15)
|
|
381
|
+
end_date = date(2023, 7, 15)
|
|
382
|
+
|
|
383
|
+
class MockCalendar:
|
|
384
|
+
def is_bizday(self, d):
|
|
385
|
+
return True
|
|
386
|
+
|
|
387
|
+
def next_bizday(self, d):
|
|
388
|
+
return d
|
|
389
|
+
|
|
390
|
+
def prev_bizday(self, d):
|
|
391
|
+
return d
|
|
392
|
+
|
|
393
|
+
cal = MockCalendar()
|
|
394
|
+
|
|
395
|
+
dates = rd.dates_schedule(start_date, end_date, cal, BIZDAY_ROLL_RULE.NO_ROLL, PROPAGATE_DATES.FORWARD, allow_stub=True)
|
|
396
|
+
|
|
397
|
+
# Should generate dates: Jan 15, Apr 15, Jul 15
|
|
398
|
+
expected = [date(2023, 1, 15), date(2023, 4, 15), date(2023, 7, 15)]
|
|
399
|
+
assert dates == expected
|
|
400
|
+
|
|
401
|
+
def test_dates_schedule_backward(self):
|
|
402
|
+
"""Test dates_schedule method with backward propagation."""
|
|
403
|
+
rd = RDate('3M')
|
|
404
|
+
start_date = date(2023, 1, 15)
|
|
405
|
+
end_date = date(2023, 7, 15)
|
|
406
|
+
|
|
407
|
+
class MockCalendar:
|
|
408
|
+
def is_bizday(self, d):
|
|
409
|
+
return True
|
|
410
|
+
|
|
411
|
+
def next_bizday(self, d):
|
|
412
|
+
return d
|
|
413
|
+
|
|
414
|
+
def prev_bizday(self, d):
|
|
415
|
+
return d
|
|
416
|
+
|
|
417
|
+
cal = MockCalendar()
|
|
418
|
+
|
|
419
|
+
dates = rd.dates_schedule(start_date, end_date, cal, BIZDAY_ROLL_RULE.NO_ROLL, PROPAGATE_DATES.BACKWARD, allow_stub=True)
|
|
420
|
+
|
|
421
|
+
# Should generate dates: Jul 15, Apr 15, Jan 15 (backward from end)
|
|
422
|
+
expected = [date(2023, 1, 15), date(2023, 4, 15), date(2023, 7, 15)]
|
|
423
|
+
assert dates == expected
|
|
424
|
+
|
|
425
|
+
def test_period_dates(self):
|
|
426
|
+
"""Test period_dates method."""
|
|
427
|
+
rd = RDate('3M')
|
|
428
|
+
start_date = date(2023, 1, 15)
|
|
429
|
+
end_date = date(2023, 7, 15)
|
|
430
|
+
|
|
431
|
+
class MockCalendar:
|
|
432
|
+
def is_bizday(self, d):
|
|
433
|
+
return True
|
|
434
|
+
|
|
435
|
+
def next_bizday(self, d):
|
|
436
|
+
return d
|
|
437
|
+
|
|
438
|
+
def prev_bizday(self, d):
|
|
439
|
+
return d
|
|
440
|
+
|
|
441
|
+
cal = MockCalendar()
|
|
442
|
+
|
|
443
|
+
start_dates, end_dates, all_dates = rd.period_dates(
|
|
444
|
+
start_date, end_date, cal, BIZDAY_ROLL_RULE.NO_ROLL, PROPAGATE_DATES.FORWARD, allow_stub=True
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
expected_all = [date(2023, 1, 15), date(2023, 4, 15), date(2023, 7, 15)]
|
|
448
|
+
expected_starts = [date(2023, 1, 15), date(2023, 4, 15)]
|
|
449
|
+
expected_ends = [date(2023, 4, 15), date(2023, 7, 15)]
|
|
450
|
+
|
|
451
|
+
assert all_dates == expected_all
|
|
452
|
+
assert start_dates == expected_starts
|
|
453
|
+
assert end_dates == expected_ends
|
|
454
|
+
|
|
455
|
+
def test_period_dates_for_tenor(self):
|
|
456
|
+
"""Test period_dates_for_tenor method."""
|
|
457
|
+
rd = RDate('3M') # frequency
|
|
458
|
+
tenor = RDate('9M') # total tenor
|
|
459
|
+
start_date = date(2023, 1, 15)
|
|
460
|
+
|
|
461
|
+
class MockCalendar:
|
|
462
|
+
def is_bizday(self, d):
|
|
463
|
+
return True
|
|
464
|
+
|
|
465
|
+
def next_bizday(self, d):
|
|
466
|
+
return d
|
|
467
|
+
|
|
468
|
+
def prev_bizday(self, d):
|
|
469
|
+
return d
|
|
470
|
+
|
|
471
|
+
cal = MockCalendar()
|
|
472
|
+
|
|
473
|
+
start_dates, end_dates, all_dates = rd.period_dates_for_tenor(
|
|
474
|
+
start_date, tenor, cal, BIZDAY_ROLL_RULE.NO_ROLL, PROPAGATE_DATES.FORWARD, allow_stub=True
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# Start at Jan 15, end at Oct 15 (9M later), with 3M periods
|
|
478
|
+
expected_all = [date(2023, 1, 15), date(2023, 4, 15), date(2023, 7, 15), date(2023, 10, 15)]
|
|
479
|
+
expected_starts = [date(2023, 1, 15), date(2023, 4, 15), date(2023, 7, 15)]
|
|
480
|
+
expected_ends = [date(2023, 4, 15), date(2023, 7, 15), date(2023, 10, 15)]
|
|
481
|
+
|
|
482
|
+
assert all_dates == expected_all
|
|
483
|
+
assert start_dates == expected_starts
|
|
484
|
+
assert end_dates == expected_ends
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from core_10x.rc import RC
|
|
6
|
+
from core_10x.trait_definition import T
|
|
7
|
+
from core_10x.trait_method_error import TraitMethodError
|
|
8
|
+
from core_10x.traitable import Traitable, AnonymousTraitable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
OUTPUTS = ['exception', 'object', 'args', 'value']
|
|
12
|
+
GROUPS = [
|
|
13
|
+
{'serialize_nx': lambda x: x.serialize_object(False)},
|
|
14
|
+
{'z_deserialize': lambda x: x.__class__.deserialize({'z': 2000}), 'y_get': lambda x: x.y},
|
|
15
|
+
{'x_get': lambda x: x.x(0)},
|
|
16
|
+
{'x_set': lambda x: x.set_value('x', 100, 10)},
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
bombing_methods = {'_'.join(OUTPUTS[: i + 1]): group for i, group in enumerate(GROUPS)}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.parametrize(argnames=['cnt', 'key'], argvalues=enumerate(bombing_methods.keys()))
|
|
23
|
+
def test_trait_method_error(cnt, key):
|
|
24
|
+
class X(AnonymousTraitable):
|
|
25
|
+
x: int
|
|
26
|
+
y: int
|
|
27
|
+
z: int = T(1000)
|
|
28
|
+
t: Traitable = T()
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def bombing_method(cls, x):
|
|
32
|
+
raise KeyError(x + 1)
|
|
33
|
+
|
|
34
|
+
def x_get(self, x):
|
|
35
|
+
return self.bombing_method(x)
|
|
36
|
+
|
|
37
|
+
def y_get(self):
|
|
38
|
+
return self.bombing_method(0)
|
|
39
|
+
|
|
40
|
+
def x_set(self, trait, value, x) -> RC:
|
|
41
|
+
return self.bombing_method(value)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def y_serialize(cls, value):
|
|
45
|
+
return cls.bombing_method(value)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def y_deserialize(cls, trait, value):
|
|
49
|
+
return cls.bombing_method(value)
|
|
50
|
+
|
|
51
|
+
x = X()
|
|
52
|
+
x.t = X()
|
|
53
|
+
for m, f in bombing_methods[key].items():
|
|
54
|
+
try:
|
|
55
|
+
f(x)
|
|
56
|
+
except TraitMethodError as e:
|
|
57
|
+
e_str = str(e)
|
|
58
|
+
if cnt:
|
|
59
|
+
assert 'KeyError' in e_str
|
|
60
|
+
else:
|
|
61
|
+
assert 'TypeError' in e_str
|
|
62
|
+
assert f'value = {x.t}' in e_str
|
|
63
|
+
assert (
|
|
64
|
+
"original exception = TypeError: test_trait_method_error.<locals>.X - anonymous' instance may not be serialized as external reference"
|
|
65
|
+
in e_str
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
for i, output in enumerate(key.split('_')):
|
|
69
|
+
expected = i < cnt + 1
|
|
70
|
+
if cnt or (not expected and output != 'value'):
|
|
71
|
+
assert (f' {output} =' in e_str) is expected, f'did{" not" if expected else ""} find {output} for {key} in {e_str}'
|
|
72
|
+
if cnt:
|
|
73
|
+
assert f"Failed in <class 'test_trait_method_error.test_trait_method_error.<locals>.X'>.{m.split('_')[0]}.{m}" in e_str
|
|
74
|
+
|
|
75
|
+
tb_str = traceback.format_exc()
|
|
76
|
+
if cnt:
|
|
77
|
+
assert 'f(x)' in tb_str
|
|
78
|
+
assert f"'{m}': lambda x: x." in tb_str
|
|
79
|
+
assert 'bombing_method(' in tb_str
|
|
80
|
+
assert 'raise KeyError(x + 1)' in tb_str
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from core_10x.traitable import RT, M, Trait, Traitable
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class A(Traitable):
|
|
5
|
+
name: str = RT()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class B(A):
|
|
9
|
+
name: int = M()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def name_ui_flags(cls):
|
|
13
|
+
t: Trait = cls.trait('name')
|
|
14
|
+
return t.ui_hint.flags
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_uihint_modification():
|
|
18
|
+
flags = (name_ui_flags(A), name_ui_flags(B))
|
|
19
|
+
assert flags[0] == flags[1]
|