opencode-pyneruntime 6.6.4__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.
- opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
- opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
- opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
- opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
- opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
- pynecore/__init__.py +6 -0
- pynecore/cli/__init__.py +2 -0
- pynecore/cli/app.py +238 -0
- pynecore/cli/commands/__init__.py +343 -0
- pynecore/cli/commands/benchmark.py +186 -0
- pynecore/cli/commands/compile.py +198 -0
- pynecore/cli/commands/data.py +857 -0
- pynecore/cli/commands/debug.py +63 -0
- pynecore/cli/commands/optimize.py +956 -0
- pynecore/cli/commands/plugin.py +242 -0
- pynecore/cli/commands/run.py +2006 -0
- pynecore/cli/pluggable.py +132 -0
- pynecore/cli/utils/__init__.py +0 -0
- pynecore/cli/utils/api_error_handler.py +168 -0
- pynecore/cli/utils/broker_picker.py +330 -0
- pynecore/cli/utils/error_hook.py +28 -0
- pynecore/cli/utils/keyreader.py +178 -0
- pynecore/cli/utils/provider_picker.py +19 -0
- pynecore/cli/utils/symbol_browser.py +1149 -0
- pynecore/core/__init__.py +0 -0
- pynecore/core/aggregator.py +257 -0
- pynecore/core/bar_magnifier.py +168 -0
- pynecore/core/broker/__init__.py +64 -0
- pynecore/core/broker/defaults.py +113 -0
- pynecore/core/broker/disappearance.py +927 -0
- pynecore/core/broker/emulator.py +345 -0
- pynecore/core/broker/exceptions.py +346 -0
- pynecore/core/broker/idempotency.py +401 -0
- pynecore/core/broker/intent_builder.py +334 -0
- pynecore/core/broker/journal.py +1785 -0
- pynecore/core/broker/models.py +1600 -0
- pynecore/core/broker/native_failsafe_manager.py +1436 -0
- pynecore/core/broker/one_way_emulator.py +1128 -0
- pynecore/core/broker/position.py +787 -0
- pynecore/core/broker/run_identity.py +126 -0
- pynecore/core/broker/software_entry_stop_engine.py +351 -0
- pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
- pynecore/core/broker/spot_inventory.py +1327 -0
- pynecore/core/broker/storage.py +2655 -0
- pynecore/core/broker/store_helpers.py +2161 -0
- pynecore/core/broker/sync_engine.py +16070 -0
- pynecore/core/broker/validation.py +382 -0
- pynecore/core/class_property.py +7 -0
- pynecore/core/config.py +392 -0
- pynecore/core/csv_file.py +547 -0
- pynecore/core/currency.py +262 -0
- pynecore/core/data_converter.py +1002 -0
- pynecore/core/datetime.py +296 -0
- pynecore/core/download_info.py +71 -0
- pynecore/core/download_runner.py +274 -0
- pynecore/core/htf_aggregator.py +181 -0
- pynecore/core/import_hook.py +358 -0
- pynecore/core/instance_state.py +494 -0
- pynecore/core/live_ltf_collector.py +442 -0
- pynecore/core/live_ltf_window.py +189 -0
- pynecore/core/live_runner.py +1347 -0
- pynecore/core/module_property.py +26 -0
- pynecore/core/ohlcv_file.py +1888 -0
- pynecore/core/overload.py +371 -0
- pynecore/core/pine_cast.py +113 -0
- pynecore/core/pine_export.py +95 -0
- pynecore/core/pine_method.py +244 -0
- pynecore/core/pine_range.py +86 -0
- pynecore/core/pine_udt.py +69 -0
- pynecore/core/plugin/__init__.py +394 -0
- pynecore/core/plugin/broker.py +781 -0
- pynecore/core/plugin/cli.py +96 -0
- pynecore/core/plugin/live_provider.py +208 -0
- pynecore/core/plugin/provider.py +331 -0
- pynecore/core/provider_string.py +148 -0
- pynecore/core/random.py +40 -0
- pynecore/core/resampler.py +686 -0
- pynecore/core/safe_convert.py +64 -0
- pynecore/core/script.py +1011 -0
- pynecore/core/script_runner.py +3202 -0
- pynecore/core/security.py +1749 -0
- pynecore/core/security_process.py +1253 -0
- pynecore/core/security_shm.py +456 -0
- pynecore/core/series.py +417 -0
- pynecore/core/strategy_stats.py +669 -0
- pynecore/core/symbol_map.py +134 -0
- pynecore/core/syminfo.py +505 -0
- pynecore/core/viz.py +591 -0
- pynecore/lib/__init__.py +1771 -0
- pynecore/lib/_fixnan.py +32 -0
- pynecore/lib/_math_stateful.py +202 -0
- pynecore/lib/_timeframe_change.py +101 -0
- pynecore/lib/adjustment.py +6 -0
- pynecore/lib/alert.py +39 -0
- pynecore/lib/alert.pyi +14 -0
- pynecore/lib/array.py +1051 -0
- pynecore/lib/barmerge.py +60 -0
- pynecore/lib/barstate.py +30 -0
- pynecore/lib/box.py +415 -0
- pynecore/lib/chart.py +128 -0
- pynecore/lib/color.py +152 -0
- pynecore/lib/color.pyi +50 -0
- pynecore/lib/currency.py +62 -0
- pynecore/lib/dayofweek.py +36 -0
- pynecore/lib/dayofweek.pyi +18 -0
- pynecore/lib/display.py +8 -0
- pynecore/lib/dividends.py +9 -0
- pynecore/lib/earnings.py +11 -0
- pynecore/lib/extend.py +6 -0
- pynecore/lib/font.py +5 -0
- pynecore/lib/footprint.py +79 -0
- pynecore/lib/format.py +11 -0
- pynecore/lib/hline.py +67 -0
- pynecore/lib/hline.pyi +24 -0
- pynecore/lib/label.py +409 -0
- pynecore/lib/line.py +433 -0
- pynecore/lib/linefill.py +93 -0
- pynecore/lib/location.py +11 -0
- pynecore/lib/log.py +362 -0
- pynecore/lib/map.py +150 -0
- pynecore/lib/math.py +385 -0
- pynecore/lib/matrix.py +708 -0
- pynecore/lib/order.py +8 -0
- pynecore/lib/pivotpointtype.py +8 -0
- pynecore/lib/plot.py +95 -0
- pynecore/lib/plot.pyi +33 -0
- pynecore/lib/polyline.py +91 -0
- pynecore/lib/position.py +15 -0
- pynecore/lib/request.py +281 -0
- pynecore/lib/runtime.py +5 -0
- pynecore/lib/scale.py +9 -0
- pynecore/lib/session.py +267 -0
- pynecore/lib/session.pyi +12 -0
- pynecore/lib/shape.py +18 -0
- pynecore/lib/size.py +12 -0
- pynecore/lib/splits.py +4 -0
- pynecore/lib/strategy/__init__.py +4778 -0
- pynecore/lib/strategy/closedtrades.py +347 -0
- pynecore/lib/strategy/closedtrades.pyi +53 -0
- pynecore/lib/strategy/commission.py +9 -0
- pynecore/lib/strategy/direction.py +9 -0
- pynecore/lib/strategy/oca.py +13 -0
- pynecore/lib/strategy/opentrades.py +281 -0
- pynecore/lib/strategy/opentrades.pyi +49 -0
- pynecore/lib/strategy/risk.py +109 -0
- pynecore/lib/string.py +649 -0
- pynecore/lib/syminfo.py +84 -0
- pynecore/lib/ta.py +2230 -0
- pynecore/lib/table.py +290 -0
- pynecore/lib/text.py +17 -0
- pynecore/lib/ticker.py +207 -0
- pynecore/lib/timeframe.py +293 -0
- pynecore/lib/volume_row.py +67 -0
- pynecore/lib/xloc.py +4 -0
- pynecore/lib/yloc.py +5 -0
- pynecore/providers/__init__.py +0 -0
- pynecore/providers/ccxt.py +664 -0
- pynecore/providers/replay.py +187 -0
- pynecore/pynesys/__init__.py +0 -0
- pynecore/pynesys/api.py +498 -0
- pynecore/pynesys/compiler.py +112 -0
- pynecore/standalone.py +99 -0
- pynecore/testing/__init__.py +1 -0
- pynecore/testing/broker_lab/__init__.py +41 -0
- pynecore/testing/broker_lab/__main__.py +5 -0
- pynecore/testing/broker_lab/cli.py +87 -0
- pynecore/testing/broker_lab/generate.py +47 -0
- pynecore/testing/broker_lab/model.py +84 -0
- pynecore/testing/broker_lab/reference.py +645 -0
- pynecore/testing/broker_lab/runner.py +372 -0
- pynecore/testing/broker_lab/scheduler.py +50 -0
- pynecore/testing/broker_lab/subprocess.py +73 -0
- pynecore/transformers/__init__.py +0 -0
- pynecore/transformers/builtin_shadow.py +136 -0
- pynecore/transformers/closure_arguments_transformer.py +428 -0
- pynecore/transformers/display_rewrite.py +140 -0
- pynecore/transformers/dynamic_default.py +147 -0
- pynecore/transformers/function_isolation.py +757 -0
- pynecore/transformers/import_lifter.py +61 -0
- pynecore/transformers/import_normalizer.py +328 -0
- pynecore/transformers/inline_series_hoist.py +178 -0
- pynecore/transformers/input_transformer.py +175 -0
- pynecore/transformers/lib_series.py +201 -0
- pynecore/transformers/locations.py +70 -0
- pynecore/transformers/module_properties.json +3387 -0
- pynecore/transformers/module_property.py +221 -0
- pynecore/transformers/ne_guard.py +70 -0
- pynecore/transformers/persistent.py +320 -0
- pynecore/transformers/persistent_series.py +76 -0
- pynecore/transformers/safe_convert_transformer.py +97 -0
- pynecore/transformers/safe_division_transformer.py +95 -0
- pynecore/transformers/script_requirements.py +308 -0
- pynecore/transformers/security.py +752 -0
- pynecore/transformers/security_instantiation.py +274 -0
- pynecore/transformers/series.py +275 -0
- pynecore/transformers/slot_layout.py +381 -0
- pynecore/transformers/type_checking_stripper.py +25 -0
- pynecore/transformers/unused_series_detector.py +267 -0
- pynecore/types/__init__.py +21 -0
- pynecore/types/alert.py +5 -0
- pynecore/types/barmerge.py +5 -0
- pynecore/types/base.py +39 -0
- pynecore/types/box.py +37 -0
- pynecore/types/chart.py +17 -0
- pynecore/types/color.py +107 -0
- pynecore/types/currency.py +5 -0
- pynecore/types/datetime.py +6 -0
- pynecore/types/display.py +5 -0
- pynecore/types/dividends.py +5 -0
- pynecore/types/earnings.py +5 -0
- pynecore/types/extend.py +5 -0
- pynecore/types/font.py +5 -0
- pynecore/types/footprint.py +41 -0
- pynecore/types/format.py +5 -0
- pynecore/types/hline.py +24 -0
- pynecore/types/ib_persistent.py +8 -0
- pynecore/types/ib_persistent.pyi +10 -0
- pynecore/types/label.py +35 -0
- pynecore/types/line.py +32 -0
- pynecore/types/linefill.py +13 -0
- pynecore/types/location.py +5 -0
- pynecore/types/matrix.py +999 -0
- pynecore/types/na.py +237 -0
- pynecore/types/na.pyi +83 -0
- pynecore/types/ohlcv.py +12 -0
- pynecore/types/order.py +5 -0
- pynecore/types/persistent.py +8 -0
- pynecore/types/persistent.pyi +13 -0
- pynecore/types/pine_types.py +11 -0
- pynecore/types/pine_types.pyi +15 -0
- pynecore/types/pivotpointtype.py +5 -0
- pynecore/types/plot.py +12 -0
- pynecore/types/plot_meta.py +60 -0
- pynecore/types/polyline.py +40 -0
- pynecore/types/position.py +5 -0
- pynecore/types/scale.py +5 -0
- pynecore/types/script_type.py +15 -0
- pynecore/types/series.py +23 -0
- pynecore/types/series.pyi +19 -0
- pynecore/types/session.py +35 -0
- pynecore/types/shape.py +5 -0
- pynecore/types/size.py +5 -0
- pynecore/types/source.py +33 -0
- pynecore/types/splits.py +5 -0
- pynecore/types/strategy.py +45 -0
- pynecore/types/table.py +87 -0
- pynecore/types/text.py +13 -0
- pynecore/types/type_checker.py +7 -0
- pynecore/types/type_checker.pyi +48 -0
- pynecore/types/volume_row.py +36 -0
- pynecore/types/weekdays.py +11 -0
- pynecore/types/xloc.py +5 -0
- pynecore/types/yloc.py +5 -0
- pynecore/utils/__init__.py +0 -0
- pynecore/utils/file_utils.py +50 -0
- pynecore/utils/rich/__init__.py +0 -0
- pynecore/utils/rich/date_column.py +25 -0
- pynecore/utils/sequence_view.py +92 -0
- pynecore/utils/stdlib_checker.py +17 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategy statistics calculation module for PyneCore.
|
|
3
|
+
Calculates comprehensive trading statistics similar to TradingView's Strategy Tester.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from ..types.na import NA
|
|
12
|
+
from ..lib.strategy import Trade
|
|
13
|
+
from .csv_file import CSVWriter
|
|
14
|
+
from ..lib.strategy import PositionBase
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class StrategyStatistics:
|
|
19
|
+
"""Complete strategy statistics matching TradingView's output"""
|
|
20
|
+
|
|
21
|
+
# Overview metrics
|
|
22
|
+
net_profit: float = 0.0
|
|
23
|
+
net_profit_percent: float = 0.0
|
|
24
|
+
gross_profit: float = 0.0
|
|
25
|
+
gross_profit_percent: float = 0.0
|
|
26
|
+
gross_loss: float = 0.0
|
|
27
|
+
gross_loss_percent: float = 0.0
|
|
28
|
+
max_equity_runup: float = 0.0
|
|
29
|
+
max_equity_runup_percent: float = 0.0
|
|
30
|
+
max_equity_drawdown: float = 0.0
|
|
31
|
+
max_equity_drawdown_percent: float = 0.0
|
|
32
|
+
unrealized_max_drawdown: float = 0.0
|
|
33
|
+
unrealized_max_drawdown_percent: float = 0.0
|
|
34
|
+
real_max_drawdown: float = 0.0
|
|
35
|
+
real_max_drawdown_percent: float = 0.0
|
|
36
|
+
buy_and_hold_return: float = 0.0
|
|
37
|
+
buy_and_hold_return_percent: float = 0.0
|
|
38
|
+
sharpe_ratio: float = 0.0
|
|
39
|
+
sortino_ratio: float = 0.0
|
|
40
|
+
profit_factor: float = 0.0
|
|
41
|
+
|
|
42
|
+
# Trade statistics
|
|
43
|
+
total_trades: int = 0
|
|
44
|
+
winning_trades: int = 0
|
|
45
|
+
losing_trades: int = 0
|
|
46
|
+
percent_profitable: float = 0.0
|
|
47
|
+
avg_trade: float = 0.0
|
|
48
|
+
avg_trade_percent: float = 0.0
|
|
49
|
+
avg_winning_trade: float = 0.0
|
|
50
|
+
avg_winning_trade_percent: float = 0.0
|
|
51
|
+
avg_losing_trade: float = 0.0
|
|
52
|
+
avg_losing_trade_percent: float = 0.0
|
|
53
|
+
largest_winning_trade: float = 0.0
|
|
54
|
+
largest_winning_trade_percent: float = 0.0
|
|
55
|
+
largest_losing_trade: float = 0.0
|
|
56
|
+
largest_losing_trade_percent: float = 0.0
|
|
57
|
+
avg_bars_in_trades: float = 0.0
|
|
58
|
+
avg_bars_in_winning_trades: float = 0.0
|
|
59
|
+
avg_bars_in_losing_trades: float = 0.0
|
|
60
|
+
|
|
61
|
+
# Long/Short breakdown
|
|
62
|
+
long_trades: int = 0
|
|
63
|
+
long_winning_trades: int = 0
|
|
64
|
+
long_net_profit: float = 0.0
|
|
65
|
+
long_net_profit_percent: float = 0.0
|
|
66
|
+
long_gross_profit: float = 0.0
|
|
67
|
+
long_gross_profit_percent: float = 0.0
|
|
68
|
+
long_gross_loss: float = 0.0
|
|
69
|
+
long_gross_loss_percent: float = 0.0
|
|
70
|
+
long_avg_trade: float = 0.0
|
|
71
|
+
long_avg_trade_percent: float = 0.0
|
|
72
|
+
long_largest_winning_trade: float = 0.0
|
|
73
|
+
long_largest_winning_trade_percent: float = 0.0
|
|
74
|
+
long_largest_losing_trade: float = 0.0
|
|
75
|
+
long_largest_losing_trade_percent: float = 0.0
|
|
76
|
+
long_avg_bars: float = 0.0
|
|
77
|
+
|
|
78
|
+
short_trades: int = 0
|
|
79
|
+
short_winning_trades: int = 0
|
|
80
|
+
short_net_profit: float = 0.0
|
|
81
|
+
short_net_profit_percent: float = 0.0
|
|
82
|
+
short_gross_profit: float = 0.0
|
|
83
|
+
short_gross_profit_percent: float = 0.0
|
|
84
|
+
short_gross_loss: float = 0.0
|
|
85
|
+
short_gross_loss_percent: float = 0.0
|
|
86
|
+
short_avg_trade: float = 0.0
|
|
87
|
+
short_avg_trade_percent: float = 0.0
|
|
88
|
+
short_largest_winning_trade: float = 0.0
|
|
89
|
+
short_largest_winning_trade_percent: float = 0.0
|
|
90
|
+
short_largest_losing_trade: float = 0.0
|
|
91
|
+
short_largest_losing_trade_percent: float = 0.0
|
|
92
|
+
short_avg_bars: float = 0.0
|
|
93
|
+
|
|
94
|
+
# P&L breakdown (realized / unrealized / total)
|
|
95
|
+
total_pnl: float = 0.0
|
|
96
|
+
total_pnl_percent: float = 0.0
|
|
97
|
+
realized_pnl: float = 0.0
|
|
98
|
+
realized_pnl_percent: float = 0.0
|
|
99
|
+
unrealized_pnl: float = 0.0
|
|
100
|
+
unrealized_pnl_percent: float = 0.0
|
|
101
|
+
|
|
102
|
+
# Other metrics
|
|
103
|
+
margin_calls: int = 0
|
|
104
|
+
max_contracts_held: float = 0.0
|
|
105
|
+
commission_paid: float = 0.0
|
|
106
|
+
total_open_trades: int = 0
|
|
107
|
+
|
|
108
|
+
# Drawdown metrics
|
|
109
|
+
max_cons_winning_trades: int = 0
|
|
110
|
+
max_cons_losing_trades: int = 0
|
|
111
|
+
|
|
112
|
+
# Additional ratio calculations
|
|
113
|
+
ratio_avg_win_loss: float = 0.0
|
|
114
|
+
|
|
115
|
+
def to_dict(self) -> dict[str, float | int]:
|
|
116
|
+
"""Convert statistics to dictionary for CSV export"""
|
|
117
|
+
return {
|
|
118
|
+
# Overview
|
|
119
|
+
"Net Profit": self.net_profit,
|
|
120
|
+
"Net Profit %": self.net_profit_percent,
|
|
121
|
+
"Gross Profit": self.gross_profit,
|
|
122
|
+
"Gross Profit %": self.gross_profit_percent,
|
|
123
|
+
"Gross Loss": self.gross_loss,
|
|
124
|
+
"Gross Loss %": self.gross_loss_percent,
|
|
125
|
+
"Max Equity Run-up": self.max_equity_runup,
|
|
126
|
+
"Max Equity Run-up %": self.max_equity_runup_percent,
|
|
127
|
+
"Max Equity Drawdown": self.max_equity_drawdown,
|
|
128
|
+
"Max Equity Drawdown %": self.max_equity_drawdown_percent,
|
|
129
|
+
"Unrealized Max Drawdown": self.unrealized_max_drawdown,
|
|
130
|
+
"Unrealized Max Drawdown %": self.unrealized_max_drawdown_percent,
|
|
131
|
+
"Real Max Drawdown": self.real_max_drawdown,
|
|
132
|
+
"Real Max Drawdown %": self.real_max_drawdown_percent,
|
|
133
|
+
"Buy & Hold Return": self.buy_and_hold_return,
|
|
134
|
+
"Buy & Hold Return %": self.buy_and_hold_return_percent,
|
|
135
|
+
"Sharpe Ratio": self.sharpe_ratio,
|
|
136
|
+
"Sortino Ratio": self.sortino_ratio,
|
|
137
|
+
"Profit Factor": self.profit_factor,
|
|
138
|
+
|
|
139
|
+
# Trades
|
|
140
|
+
"Total Trades": self.total_trades,
|
|
141
|
+
"Winning Trades": self.winning_trades,
|
|
142
|
+
"Losing Trades": self.losing_trades,
|
|
143
|
+
"Percent Profitable": self.percent_profitable,
|
|
144
|
+
"Avg Trade": self.avg_trade,
|
|
145
|
+
"Avg Trade %": self.avg_trade_percent,
|
|
146
|
+
"Avg Winning Trade": self.avg_winning_trade,
|
|
147
|
+
"Avg Winning Trade %": self.avg_winning_trade_percent,
|
|
148
|
+
"Avg Losing Trade": self.avg_losing_trade,
|
|
149
|
+
"Avg Losing Trade %": self.avg_losing_trade_percent,
|
|
150
|
+
"Ratio Avg Win/Loss": self.ratio_avg_win_loss,
|
|
151
|
+
"Largest Winning Trade": self.largest_winning_trade,
|
|
152
|
+
"Largest Winning Trade %": self.largest_winning_trade_percent,
|
|
153
|
+
"Largest Losing Trade": self.largest_losing_trade,
|
|
154
|
+
"Largest Losing Trade %": self.largest_losing_trade_percent,
|
|
155
|
+
"Avg # Bars in Trades": self.avg_bars_in_trades,
|
|
156
|
+
"Avg # Bars in Winning Trades": self.avg_bars_in_winning_trades,
|
|
157
|
+
"Avg # Bars in Losing Trades": self.avg_bars_in_losing_trades,
|
|
158
|
+
"Max Consecutive Wins": self.max_cons_winning_trades,
|
|
159
|
+
"Max Consecutive Losses": self.max_cons_losing_trades,
|
|
160
|
+
|
|
161
|
+
# Long trades
|
|
162
|
+
"Long Trades": self.long_trades,
|
|
163
|
+
"Long Winning Trades": self.long_winning_trades,
|
|
164
|
+
"Long Net Profit": self.long_net_profit,
|
|
165
|
+
"Long Net Profit %": self.long_net_profit_percent,
|
|
166
|
+
"Long Gross Profit": self.long_gross_profit,
|
|
167
|
+
"Long Gross Profit %": self.long_gross_profit_percent,
|
|
168
|
+
"Long Gross Loss": self.long_gross_loss,
|
|
169
|
+
"Long Gross Loss %": self.long_gross_loss_percent,
|
|
170
|
+
"Long Avg Trade": self.long_avg_trade,
|
|
171
|
+
"Long Avg Trade %": self.long_avg_trade_percent,
|
|
172
|
+
"Long Largest Winning Trade": self.long_largest_winning_trade,
|
|
173
|
+
"Long Largest Winning Trade %": self.long_largest_winning_trade_percent,
|
|
174
|
+
"Long Largest Losing Trade": self.long_largest_losing_trade,
|
|
175
|
+
"Long Largest Losing Trade %": self.long_largest_losing_trade_percent,
|
|
176
|
+
"Long Avg # Bars": self.long_avg_bars,
|
|
177
|
+
|
|
178
|
+
# Short trades
|
|
179
|
+
"Short Trades": self.short_trades,
|
|
180
|
+
"Short Winning Trades": self.short_winning_trades,
|
|
181
|
+
"Short Net Profit": self.short_net_profit,
|
|
182
|
+
"Short Net Profit %": self.short_net_profit_percent,
|
|
183
|
+
"Short Gross Profit": self.short_gross_profit,
|
|
184
|
+
"Short Gross Profit %": self.short_gross_profit_percent,
|
|
185
|
+
"Short Gross Loss": self.short_gross_loss,
|
|
186
|
+
"Short Gross Loss %": self.short_gross_loss_percent,
|
|
187
|
+
"Short Avg Trade": self.short_avg_trade,
|
|
188
|
+
"Short Avg Trade %": self.short_avg_trade_percent,
|
|
189
|
+
"Short Largest Winning Trade": self.short_largest_winning_trade,
|
|
190
|
+
"Short Largest Winning Trade %": self.short_largest_winning_trade_percent,
|
|
191
|
+
"Short Largest Losing Trade": self.short_largest_losing_trade,
|
|
192
|
+
"Short Largest Losing Trade %": self.short_largest_losing_trade_percent,
|
|
193
|
+
"Short Avg # Bars": self.short_avg_bars,
|
|
194
|
+
|
|
195
|
+
# P&L breakdown
|
|
196
|
+
"Total P&L": self.total_pnl,
|
|
197
|
+
"Total P&L %": self.total_pnl_percent,
|
|
198
|
+
"Realized P&L": self.realized_pnl,
|
|
199
|
+
"Realized P&L %": self.realized_pnl_percent,
|
|
200
|
+
"Unrealized P&L": self.unrealized_pnl,
|
|
201
|
+
"Unrealized P&L %": self.unrealized_pnl_percent,
|
|
202
|
+
|
|
203
|
+
# Other
|
|
204
|
+
"Margin Calls": self.margin_calls,
|
|
205
|
+
"Max Contracts Held": self.max_contracts_held,
|
|
206
|
+
"Commission Paid": self.commission_paid,
|
|
207
|
+
"Total Open Trades": self.total_open_trades,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def calculate_strategy_statistics(
|
|
212
|
+
position: PositionBase,
|
|
213
|
+
initial_capital: float,
|
|
214
|
+
equity_curve: list[float] | None = None,
|
|
215
|
+
first_price: float | None = None,
|
|
216
|
+
last_price: float | None = None
|
|
217
|
+
) -> StrategyStatistics:
|
|
218
|
+
"""
|
|
219
|
+
Calculate comprehensive strategy statistics from position data.
|
|
220
|
+
|
|
221
|
+
:param position: PositionBase object containing all trade data
|
|
222
|
+
:param initial_capital: Initial capital for percentage calculations
|
|
223
|
+
:param equity_curve: List of equity values for Sharpe/Sortino calculations
|
|
224
|
+
:param first_price: First price for buy & hold calculation
|
|
225
|
+
:param last_price: Last price for buy & hold calculation
|
|
226
|
+
:return: StrategyStatistics object with all calculated metrics
|
|
227
|
+
"""
|
|
228
|
+
stats = StrategyStatistics()
|
|
229
|
+
|
|
230
|
+
# Basic metrics from position
|
|
231
|
+
stats.net_profit = float(position.netprofit) if not isinstance(position.netprofit, NA) else 0.0
|
|
232
|
+
stats.gross_profit = float(position.grossprofit) if not isinstance(position.grossprofit, NA) else 0.0
|
|
233
|
+
stats.gross_loss = float(position.grossloss) if not isinstance(position.grossloss, NA) else 0.0
|
|
234
|
+
stats.max_equity_drawdown = float(position.max_drawdown) if not isinstance(position.max_drawdown, NA) else 0.0
|
|
235
|
+
stats.max_equity_runup = float(position.max_runup) if not isinstance(position.max_runup, NA) else 0.0
|
|
236
|
+
# Fork-parity drawdown family (percents already computed per-bar in Position)
|
|
237
|
+
stats.unrealized_max_drawdown = float(position.unrealized_max_drawdown)
|
|
238
|
+
stats.unrealized_max_drawdown_percent = float(position.unrealized_max_drawdown_percent)
|
|
239
|
+
stats.real_max_drawdown = float(position.real_max_drawdown)
|
|
240
|
+
stats.real_max_drawdown_percent = float(position.real_max_drawdown_percent)
|
|
241
|
+
|
|
242
|
+
# P&L breakdown: realized (closed) + unrealized (open) = total
|
|
243
|
+
stats.realized_pnl = stats.net_profit
|
|
244
|
+
stats.unrealized_pnl = float(position.openprofit) if not isinstance(position.openprofit, NA) else 0.0
|
|
245
|
+
stats.total_pnl = stats.realized_pnl + stats.unrealized_pnl
|
|
246
|
+
|
|
247
|
+
# Calculate percentages
|
|
248
|
+
if initial_capital > 0:
|
|
249
|
+
stats.net_profit_percent = (stats.net_profit / initial_capital) * 100
|
|
250
|
+
stats.gross_profit_percent = (stats.gross_profit / initial_capital) * 100
|
|
251
|
+
stats.gross_loss_percent = (stats.gross_loss / initial_capital) * 100
|
|
252
|
+
stats.max_equity_drawdown_percent = (stats.max_equity_drawdown / initial_capital) * 100
|
|
253
|
+
stats.max_equity_runup_percent = (stats.max_equity_runup / initial_capital) * 100
|
|
254
|
+
stats.realized_pnl_percent = (stats.realized_pnl / initial_capital) * 100
|
|
255
|
+
stats.unrealized_pnl_percent = (stats.unrealized_pnl / initial_capital) * 100
|
|
256
|
+
stats.total_pnl_percent = (stats.total_pnl / initial_capital) * 100
|
|
257
|
+
|
|
258
|
+
# Buy & Hold calculation
|
|
259
|
+
if first_price and last_price and first_price > 0:
|
|
260
|
+
buy_hold_shares = initial_capital / first_price
|
|
261
|
+
buy_hold_value = buy_hold_shares * last_price
|
|
262
|
+
stats.buy_and_hold_return = buy_hold_value - initial_capital
|
|
263
|
+
stats.buy_and_hold_return_percent = (stats.buy_and_hold_return / initial_capital) * 100
|
|
264
|
+
|
|
265
|
+
# Get all trades (closed + open)
|
|
266
|
+
all_trades: list[Trade] = list(position.closed_trades) + position.open_trades
|
|
267
|
+
closed_trades = list(position.closed_trades)
|
|
268
|
+
|
|
269
|
+
stats.total_trades = position.closed_trades_count
|
|
270
|
+
stats.winning_trades = position.wintrades
|
|
271
|
+
stats.losing_trades = position.losstrades
|
|
272
|
+
stats.total_open_trades = len(position.open_trades)
|
|
273
|
+
|
|
274
|
+
# Percent profitable
|
|
275
|
+
if stats.total_trades > 0:
|
|
276
|
+
stats.percent_profitable = (stats.winning_trades / stats.total_trades) * 100
|
|
277
|
+
|
|
278
|
+
# Profit factor
|
|
279
|
+
if stats.gross_loss != 0:
|
|
280
|
+
stats.profit_factor = abs(stats.gross_profit / stats.gross_loss)
|
|
281
|
+
|
|
282
|
+
# Calculate trade statistics
|
|
283
|
+
if closed_trades:
|
|
284
|
+
# Commission
|
|
285
|
+
stats.commission_paid = float(sum(trade.commission for trade in closed_trades))
|
|
286
|
+
|
|
287
|
+
# Average calculations
|
|
288
|
+
stats.avg_trade = stats.net_profit / len(closed_trades)
|
|
289
|
+
stats.avg_trade_percent = stats.net_profit_percent / len(closed_trades)
|
|
290
|
+
|
|
291
|
+
# Separate winning and losing trades
|
|
292
|
+
winning_trades = [t for t in closed_trades if float(t.profit) > 0]
|
|
293
|
+
losing_trades = [t for t in closed_trades if float(t.profit) < 0]
|
|
294
|
+
|
|
295
|
+
# Winning trades statistics
|
|
296
|
+
if winning_trades:
|
|
297
|
+
total_win_profit = sum(float(t.profit) for t in winning_trades)
|
|
298
|
+
stats.avg_winning_trade = total_win_profit / len(winning_trades)
|
|
299
|
+
stats.avg_winning_trade_percent = stats.avg_winning_trade / initial_capital * 100
|
|
300
|
+
|
|
301
|
+
# Largest winning trade
|
|
302
|
+
max_win = max(winning_trades, key=lambda t: float(t.profit))
|
|
303
|
+
stats.largest_winning_trade = float(max_win.profit)
|
|
304
|
+
stats.largest_winning_trade_percent = float(max_win.profit_percent)
|
|
305
|
+
|
|
306
|
+
# Average bars in winning trades
|
|
307
|
+
bars_in_wins = [t.exit_bar_index - t.entry_bar_index for t in winning_trades if t.exit_bar_index >= 0]
|
|
308
|
+
if bars_in_wins:
|
|
309
|
+
stats.avg_bars_in_winning_trades = sum(bars_in_wins) / len(bars_in_wins)
|
|
310
|
+
|
|
311
|
+
# Losing trades statistics
|
|
312
|
+
if losing_trades:
|
|
313
|
+
total_loss_profit = sum(float(t.profit) for t in losing_trades)
|
|
314
|
+
stats.avg_losing_trade = total_loss_profit / len(losing_trades)
|
|
315
|
+
stats.avg_losing_trade_percent = stats.avg_losing_trade / initial_capital * 100
|
|
316
|
+
|
|
317
|
+
# Largest losing trade
|
|
318
|
+
max_loss = min(losing_trades, key=lambda t: float(t.profit))
|
|
319
|
+
stats.largest_losing_trade = float(max_loss.profit)
|
|
320
|
+
stats.largest_losing_trade_percent = float(max_loss.profit_percent)
|
|
321
|
+
|
|
322
|
+
# Average bars in losing trades
|
|
323
|
+
bars_in_losses = [t.exit_bar_index - t.entry_bar_index for t in losing_trades if t.exit_bar_index >= 0]
|
|
324
|
+
if bars_in_losses:
|
|
325
|
+
stats.avg_bars_in_losing_trades = sum(bars_in_losses) / len(bars_in_losses)
|
|
326
|
+
|
|
327
|
+
# Ratio of average win to average loss
|
|
328
|
+
if stats.avg_losing_trade != 0:
|
|
329
|
+
stats.ratio_avg_win_loss = abs(stats.avg_winning_trade / stats.avg_losing_trade)
|
|
330
|
+
|
|
331
|
+
# Average bars in all trades
|
|
332
|
+
bars_in_trades = [t.exit_bar_index - t.entry_bar_index for t in closed_trades if t.exit_bar_index >= 0]
|
|
333
|
+
if bars_in_trades:
|
|
334
|
+
stats.avg_bars_in_trades = sum(bars_in_trades) / len(bars_in_trades)
|
|
335
|
+
|
|
336
|
+
# Long/Short breakdown
|
|
337
|
+
long_trades = [t for t in closed_trades if t.sign > 0]
|
|
338
|
+
short_trades = [t for t in closed_trades if t.sign < 0]
|
|
339
|
+
|
|
340
|
+
# Long statistics
|
|
341
|
+
if long_trades:
|
|
342
|
+
stats.long_trades = len(long_trades)
|
|
343
|
+
long_winning = [t for t in long_trades if float(t.profit) > 0]
|
|
344
|
+
long_losing = [t for t in long_trades if float(t.profit) < 0]
|
|
345
|
+
|
|
346
|
+
stats.long_winning_trades = len(long_winning)
|
|
347
|
+
stats.long_net_profit = sum(float(t.profit) for t in long_trades)
|
|
348
|
+
stats.long_net_profit_percent = (stats.long_net_profit / initial_capital) * 100
|
|
349
|
+
|
|
350
|
+
if long_winning:
|
|
351
|
+
stats.long_gross_profit = sum(float(t.profit) for t in long_winning)
|
|
352
|
+
stats.long_gross_profit_percent = (stats.long_gross_profit / initial_capital) * 100
|
|
353
|
+
max_long_win = max(long_winning, key=lambda t: float(t.profit))
|
|
354
|
+
stats.long_largest_winning_trade = float(max_long_win.profit)
|
|
355
|
+
stats.long_largest_winning_trade_percent = float(max_long_win.profit_percent)
|
|
356
|
+
|
|
357
|
+
if long_losing:
|
|
358
|
+
stats.long_gross_loss = sum(float(t.profit) for t in long_losing)
|
|
359
|
+
stats.long_gross_loss_percent = (stats.long_gross_loss / initial_capital) * 100
|
|
360
|
+
max_long_loss = min(long_losing, key=lambda t: float(t.profit))
|
|
361
|
+
stats.long_largest_losing_trade = float(max_long_loss.profit)
|
|
362
|
+
stats.long_largest_losing_trade_percent = float(max_long_loss.profit_percent)
|
|
363
|
+
|
|
364
|
+
stats.long_avg_trade = stats.long_net_profit / len(long_trades)
|
|
365
|
+
stats.long_avg_trade_percent = stats.long_net_profit_percent / len(long_trades)
|
|
366
|
+
|
|
367
|
+
long_bars = [t.exit_bar_index - t.entry_bar_index for t in long_trades if t.exit_bar_index >= 0]
|
|
368
|
+
if long_bars:
|
|
369
|
+
stats.long_avg_bars = sum(long_bars) / len(long_bars)
|
|
370
|
+
|
|
371
|
+
# Short statistics
|
|
372
|
+
if short_trades:
|
|
373
|
+
stats.short_trades = len(short_trades)
|
|
374
|
+
short_winning = [t for t in short_trades if float(t.profit) > 0]
|
|
375
|
+
short_losing = [t for t in short_trades if float(t.profit) < 0]
|
|
376
|
+
|
|
377
|
+
stats.short_winning_trades = len(short_winning)
|
|
378
|
+
stats.short_net_profit = sum(float(t.profit) for t in short_trades)
|
|
379
|
+
stats.short_net_profit_percent = (stats.short_net_profit / initial_capital) * 100
|
|
380
|
+
|
|
381
|
+
if short_winning:
|
|
382
|
+
stats.short_gross_profit = sum(float(t.profit) for t in short_winning)
|
|
383
|
+
stats.short_gross_profit_percent = (stats.short_gross_profit / initial_capital) * 100
|
|
384
|
+
max_short_win = max(short_winning, key=lambda t: float(t.profit))
|
|
385
|
+
stats.short_largest_winning_trade = float(max_short_win.profit)
|
|
386
|
+
stats.short_largest_winning_trade_percent = float(max_short_win.profit_percent)
|
|
387
|
+
|
|
388
|
+
if short_losing:
|
|
389
|
+
stats.short_gross_loss = sum(float(t.profit) for t in short_losing)
|
|
390
|
+
stats.short_gross_loss_percent = (stats.short_gross_loss / initial_capital) * 100
|
|
391
|
+
max_short_loss = min(short_losing, key=lambda t: float(t.profit))
|
|
392
|
+
stats.short_largest_losing_trade = float(max_short_loss.profit)
|
|
393
|
+
stats.short_largest_losing_trade_percent = float(max_short_loss.profit_percent)
|
|
394
|
+
|
|
395
|
+
stats.short_avg_trade = stats.short_net_profit / len(short_trades)
|
|
396
|
+
stats.short_avg_trade_percent = stats.short_net_profit_percent / len(short_trades)
|
|
397
|
+
|
|
398
|
+
short_bars = [t.exit_bar_index - t.entry_bar_index for t in short_trades if t.exit_bar_index >= 0]
|
|
399
|
+
if short_bars:
|
|
400
|
+
stats.short_avg_bars = sum(short_bars) / len(short_bars)
|
|
401
|
+
|
|
402
|
+
# Max consecutive wins/losses
|
|
403
|
+
if closed_trades:
|
|
404
|
+
current_wins = 0
|
|
405
|
+
current_losses = 0
|
|
406
|
+
max_wins = 0
|
|
407
|
+
max_losses = 0
|
|
408
|
+
|
|
409
|
+
for trade in closed_trades:
|
|
410
|
+
profit = float(trade.profit)
|
|
411
|
+
if profit > 0:
|
|
412
|
+
current_wins += 1
|
|
413
|
+
current_losses = 0
|
|
414
|
+
max_wins = max(max_wins, current_wins)
|
|
415
|
+
elif profit < 0:
|
|
416
|
+
current_losses += 1
|
|
417
|
+
current_wins = 0
|
|
418
|
+
max_losses = max(max_losses, current_losses)
|
|
419
|
+
else:
|
|
420
|
+
current_wins = 0
|
|
421
|
+
current_losses = 0
|
|
422
|
+
|
|
423
|
+
stats.max_cons_winning_trades = max_wins
|
|
424
|
+
stats.max_cons_losing_trades = max_losses
|
|
425
|
+
|
|
426
|
+
# Max contracts held
|
|
427
|
+
if all_trades:
|
|
428
|
+
max_size = 0.0
|
|
429
|
+
current_positions: list[Trade] = []
|
|
430
|
+
|
|
431
|
+
# Sort all trades by entry time
|
|
432
|
+
sorted_trades = sorted(all_trades, key=lambda t: t.entry_time)
|
|
433
|
+
|
|
434
|
+
for trade in sorted_trades:
|
|
435
|
+
# Add to current positions
|
|
436
|
+
current_positions.append(trade)
|
|
437
|
+
|
|
438
|
+
# Remove closed positions that exit before this entry
|
|
439
|
+
current_positions = [t for t in current_positions
|
|
440
|
+
if t.exit_time < 0 or t.exit_time > trade.entry_time]
|
|
441
|
+
|
|
442
|
+
# Calculate current size
|
|
443
|
+
current_size = sum(abs(t.size) for t in current_positions)
|
|
444
|
+
max_size = max(max_size, current_size)
|
|
445
|
+
|
|
446
|
+
stats.max_contracts_held = max_size
|
|
447
|
+
|
|
448
|
+
# Sharpe and Sortino ratios (if equity curve provided)
|
|
449
|
+
if equity_curve and len(equity_curve) > 1:
|
|
450
|
+
returns = []
|
|
451
|
+
for i in range(1, len(equity_curve)):
|
|
452
|
+
if equity_curve[i - 1] != 0:
|
|
453
|
+
ret = (equity_curve[i] - equity_curve[i - 1]) / equity_curve[i - 1]
|
|
454
|
+
returns.append(ret)
|
|
455
|
+
|
|
456
|
+
if returns:
|
|
457
|
+
avg_return = sum(returns) / len(returns)
|
|
458
|
+
|
|
459
|
+
# Sharpe ratio calculation
|
|
460
|
+
if len(returns) > 1:
|
|
461
|
+
variance = sum((r - avg_return) ** 2 for r in returns) / (len(returns) - 1)
|
|
462
|
+
std_dev = math.sqrt(variance)
|
|
463
|
+
if std_dev > 0:
|
|
464
|
+
# Annualized Sharpe ratio (assuming daily returns and 252 trading days)
|
|
465
|
+
stats.sharpe_ratio = (avg_return * 252) / (std_dev * math.sqrt(252))
|
|
466
|
+
|
|
467
|
+
# Sortino ratio calculation
|
|
468
|
+
downside_returns = [r for r in returns if r < 0]
|
|
469
|
+
if len(downside_returns) > 1:
|
|
470
|
+
downside_variance = sum(r ** 2 for r in downside_returns) / len(downside_returns)
|
|
471
|
+
downside_std = math.sqrt(downside_variance)
|
|
472
|
+
if downside_std > 0:
|
|
473
|
+
# Annualized Sortino ratio
|
|
474
|
+
stats.sortino_ratio = (avg_return * 252) / (downside_std * math.sqrt(252))
|
|
475
|
+
|
|
476
|
+
return stats
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def write_strategy_statistics_csv(
|
|
480
|
+
stats: StrategyStatistics,
|
|
481
|
+
csv_writer: CSVWriter
|
|
482
|
+
) -> None:
|
|
483
|
+
"""
|
|
484
|
+
Write strategy statistics to CSV file in TradingView format.
|
|
485
|
+
|
|
486
|
+
:param stats: Calculated strategy statistics
|
|
487
|
+
:param csv_writer: CSV writer instance (already opened)
|
|
488
|
+
"""
|
|
489
|
+
# P&L breakdown: Total / Realized / Unrealized (fork-parity)
|
|
490
|
+
csv_writer.write("Total P&L",
|
|
491
|
+
stats.total_pnl, stats.total_pnl_percent,
|
|
492
|
+
"", "", "", ""
|
|
493
|
+
)
|
|
494
|
+
csv_writer.write("Realized P&L",
|
|
495
|
+
stats.realized_pnl, stats.realized_pnl_percent,
|
|
496
|
+
"", "", "", ""
|
|
497
|
+
)
|
|
498
|
+
csv_writer.write("Unrealized P&L",
|
|
499
|
+
stats.unrealized_pnl, stats.unrealized_pnl_percent,
|
|
500
|
+
"", "", "", ""
|
|
501
|
+
)
|
|
502
|
+
# Row 1: Net profit
|
|
503
|
+
csv_writer.write("Net profit",
|
|
504
|
+
stats.net_profit, stats.net_profit_percent,
|
|
505
|
+
stats.long_net_profit, stats.long_net_profit_percent,
|
|
506
|
+
stats.short_net_profit, stats.short_net_profit_percent
|
|
507
|
+
)
|
|
508
|
+
# Row 2: Gross profit
|
|
509
|
+
csv_writer.write("Gross profit",
|
|
510
|
+
stats.gross_profit, stats.gross_profit_percent,
|
|
511
|
+
stats.long_gross_profit, stats.long_gross_profit_percent,
|
|
512
|
+
stats.short_gross_profit, stats.short_gross_profit_percent
|
|
513
|
+
)
|
|
514
|
+
# Row 3: Gross loss
|
|
515
|
+
csv_writer.write("Gross loss",
|
|
516
|
+
stats.gross_loss, stats.gross_loss_percent,
|
|
517
|
+
stats.long_gross_loss, stats.long_gross_loss_percent,
|
|
518
|
+
stats.short_gross_loss, stats.short_gross_loss_percent
|
|
519
|
+
)
|
|
520
|
+
# Row 4: Commission paid
|
|
521
|
+
csv_writer.write("Commission paid",
|
|
522
|
+
stats.commission_paid, "",
|
|
523
|
+
stats.commission_paid, "",
|
|
524
|
+
0, ""
|
|
525
|
+
)
|
|
526
|
+
# Row 5: Buy & hold return
|
|
527
|
+
csv_writer.write("Buy & hold return",
|
|
528
|
+
stats.buy_and_hold_return, stats.buy_and_hold_return_percent,
|
|
529
|
+
"", "", "", ""
|
|
530
|
+
)
|
|
531
|
+
# Row 6: Max equity run-up
|
|
532
|
+
csv_writer.write("Max equity run-up",
|
|
533
|
+
stats.max_equity_runup, stats.max_equity_runup_percent,
|
|
534
|
+
"", "", "", ""
|
|
535
|
+
)
|
|
536
|
+
# Row 7: Max equity drawdown
|
|
537
|
+
csv_writer.write("Max equity drawdown",
|
|
538
|
+
stats.max_equity_drawdown, stats.max_equity_drawdown_percent,
|
|
539
|
+
"", "", "", ""
|
|
540
|
+
)
|
|
541
|
+
# Unrealized (intrabar) + Real max drawdown (fork-parity)
|
|
542
|
+
csv_writer.write("Unrealized max drawdown",
|
|
543
|
+
stats.unrealized_max_drawdown, stats.unrealized_max_drawdown_percent,
|
|
544
|
+
"", "", "", ""
|
|
545
|
+
)
|
|
546
|
+
csv_writer.write("Real max drawdown",
|
|
547
|
+
stats.real_max_drawdown, stats.real_max_drawdown_percent,
|
|
548
|
+
"", "", "", ""
|
|
549
|
+
)
|
|
550
|
+
# Row 8: Max contracts held
|
|
551
|
+
csv_writer.write("Max contracts held",
|
|
552
|
+
stats.max_contracts_held, "",
|
|
553
|
+
stats.max_contracts_held, "",
|
|
554
|
+
0, ""
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# Empty row
|
|
558
|
+
csv_writer.write("", "", "", "", "", "", "")
|
|
559
|
+
|
|
560
|
+
# Trade statistics section
|
|
561
|
+
csv_writer.write("Total trades",
|
|
562
|
+
stats.total_trades, "",
|
|
563
|
+
stats.long_trades, "",
|
|
564
|
+
stats.short_trades, ""
|
|
565
|
+
)
|
|
566
|
+
csv_writer.write("Total open trades",
|
|
567
|
+
stats.total_open_trades, "",
|
|
568
|
+
stats.total_open_trades, "",
|
|
569
|
+
0, ""
|
|
570
|
+
)
|
|
571
|
+
csv_writer.write("Winning trades",
|
|
572
|
+
stats.winning_trades, "",
|
|
573
|
+
stats.long_winning_trades, "",
|
|
574
|
+
stats.short_winning_trades, ""
|
|
575
|
+
)
|
|
576
|
+
csv_writer.write("Losing trades",
|
|
577
|
+
stats.losing_trades, "",
|
|
578
|
+
stats.long_trades - stats.long_winning_trades, "",
|
|
579
|
+
stats.short_trades - stats.short_winning_trades, ""
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Calculate percentages with safe division
|
|
583
|
+
all_percent = stats.percent_profitable
|
|
584
|
+
long_percent = (stats.long_winning_trades / stats.long_trades * 100) if stats.long_trades > 0 else 0
|
|
585
|
+
short_percent = (stats.short_winning_trades / stats.short_trades * 100) if stats.short_trades > 0 else 0
|
|
586
|
+
|
|
587
|
+
csv_writer.write("Percent profitable",
|
|
588
|
+
"", all_percent,
|
|
589
|
+
"", long_percent,
|
|
590
|
+
"", short_percent
|
|
591
|
+
)
|
|
592
|
+
csv_writer.write("Avg P&L",
|
|
593
|
+
stats.avg_trade, stats.avg_trade_percent,
|
|
594
|
+
stats.long_avg_trade, stats.long_avg_trade_percent,
|
|
595
|
+
stats.short_avg_trade, stats.short_avg_trade_percent
|
|
596
|
+
)
|
|
597
|
+
csv_writer.write("Avg winning trade",
|
|
598
|
+
stats.avg_winning_trade, stats.avg_winning_trade_percent,
|
|
599
|
+
stats.avg_winning_trade, stats.avg_winning_trade_percent,
|
|
600
|
+
0, ""
|
|
601
|
+
)
|
|
602
|
+
csv_writer.write("Avg losing trade",
|
|
603
|
+
stats.avg_losing_trade, stats.avg_losing_trade_percent,
|
|
604
|
+
stats.avg_losing_trade, stats.avg_losing_trade_percent,
|
|
605
|
+
0, ""
|
|
606
|
+
)
|
|
607
|
+
csv_writer.write("Ratio avg win / avg loss",
|
|
608
|
+
stats.ratio_avg_win_loss, "",
|
|
609
|
+
stats.ratio_avg_win_loss, "",
|
|
610
|
+
0, ""
|
|
611
|
+
)
|
|
612
|
+
csv_writer.write("Largest winning trade",
|
|
613
|
+
stats.largest_winning_trade, "",
|
|
614
|
+
stats.long_largest_winning_trade, "",
|
|
615
|
+
stats.short_largest_winning_trade, ""
|
|
616
|
+
)
|
|
617
|
+
csv_writer.write("Largest winning trade percent",
|
|
618
|
+
"", stats.largest_winning_trade_percent,
|
|
619
|
+
"", stats.long_largest_winning_trade_percent,
|
|
620
|
+
"", stats.short_largest_winning_trade_percent
|
|
621
|
+
)
|
|
622
|
+
csv_writer.write("Largest losing trade",
|
|
623
|
+
stats.largest_losing_trade, "",
|
|
624
|
+
stats.long_largest_losing_trade, "",
|
|
625
|
+
stats.short_largest_losing_trade, ""
|
|
626
|
+
)
|
|
627
|
+
csv_writer.write("Largest losing trade percent",
|
|
628
|
+
"", stats.largest_losing_trade_percent,
|
|
629
|
+
"", stats.long_largest_losing_trade_percent,
|
|
630
|
+
"", stats.short_largest_losing_trade_percent
|
|
631
|
+
)
|
|
632
|
+
csv_writer.write("Avg # bars in trades",
|
|
633
|
+
stats.avg_bars_in_trades, "",
|
|
634
|
+
stats.long_avg_bars, "",
|
|
635
|
+
stats.short_avg_bars, ""
|
|
636
|
+
)
|
|
637
|
+
csv_writer.write("Avg # bars in winning trades",
|
|
638
|
+
stats.avg_bars_in_winning_trades, "",
|
|
639
|
+
stats.avg_bars_in_winning_trades, "",
|
|
640
|
+
0, ""
|
|
641
|
+
)
|
|
642
|
+
csv_writer.write("Avg # bars in losing trades",
|
|
643
|
+
stats.avg_bars_in_losing_trades, "",
|
|
644
|
+
stats.avg_bars_in_losing_trades, "",
|
|
645
|
+
0, ""
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
# Empty row
|
|
649
|
+
csv_writer.write("", "", "", "", "", "", "")
|
|
650
|
+
|
|
651
|
+
# Additional statistics
|
|
652
|
+
csv_writer.write("Sharpe ratio",
|
|
653
|
+
stats.sharpe_ratio, "",
|
|
654
|
+
"", "", "", ""
|
|
655
|
+
)
|
|
656
|
+
csv_writer.write("Sortino ratio",
|
|
657
|
+
stats.sortino_ratio, "",
|
|
658
|
+
"", "", "", ""
|
|
659
|
+
)
|
|
660
|
+
csv_writer.write("Profit factor",
|
|
661
|
+
stats.profit_factor, "",
|
|
662
|
+
stats.profit_factor, "",
|
|
663
|
+
0, ""
|
|
664
|
+
)
|
|
665
|
+
csv_writer.write("Margin calls",
|
|
666
|
+
stats.margin_calls, "",
|
|
667
|
+
stats.margin_calls, "",
|
|
668
|
+
stats.margin_calls, ""
|
|
669
|
+
)
|