From: Jérôme Benoit Date: Sun, 8 Nov 2026 23:34:52 +0000 (+0100) Subject: feat(quickadapter): replace pi_extensions take-profit series with equal-division... X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=f80aed115300f962661c81ed044b5e41f0a8f915;p=freqai-strategies.git feat(quickadapter): replace pi_extensions take-profit series with equal-division and Gann octaves - Add `equal_division` (k/(n+1)) and `gann_octaves` (Gann octave/third levels closest to the full move, capped at nine) series; remove `pi_extensions` (near-entry bias, no trading basis). - Rename `fibonacci_extensions` -> `fibonacci_retracements` (fractions are all < 1 = retracements, not extensions > 1); not backward compatible. - Dispatch the series by `TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES[i]` index through a typed table (project enum-tuple convention). - Bump version 3.13.2 -> 3.13.3. --- diff --git a/quickadapter/.basedpyright/diagnostics.json b/quickadapter/.basedpyright/diagnostics.json index 144f8a6..342b0e6 100644 --- a/quickadapter/.basedpyright/diagnostics.json +++ b/quickadapter/.basedpyright/diagnostics.json @@ -1603,523 +1603,523 @@ }, { "endCharacter": 98, - "endLine": 1739, + "endLine": 1768, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Type \"bool | bool_\" is not assignable to return type \"bool\"\n  Type \"bool | bool_\" is not assignable to type \"bool\"\n    \"numpy.bool[builtins.bool]\" is not assignable to \"bool\"", "rule": "reportReturnType", "severity": "error", "startCharacter": 15, - "startLine": 1739 + "startLine": 1768 }, { "endCharacter": 33, - "endLine": 1773, + "endLine": 1802, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"ScalarOrNA | bool_ | Period | Interval[Any]\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"ScalarOrNA | bool_ | Period | Interval[Any]\" is not assignable to type \"ConvertibleToInt\"\n    Type \"NAType\" is not assignable to type \"ConvertibleToInt\"\n      \"NAType\" is not assignable to \"str\"\n      \"NAType\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"NAType\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"NAType\" is incompatible with protocol \"SupportsIndex\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 20, - "startLine": 1773 + "startLine": 1802 }, { "endCharacter": 33, - "endLine": 1773, + "endLine": 1802, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"ScalarOrNA | bool_ | Period | Interval[Any]\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"ScalarOrNA | bool_ | Period | Interval[Any]\" is not assignable to type \"ConvertibleToInt\"\n    Type \"NAType\" is not assignable to type \"ConvertibleToInt\"\n      \"NAType\" is not assignable to \"str\"\n      \"NAType\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"NAType\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"NAType\" is incompatible with protocol \"SupportsIndex\"\n ...", "rule": "reportArgumentType", "severity": "error", "startCharacter": 20, - "startLine": 1773 + "startLine": 1802 }, { "endCharacter": 23, - "endLine": 1785, + "endLine": 1814, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Type \"float\" is not assignable to return type \"T@get_distance\"\n  Type \"float\" is not assignable to type \"T@get_distance\"", "rule": "reportReturnType", "severity": "error", "startCharacter": 11, - "startLine": 1785 + "startLine": 1814 }, { "endCharacter": 83, - "endLine": 2638, + "endLine": 2667, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"str\" cannot be assigned to parameter \"key\" of type \"CombinedMetric\" in function \"__getitem__\"\n  Type \"str\" is not assignable to type \"CombinedMetric\"\n    \"str\" is not assignable to type \"Literal['amplitude']\"\n    \"str\" is not assignable to type \"Literal['amplitude_threshold_ratio']\"\n    \"str\" is not assignable to type \"Literal['volume_rate']\"\n    \"str\" is not assignable to type \"Literal['speed']\"\n    \"str\" is not assignable to type \"Literal['efficiency_ratio']\"\n    \"str\" is not assignable to type \"Literal['volume_weighted_efficiency_ratio']\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 58, - "startLine": 2638 + "startLine": 2667 }, { "endCharacter": 38, - "endLine": 3528, + "endLine": 3557, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Cannot access attribute \"__func__\" for class \"FunctionType\"\n  Attribute \"__func__\" is unknown", "rule": "reportFunctionMemberAccess", "severity": "error", "startCharacter": 30, - "startLine": 3528 + "startLine": 3557 }, { "endCharacter": 34, - "endLine": 3530, + "endLine": 3559, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Cannot access attribute \"__func__\" for class \"FunctionType\"\n  Attribute \"__func__\" is unknown", "rule": "reportFunctionMemberAccess", "severity": "error", "startCharacter": 26, - "startLine": 3530 + "startLine": 3559 }, { "endCharacter": 55, - "endLine": 3760, + "endLine": 3789, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"rolling\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 48, - "startLine": 3760 + "startLine": 3789 }, { "endCharacter": 38, - "endLine": 3785, + "endLine": 3814, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"rolling\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 31, - "startLine": 3785 + "startLine": 3814 }, { "endCharacter": 55, - "endLine": 3811, + "endLine": 3840, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"rolling\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 48, - "startLine": 3811 + "startLine": 3840 }, { "endCharacter": 56, - "endLine": 3812, + "endLine": 3841, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"rolling\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 49, - "startLine": 3812 + "startLine": 3841 }, { "endCharacter": 33, - "endLine": 3884, + "endLine": 3913, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"Series[Any] | NDArray[floating[Any]]\" cannot be assigned to parameter \"series\" of type \"Series[Any]\" in function \"calculate_zero_lag\"\n  Type \"Series[Any] | NDArray[floating[Any]]\" is not assignable to type \"Series[Any]\"\n    \"ndarray[_AnyShape, dtype[floating[Any]]]\" is not assignable to \"Series[Any]\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 27, - "startLine": 3884 + "startLine": 3913 }, { "endCharacter": 69, - "endLine": 3884, + "endLine": 3913, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Expected 1 more positional argument", "rule": "reportCallIssue", "severity": "error", "startCharacter": 59, - "startLine": 3884 + "startLine": 3913 }, { "endCharacter": 40, - "endLine": 3951, + "endLine": 3980, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"Series[Any] | None\" cannot be assigned to parameter \"series\" of type \"Series[Any]\" in function \"calculate_zero_lag\"\n  Type \"Series[Any] | None\" is not assignable to type \"Series[Any]\"\n    \"None\" is not assignable to \"Series[Any]\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 35, - "startLine": 3951 + "startLine": 3980 }, { "endCharacter": 38, - "endLine": 3952, + "endLine": 3981, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"Series[Any] | None\" cannot be assigned to parameter \"series\" of type \"Series[Any]\" in function \"calculate_zero_lag\"\n  Type \"Series[Any] | None\" is not assignable to type \"Series[Any]\"\n    \"None\" is not assignable to \"Series[Any]\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 34, - "startLine": 3952 + "startLine": 3981 }, { "endCharacter": 42, - "endLine": 3953, + "endLine": 3982, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"Series[Any] | None\" cannot be assigned to parameter \"series\" of type \"Series[Any]\" in function \"calculate_zero_lag\"\n  Type \"Series[Any] | None\" is not assignable to type \"Series[Any]\"\n    \"None\" is not assignable to \"Series[Any]\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 36, - "startLine": 3953 + "startLine": 3982 }, { "endCharacter": 45, - "endLine": 3955, + "endLine": 3984, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"index\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 40, - "startLine": 3955 + "startLine": 3984 }, { "endCharacter": 33, - "endLine": 3957, + "endLine": 3986, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"iloc\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 29, - "startLine": 3957 + "startLine": 3986 }, { "endCharacter": 31, - "endLine": 3958, + "endLine": 3987, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"iloc\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 27, - "startLine": 3958 + "startLine": 3987 }, { "endCharacter": 48, - "endLine": 3963, + "endLine": 3992, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"index\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 43, - "startLine": 3963 + "startLine": 3992 }, { "endCharacter": 40, - "endLine": 3964, + "endLine": 3993, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"iloc\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 36, - "startLine": 3964 + "startLine": 3993 }, { "endCharacter": 59, - "endLine": 3966, + "endLine": 3995, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Cannot access attribute \"iloc\" for class \"NDArray[Any]\"\n  Attribute \"iloc\" is unknown", "rule": "reportAttributeAccessIssue", "severity": "error", "startCharacter": 55, - "startLine": 3966 + "startLine": 3995 }, { "endCharacter": 34, - "endLine": 3968, + "endLine": 3997, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Cannot access attribute \"iloc\" for class \"NDArray[Any]\"\n  Attribute \"iloc\" is unknown", "rule": "reportAttributeAccessIssue", "severity": "error", "startCharacter": 30, - "startLine": 3968 + "startLine": 3997 }, { "endCharacter": 51, - "endLine": 3968, + "endLine": 3997, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"iloc\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 47, - "startLine": 3968 + "startLine": 3997 }, { "endCharacter": 72, - "endLine": 3968, + "endLine": 3997, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Cannot access attribute \"iloc\" for class \"NDArray[Any]\"\n  Attribute \"iloc\" is unknown", "rule": "reportAttributeAccessIssue", "severity": "error", "startCharacter": 68, - "startLine": 3968 + "startLine": 3997 }, { "endCharacter": 40, - "endLine": 4035, + "endLine": 4064, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Type \"(Series[Any] | NDArray[floating[Any]], int) -> (Series[Any] | NDArray[floating[Any]])\" is not assignable to declared type \"(series: Unknown, timeperiod: Unknown) -> Series[Any]\"\n  Type \"(Series[Any] | NDArray[floating[Any]], int) -> (Series[Any] | NDArray[floating[Any]])\" is not assignable to type \"(series: Unknown, timeperiod: Unknown) -> Series[Any]\"\n    Missing keyword parameter \"series\"\n    Missing keyword parameter \"timeperiod\"\n      Position-only parameter mismatch; parameter \"series\" is not position-only\n      Position-only parameter mismatch; parameter \"timeperiod\" is not position-only\n      Position-only parameter mismatch; expected 2 but received 0\n      Function return type \"Series[Any] | NDArray[floating[Any]]\" is incompatible with type \"Series[Any]\"\n        Type \"Series[Any] | NDArray[floating[Any]]\" is not assignable to type \"Series[Any]\"\n ...", "rule": "reportAssignmentType", "severity": "error", "startCharacter": 20, - "startLine": 4035 + "startLine": 4064 }, { "endCharacter": 33, - "endLine": 4037, + "endLine": 4066, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Type \"(Series[Any] | NDArray[floating[Any]], int) -> (Series[Any] | NDArray[floating[Any]])\" is not assignable to declared type \"(series: Unknown, timeperiod: Unknown) -> Series[Any]\"\n  Type \"(Series[Any] | NDArray[floating[Any]], int) -> (Series[Any] | NDArray[floating[Any]])\" is not assignable to type \"(series: Unknown, timeperiod: Unknown) -> Series[Any]\"\n    Missing keyword parameter \"series\"\n    Missing keyword parameter \"timeperiod\"\n      Position-only parameter mismatch; parameter \"series\" is not position-only\n      Position-only parameter mismatch; parameter \"timeperiod\" is not position-only\n      Position-only parameter mismatch; expected 2 but received 0\n      Function return type \"Series[Any] | NDArray[floating[Any]]\" is incompatible with type \"Series[Any]\"\n        Type \"Series[Any] | NDArray[floating[Any]]\" is not assignable to type \"Series[Any]\"\n ...", "rule": "reportAssignmentType", "severity": "error", "startCharacter": 16, - "startLine": 4037 + "startLine": 4066 }, { "endCharacter": 35, - "endLine": 4083, + "endLine": 4112, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"to_numpy\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 27, - "startLine": 4083 + "startLine": 4112 }, { "endCharacter": 33, - "endLine": 4084, + "endLine": 4113, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"to_numpy\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 25, - "startLine": 4084 + "startLine": 4113 }, { "endCharacter": 37, - "endLine": 4201, + "endLine": 4230, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"to_numpy\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 29, - "startLine": 4201 + "startLine": 4230 }, { "endCharacter": 35, - "endLine": 4202, + "endLine": 4231, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"to_numpy\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 27, - "startLine": 4202 + "startLine": 4231 }, { "endCharacter": 33, - "endLine": 4203, + "endLine": 4232, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"to_numpy\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 25, - "startLine": 4203 + "startLine": 4232 }, { "endCharacter": 39, - "endLine": 4223, + "endLine": 4252, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"to_numpy\" is not a known attribute of \"None\"", "rule": "reportOptionalMemberAccess", "severity": "error", "startCharacter": 31, - "startLine": 4223 + "startLine": 4252 }, { "endCharacter": 92, - "endLine": 4582, + "endLine": 4611, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Type \"bool_ | bool\" is not assignable to return type \"bool\"\n  Type \"bool_ | bool\" is not assignable to type \"bool\"\n    \"numpy.bool[builtins.bool]\" is not assignable to \"bool\"", "rule": "reportReturnType", "severity": "error", "startCharacter": 15, - "startLine": 4582 + "startLine": 4611 }, { "endCharacter": 85, - "endLine": 4983, + "endLine": 5012, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"XGBoostPruningCallback\" cannot be assigned to parameter \"object\" of type \"RegressorCallback\" in function \"append\"\n  Type \"XGBoostPruningCallback\" is not assignable to type \"RegressorCallback\"\n    Type \"XGBoostPruningCallback\" is not assignable to type \"(...) -> Any\"\n    \"XGBoostPruningCallback\" is not assignable to \"TrainingCallback\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 16, - "startLine": 4983 + "startLine": 5012 }, { "endCharacter": 29, - "endLine": 5026, + "endLine": 5055, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"list[tuple[DataFrame, DataFrame]] | None\" cannot be assigned to parameter \"eval_set\" of type \"List[_LGBM_ScikitValidSet] | None\" in function \"fit\"\n  Type \"list[tuple[DataFrame, DataFrame]] | None\" is not assignable to type \"List[_LGBM_ScikitValidSet] | None\"\n    Type \"list[tuple[DataFrame, DataFrame]]\" is not assignable to type \"List[_LGBM_ScikitValidSet] | None\"\n      \"list[tuple[DataFrame, DataFrame]]\" is not assignable to \"List[_LGBM_ScikitValidSet]\"\n        Type parameter \"_T@list\" is invariant, but \"tuple[DataFrame, DataFrame]\" is not the same as \"_LGBM_ScikitValidSet\"\n        Consider switching from \"list\" to \"Sequence\" which is covariant\n      \"list[tuple[DataFrame, DataFrame]]\" is not assignable to \"None\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 21, - "startLine": 5026 + "startLine": 5055 }, { "endCharacter": 43, - "endLine": 5027, + "endLine": 5056, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"list[NDArray[floating[Any]]] | None\" cannot be assigned to parameter \"eval_sample_weight\" of type \"List[_LGBM_WeightType] | None\" in function \"fit\"\n  Type \"list[NDArray[floating[Any]]] | None\" is not assignable to type \"List[_LGBM_WeightType] | None\"\n    Type \"list[NDArray[floating[Any]]]\" is not assignable to type \"List[_LGBM_WeightType] | None\"\n      \"list[NDArray[floating[Any]]]\" is not assignable to \"List[_LGBM_WeightType]\"\n        Type parameter \"_T@list\" is invariant, but \"NDArray[floating[Any]]\" is not the same as \"_LGBM_WeightType\"\n        Consider switching from \"list\" to \"Sequence\" which is covariant\n      \"list[NDArray[floating[Any]]]\" is not assignable to \"None\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 31, - "startLine": 5027 + "startLine": 5056 }, { "endCharacter": 62, - "endLine": 5030, + "endLine": 5059, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"list[RegressorCallback] | None\" cannot be assigned to parameter \"callbacks\" of type \"List[(...) -> Unknown] | None\" in function \"fit\"\n  Type \"list[RegressorCallback] | None\" is not assignable to type \"List[(...) -> Unknown] | None\"\n    Type \"list[RegressorCallback]\" is not assignable to type \"List[(...) -> Unknown] | None\"\n      \"list[RegressorCallback]\" is not assignable to \"List[(...) -> Unknown]\"\n        Type parameter \"_T@list\" is invariant, but \"RegressorCallback\" is not the same as \"(...) -> Unknown\"\n        Consider switching from \"list\" to \"Sequence\" which is covariant\n      \"list[RegressorCallback]\" is not assignable to \"None\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 22, - "startLine": 5030 + "startLine": 5059 }, { "endCharacter": 35, - "endLine": 5052, + "endLine": 5081, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Object of type \"None\" is not subscriptable", "rule": "reportOptionalSubscript", "severity": "error", "startCharacter": 27, - "startLine": 5052 + "startLine": 5081 }, { "endCharacter": 35, - "endLine": 5098, + "endLine": 5127, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Object of type \"None\" is not subscriptable", "rule": "reportOptionalSubscript", "severity": "error", "startCharacter": 27, - "startLine": 5098 + "startLine": 5127 }, { "endCharacter": 49, - "endLine": 5168, + "endLine": 5197, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Argument of type \"CatBoostPruningCallback\" cannot be assigned to parameter \"object\" of type \"RegressorCallback\" in function \"append\"\n  Type \"CatBoostPruningCallback\" is not assignable to type \"RegressorCallback\"\n    Type \"CatBoostPruningCallback\" is not assignable to type \"(...) -> Any\"\n    \"CatBoostPruningCallback\" is not assignable to \"TrainingCallback\"", "rule": "reportArgumentType", "severity": "error", "startCharacter": 33, - "startLine": 5168 + "startLine": 5197 }, { "endCharacter": 75, - "endLine": 5411, + "endLine": 5440, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Operator \"<=\" not supported for \"None\"", "rule": "reportOptionalOperand", "severity": "error", "startCharacter": 54, - "startLine": 5411 + "startLine": 5440 }, { "endCharacter": 23, - "endLine": 5574, + "endLine": 5603, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"best_params\" is possibly unbound", "rule": "reportPossiblyUnboundVariable", "severity": "error", "startCharacter": 12, - "startLine": 5574 + "startLine": 5603 }, { "endCharacter": 38, - "endLine": 5581, + "endLine": 5610, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"best_params\" is possibly unbound", "rule": "reportPossiblyUnboundVariable", "severity": "error", "startCharacter": 27, - "startLine": 5581 + "startLine": 5610 }, { "endCharacter": 22, - "endLine": 5593, + "endLine": 5622, "file": "quickadapter/user_data/strategies/Utils.py", "message": "\"best_params\" is possibly unbound", "rule": "reportPossiblyUnboundVariable", "severity": "error", "startCharacter": 11, - "startLine": 5593 + "startLine": 5622 }, { "endCharacter": 22, - "endLine": 5751, + "endLine": 5780, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Declaration \"default_ranges\" is obscured by a declaration of the same name", "rule": "reportRedeclaration", "severity": "error", "startCharacter": 8, - "startLine": 5751 + "startLine": 5780 }, { "endCharacter": 14, - "endLine": 5787, + "endLine": 5816, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Declaration \"params\" is obscured by a declaration of the same name", "rule": "reportRedeclaration", "severity": "error", "startCharacter": 8, - "startLine": 5787 + "startLine": 5816 }, { "endCharacter": 22, - "endLine": 5871, + "endLine": 5900, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Declaration \"default_ranges\" is obscured by a declaration of the same name", "rule": "reportRedeclaration", "severity": "error", "startCharacter": 8, - "startLine": 5871 + "startLine": 5900 }, { "endCharacter": 14, - "endLine": 5905, + "endLine": 5934, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Declaration \"params\" is obscured by a declaration of the same name", "rule": "reportRedeclaration", "severity": "error", "startCharacter": 8, - "startLine": 5905 + "startLine": 5934 }, { "endCharacter": 22, - "endLine": 5976, + "endLine": 6005, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Declaration \"default_ranges\" is obscured by a declaration of the same name", "rule": "reportRedeclaration", "severity": "error", "startCharacter": 8, - "startLine": 5976 + "startLine": 6005 }, { "endCharacter": 22, - "endLine": 6077, + "endLine": 6106, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Declaration \"default_ranges\" is obscured by a declaration of the same name", "rule": "reportRedeclaration", "severity": "error", "startCharacter": 8, - "startLine": 6077 + "startLine": 6106 }, { "endCharacter": 26, - "endLine": 6160, + "endLine": 6189, "file": "quickadapter/user_data/strategies/Utils.py", "message": "Declaration \"default_ranges\" is obscured by a declaration of the same name", "rule": "reportRedeclaration", "severity": "error", "startCharacter": 12, - "startLine": 6160 + "startLine": 6189 } ], "filesAnalyzed": 6, diff --git a/quickadapter/README.md b/quickadapter/README.md index d031bd1..d97a3b4 100644 --- a/quickadapter/README.md +++ b/quickadapter/README.md @@ -320,11 +320,11 @@ The effective leverage is clamped to `[1.0, max_leverage]`, including proposal f ### Exit pricing -| Path | Runtime fallback | Type / Range | Description | -| -------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| exit_pricing.trade_natr_method | `moving_average` | enum {`moving_average`,`quantile_interpolation`,`weighted_average`} | Trade NATR (Normalized Average True Range) aggregation for stoploss and take-profit distances. `moving_average` uses KAMA to preserve nonnegative volatility. The stoploss never loosens, including after partial exits, and remains unchanged on order fills. | -| exit_pricing.final_take_profit_retracement_fraction | 0.375 | float (0,1] | Fraction of the final take-profit target distance used as the frozen trailing retracement distance after the final target arms the exit. The final exit tracks the best subsequent per-candle rate and exits only after this material adverse move; elapsed stagnation alone does not exit. Plot annotations show only the current trail boundary from the candle that established it; earlier boundaries are not retained. | -| exit_pricing.take_profit_stage_natr_multiplier_fraction_series | `fibonacci_extensions` | enum {`fibonacci_extensions`,`pi_extensions`} | With the default four rungs, partial target distances are approximately 23.6%, 38.2% and 61.8% of the full target distance for `fibonacci_extensions`, or 3.2%, 10.1% and 31.8% for `pi_extensions`. Pi places partial targets closer to entry. Partial fractions are recomputed when the rung count changes; the final rung always uses the full distance. This setting changes target distances only, not nominal stake shares; execution constraints are described in the decision cycle. Invalid values use the default. | +| Path | Runtime fallback | Type / Range | Description | +| -------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| exit_pricing.trade_natr_method | `moving_average` | enum {`moving_average`,`quantile_interpolation`,`weighted_average`} | Trade NATR (Normalized Average True Range) aggregation for stoploss and take-profit distances. `moving_average` uses KAMA to preserve nonnegative volatility. The stoploss never loosens, including after partial exits, and remains unchanged on order fills. | +| exit_pricing.final_take_profit_retracement_fraction | 0.375 | float (0,1] | Fraction of the final take-profit target distance used as the frozen trailing retracement distance after the final target arms the exit. The final exit tracks the best subsequent per-candle rate and exits only after this material adverse move; elapsed stagnation alone does not exit. Plot annotations show only the current trail boundary from the candle that established it; earlier boundaries are not retained. | +| exit_pricing.take_profit_stage_natr_multiplier_fraction_series | `fibonacci_retracements` | enum {`fibonacci_retracements`,`equal_division`,`gann_octaves`} | Each series places the partial rungs as fractions of the full target distance. `fibonacci_retracements` uses Fibonacci retracement ratios (default three partial rungs: ~23.6%, 38.2% and 61.8%). `equal_division` spaces the rungs evenly (default three partial rungs: 25%, 50% and 75%). `gann_octaves` uses the Gann octave and third levels closest to the full move (default three partial rungs: ~66.7%, 75% and 87.5%) and supports at most nine partial rungs. Partial fractions are recomputed when the rung count changes; the final rung always uses the full distance. This setting changes target distances only, not nominal stake shares; execution constraints are described in the decision cycle. Invalid values use the default. | ### Reversal confirmation diff --git a/quickadapter/tests/test_strategy_features.py b/quickadapter/tests/test_strategy_features.py index b95cb1d..3dec445 100644 --- a/quickadapter/tests/test_strategy_features.py +++ b/quickadapter/tests/test_strategy_features.py @@ -559,7 +559,7 @@ class StrategyFeaturesTest(QaTestCase): self.assertEqual(model.get_exit_stage_natr_multiplier_fraction(99), 1.0) self.assertEqual(model.get_exit_stage_natr_multiplier_fraction(-1), 1.0) - def test_the_fibonacci_extensions_define_the_default_ladder(self): + def test_the_fibonacci_retracements_define_the_default_ladder(self): # Frozen reference rungs, not a restatement of the formula: a change of # base or of the stage count must fail here. Three partial rungs are # phi**-3, phi**-2 and phi**-1, then the full distance. @@ -594,81 +594,98 @@ class StrategyFeaturesTest(QaTestCase): def test_the_ladder_is_indexed_by_the_stage_count(self): self.assertEqual( - len(get_take_profit_stage_natr_multiplier_fractions("fibonacci_extensions", 0)), 0 + len(get_take_profit_stage_natr_multiplier_fractions("fibonacci_retracements", 0)), 0 ) for count in (1, 2, 3, 4, 5): with self.subTest(count=count): fractions = get_take_profit_stage_natr_multiplier_fractions( - "fibonacci_extensions", count + "fibonacci_retracements", count ) self.assertEqual(len(fractions), count) self.assertEqual(max(fractions), fractions[-1]) - def test_the_pi_extensions_define_the_ladder(self): + def test_the_equal_division_defines_the_default_rung_count_ladder(self): + # Evenly spaced rungs: with the default three partial rungs, k / 4 for + # k in 1..3, then the full distance. assert_allclose( tuple( strategy( - fraction_series="pi_extensions" + fraction_series="equal_division" ).exit_stage_natr_multiplier_fractions.values() ), - (0.03225153, 0.10132118, 0.31830989, 1.0), - rtol=1e-6, + (0.25, 0.5, 0.75, 1.0), + rtol=1e-12, + ) + + def test_the_gann_octaves_define_the_default_rung_count_ladder(self): + # Gann levels closest to the full move: with the default three partial + # rungs, 2/3, 3/4 and 7/8, then the full distance. + assert_allclose( + tuple( + strategy( + fraction_series="gann_octaves" + ).exit_stage_natr_multiplier_fractions.values() + ), + (2.0 / 3.0, 0.75, 0.875, 1.0), + rtol=1e-12, ) + def test_gann_octaves_refuses_more_rungs_than_it_defines(self): + # Gann defines nine sub-move levels; a tenth partial rung is refused. + self.assertEqual(len(get_take_profit_stage_natr_multiplier_fractions("gann_octaves", 9)), 9) + with self.assertRaises(ValueError): + get_take_profit_stage_natr_multiplier_fractions("gann_octaves", 10) + def test_selecting_a_series_changes_the_stage_distances(self): frame = candles([100.0, 100.0, 100.0], natr=[1.0, 4.0, 8.0], multiplier=MULTIPLIER) long_trade = trade() - fibonacci = strategy(fraction_series="fibonacci_extensions") - pi = strategy(fraction_series="pi_extensions") - self.assertEqual(pi.take_profit_stage_natr_multiplier_fraction_series, "pi_extensions") + fibonacci = strategy(fraction_series="fibonacci_retracements") + equal = strategy(fraction_series="equal_division") + self.assertEqual(equal.take_profit_stage_natr_multiplier_fraction_series, "equal_division") partial_stages = list(QuickAdapterV3.partial_exit_stages()) - for stage in partial_stages: - with self.subTest(stage=stage): - fibonacci_distance = fibonacci.get_take_profit_target(frame, long_trade, stage)[1] - pi_distance = pi.get_take_profit_target(frame, long_trade, stage)[1] - # The larger pi extension base yields smaller reciprocal fractions. - self.assertLess(pi_distance, fibonacci_distance) - # The final distance is independent of the partial extension series. + fibonacci_distances = [ + fibonacci.get_take_profit_target(frame, long_trade, stage)[1] + for stage in partial_stages + ] + equal_distances = [ + equal.get_take_profit_target(frame, long_trade, stage)[1] for stage in partial_stages + ] + # Distinct series price the partial rungs at distinct distances. + self.assertNotEqual(fibonacci_distances, equal_distances) + # The final distance is independent of the partial series. self.assertEqual( fibonacci.get_take_profit_target(frame, long_trade, QuickAdapterV3.final_exit_stage()), - pi.get_take_profit_target(frame, long_trade, QuickAdapterV3.final_exit_stage()), + equal.get_take_profit_target(frame, long_trade, QuickAdapterV3.final_exit_stage()), ) def test_fraction_series_validates_names_and_stage_counts(self): with self.assertRaises(ValueError): get_take_profit_stage_natr_multiplier_fractions("not_a_series", 3) self.assertEqual( - get_take_profit_stage_natr_multiplier_fractions("fibonacci_extensions", 0), () + get_take_profit_stage_natr_multiplier_fractions("fibonacci_retracements", 0), () ) assert_allclose( - get_take_profit_stage_natr_multiplier_fractions("fibonacci_extensions", 1), + get_take_profit_stage_natr_multiplier_fractions("fibonacci_retracements", 1), (0.618033988749895,), rtol=1e-14, ) - def test_a_degenerate_ladder_base_is_refused_by_name(self): - from Utils import _TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_BASES - - for name, base in (("zero", 0.0), ("unit", 1.0), ("near", 1.0 + 2**-52)): - with self.subTest(base=base): - _TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_BASES[name] = base - self.addCleanup(_TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_BASES.pop, name, None) - self.addCleanup(get_take_profit_stage_natr_multiplier_fractions.cache_clear) - with self.assertRaises(ValueError) as raised: - get_take_profit_stage_natr_multiplier_fractions(name, 3) - self.assertIn(name, str(raised.exception)) - def test_an_underflowing_ladder_is_refused(self): - # pi**-650 is the smallest positive subnormal; pi**-651 underflows to 0.0. - fractions = get_take_profit_stage_natr_multiplier_fractions("pi_extensions", 650) - self.assertEqual(len(fractions), 650) - self.assertEqual(fractions[0], math.ulp(0.0)) + # For a large enough rung count the geometric rungs underflow to zero; + # the first such count is refused, one rung short nothing underflows. + base = (1.0 + math.sqrt(5.0)) / 2.0 + underflowing = next(k for k in range(1, 10000) if base**-k <= 0.0) + fractions = get_take_profit_stage_natr_multiplier_fractions( + "fibonacci_retracements", underflowing - 1 + ) + self.assertEqual(len(fractions), underflowing - 1) + self.assertGreater(fractions[0], 0.0) with self.assertRaises(ValueError): - get_take_profit_stage_natr_multiplier_fractions("pi_extensions", 651) + get_take_profit_stage_natr_multiplier_fractions("fibonacci_retracements", underflowing) def test_a_negative_partial_stage_count_is_rejected(self): with self.assertRaises(ValueError): - get_take_profit_stage_natr_multiplier_fractions("fibonacci_extensions", -1) + get_take_profit_stage_natr_multiplier_fractions("fibonacci_retracements", -1) def test_the_remaining_stake_share_grows_with_the_stage_index(self): # Shares apply to the stake still open, so each rung closes a larger diff --git a/quickadapter/tests/test_utils_config_resolution.py b/quickadapter/tests/test_utils_config_resolution.py index 1cf9dfb..2d30c10 100644 --- a/quickadapter/tests/test_utils_config_resolution.py +++ b/quickadapter/tests/test_utils_config_resolution.py @@ -14,6 +14,7 @@ from Utils import ( DEFAULTS_CUSTOM_PROTECTIONS, DEFAULTS_EXIT_PRICING, DEFAULTS_REVERSAL_CONFIRMATION, + TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES, get_causal_mode, get_custom_protections_config, get_exit_pricing_config, @@ -121,7 +122,7 @@ class UtilsConfigResolutionTest(QaTestCase): self.assertEqual([logging.WARNING], [record.levelno for record in captured.records]) def test_exit_pricing_every_supported_fraction_series_is_preserved(self) -> None: - for series in ("fibonacci_extensions", "pi_extensions"): + for series in TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES: with self.subTest(series=series): with recorded_warnings() as (logger, sink): resolved = get_exit_pricing_config( diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index e1eded5..34b22bd 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -504,7 +504,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): https://github.com/sponsors/robcaulk """ - version = "3.13.2" + version = "3.13.3" _CALIBRATION_START_KEY: Final[str] = "quickadapter_calibration_start" _DEPLOYMENT_COORDINATE_MARKER_KEY: Final[str] = "quickadapter_deployment_coordinates" diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 8989f01..5dcebf8 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -221,7 +221,7 @@ class QuickAdapterV3(IStrategy): _ANNOTATION_LINE_OFFSET_CANDLES: Final[int] = 10 def version(self) -> str: - return "3.13.2" + return "3.13.3" timeframe = "5m" timeframe_minutes = timeframe_to_minutes(timeframe) @@ -444,7 +444,7 @@ class QuickAdapterV3(IStrategy): def exit_stage_natr_multiplier_fractions(self) -> dict[int, float]: """Target distance of each stage as a fraction of the full distance. - Partial rungs follow the configured reciprocal-extension series; the + Partial rungs follow the configured fraction series; the final rung sits at the full distance. """ partial_stages = QuickAdapterV3.partial_exit_stages() diff --git a/quickadapter/user_data/strategies/Utils.py b/quickadapter/user_data/strategies/Utils.py index 3cd60a2..606d1b9 100644 --- a/quickadapter/user_data/strategies/Utils.py +++ b/quickadapter/user_data/strategies/Utils.py @@ -1052,42 +1052,81 @@ TRADE_NATR_METHODS: Final[tuple[TradeNatrMethod, ...]] = ( ) -TakeProfitStageNatrMultiplierFractionSeries = Literal["fibonacci_extensions", "pi_extensions"] +TakeProfitStageNatrMultiplierFractionSeries = Literal[ + "fibonacci_retracements", + "equal_division", + "gann_octaves", +] TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES: Final[ tuple[TakeProfitStageNatrMultiplierFractionSeries, ...] ] = ( - "fibonacci_extensions", - "pi_extensions", + "fibonacci_retracements", + "equal_division", + "gann_octaves", ) -# Extension bases: stage ``k`` of ``n`` uses the reciprocal of ``base ** (n - k)``. -# These fractions place partial targets inside the full take-profit distance; -# they do not extend it. Fibonacci uses integer powers of phi; pi uses integer -# powers of pi as a custom geometric construction, not a trading convention. -# The strategy appends the final fraction ``1.0`` to the generated -# partial-target distance ladder. -_TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_BASES: Final[dict[str, float]] = { - "fibonacci_extensions": (1.0 + math.sqrt(5.0)) / 2.0, - "pi_extensions": math.pi, -} +# Series place partial targets strictly inside the full take-profit distance. +# The strategy appends the independent final fraction +# ``1.0`` to the generated partial-target ladder. Each series is dispatched by +# its index into TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES. +# The Fibonacci retracement base (the golden ratio): the closest partial +# fraction is 1 / base = 0.618, at most 2/3, keeping rungs inside the move. +_FIBONACCI_RETRACEMENT_BASE: Final[float] = (1.0 + math.sqrt(5.0)) / 2.0 +# Gann octave (``i / 8``) and third (``1/3``, ``2/3``) levels strictly inside the +# full move, ascending; the series takes the levels closest to the full distance, +# so it defines at most nine partial rungs. +_GANN_OCTAVE_LEVELS: Final[tuple[float, ...]] = tuple( + sorted([i / 8.0 for i in range(1, 8)] + [1.0 / 3.0, 2.0 / 3.0]) +) + + +def _fibonacci_retracement_stage_fractions(partial_stage_count: int) -> tuple[float, ...]: + """Stage ``k`` of ``n`` uses 1 / base ** (n - k), i.e. the ``n`` largest + reciprocal powers of the golden ratio; the first three are the canonical + Fibonacci retracement ratios (0.618, 0.382, 0.236).""" + base = _FIBONACCI_RETRACEMENT_BASE + return tuple(base ** -(partial_stage_count - stage) for stage in range(partial_stage_count)) -# The closest partial fraction is 1 / base; a 1.5 floor keeps it at most 2/3. -# This relative gap does not guarantee distinct prices after rounding. -_TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_MIN_BASE: Final[float] = 1.5 + +def _equal_division_stage_fractions(partial_stage_count: int) -> tuple[float, ...]: + """``n`` equal spacings across the full move: k / (n + 1) for k in 1..n.""" + return tuple((stage + 1) / (partial_stage_count + 1) for stage in range(partial_stage_count)) + + +def _gann_octaves_stage_fractions(partial_stage_count: int) -> tuple[float, ...]: + """The ``n`` Gann octave/third levels closest to the full move (at most nine).""" + if partial_stage_count > len(_GANN_OCTAVE_LEVELS): + raise ValueError( + f"Invalid partial_stage_count value {partial_stage_count}: " + f"series {TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES[2]!r} " + f"defines {len(_GANN_OCTAVE_LEVELS)} rungs" + ) + return _GANN_OCTAVE_LEVELS[-partial_stage_count:] if partial_stage_count else () @lru_cache(maxsize=_CACHE_MAXSIZE_SMALL) def get_take_profit_stage_natr_multiplier_fractions( series: str, partial_stage_count: int ) -> tuple[float, ...]: - """Return ascending reciprocal-extension fractions for partial take-profit targets. + """Return ascending fractions of the full take-profit distance for partial targets. - Stage ``k`` of ``n`` uses ``1 / base ** (n - k)``. Each fraction scales - the full take-profit distance and remains strictly below the independent - final fraction ``1.0``. + Each series places its ``partial_stage_count`` rungs strictly inside the full + distance; the strategy appends the independent final fraction ``1.0``. """ - base = _TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_BASES.get(series) - if base is None: + if partial_stage_count < 0: + raise ValueError(f"Invalid partial_stage_count value {partial_stage_count}: must be >= 0") + stage_fractions: dict[str, Callable[[int], tuple[float, ...]]] = { + # 0 - "fibonacci_retracements" + TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES[ + 0 + ]: _fibonacci_retracement_stage_fractions, + # 1 - "equal_division" + TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES[1]: _equal_division_stage_fractions, + # 2 - "gann_octaves" + TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES[2]: _gann_octaves_stage_fractions, + } + handler = stage_fractions.get(series) + if handler is None: raise ValueError( enum_error_message( "take_profit_stage_natr_multiplier_fraction_series", @@ -1095,20 +1134,10 @@ def get_take_profit_stage_natr_multiplier_fractions( TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_SERIES, ) ) - if partial_stage_count < 0: - raise ValueError(f"Invalid partial_stage_count value {partial_stage_count}: must be >= 0") - if base < _TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_MIN_BASE: - raise ValueError( - f"Invalid base value {base!r} for series {series!r}: " - f"must be >= {_TAKE_PROFIT_STAGE_NATR_MULTIPLIER_FRACTION_MIN_BASE}" - ) - - fractions = tuple( - base ** -(partial_stage_count - stage) for stage in range(partial_stage_count) - ) + fractions = handler(partial_stage_count) if any(fraction <= 0.0 for fraction in fractions): - # Only reachable by denormal underflow: the base floor above guarantees - # ``base > 1``, so no rung can reach or exceed ``1.0``. + # Defensive: an unbounded rung count can underflow a rung to zero + # (fibonacci_retracements, where base ** -(n-k) underflows to zero at n = 1549). raise ValueError( f"Invalid partial_stage_count value {partial_stage_count!r} for series {series!r}: " "NATR multiplier fractions must be > 0 (underflow to zero)"