PyAlgoEngine 0.12.3__cp315-cp315-win_amd64.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.
Files changed (156) hide show
  1. algo_engine/__infra__.pxd +3 -0
  2. algo_engine/__init__.pxd +3 -0
  3. algo_engine/__init__.py +107 -0
  4. algo_engine/apps/__init__.py +17 -0
  5. algo_engine/apps/backtest/__init__.py +20 -0
  6. algo_engine/apps/backtest/doc_server.py +328 -0
  7. algo_engine/apps/backtest/static/styles/dash.css +48 -0
  8. algo_engine/apps/backtest/templates/dash.html +20 -0
  9. algo_engine/apps/backtest/templates/index.html +40 -0
  10. algo_engine/apps/backtest/tester.py +252 -0
  11. algo_engine/apps/backtest/web_app.py +125 -0
  12. algo_engine/apps/bokeh_server.py +245 -0
  13. algo_engine/apps/demo/__init__.py +0 -0
  14. algo_engine/apps/demo/test.py +40 -0
  15. algo_engine/apps/sim_input/__init__.py +23 -0
  16. algo_engine/apps/sim_input/client.py +412 -0
  17. algo_engine/apps/sim_input/sim_keyboard.py +88 -0
  18. algo_engine/apps/sim_input/sim_mouse.py +137 -0
  19. algo_engine/apps/sim_input/window.py +162 -0
  20. algo_engine/backtest/__init__.py +19 -0
  21. algo_engine/backtest/__main__.py +51 -0
  22. algo_engine/backtest/metrics.py +179 -0
  23. algo_engine/backtest/replay.py +635 -0
  24. algo_engine/backtest/sim_match.py +506 -0
  25. algo_engine/base/__infra__.pxd +3 -0
  26. algo_engine/base/__init__.pxd +3 -0
  27. algo_engine/base/__init__.py +60 -0
  28. algo_engine/base/c_allocator_protocol.c +11608 -0
  29. algo_engine/base/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  30. algo_engine/base/c_allocator_protocol.pxd +24 -0
  31. algo_engine/base/c_allocator_protocol.pyi +68 -0
  32. algo_engine/base/c_allocator_protocol.pyx +111 -0
  33. algo_engine/base/c_intern_string.c +5908 -0
  34. algo_engine/base/c_intern_string.cp315-win_amd64.pyd +0 -0
  35. algo_engine/base/c_intern_string.pxd +14 -0
  36. algo_engine/base/c_intern_string.pyi +22 -0
  37. algo_engine/base/c_intern_string.pyx +17 -0
  38. algo_engine/base/c_market_data/__infra__.pxd +196 -0
  39. algo_engine/base/c_market_data/__init__.pxd +196 -0
  40. algo_engine/base/c_market_data/__init__.py +24 -0
  41. algo_engine/base/c_market_data/c_candlestick.c +18996 -0
  42. algo_engine/base/c_market_data/c_candlestick.cp315-win_amd64.pyd +0 -0
  43. algo_engine/base/c_market_data/c_candlestick.pxd +27 -0
  44. algo_engine/base/c_market_data/c_candlestick.pyi +217 -0
  45. algo_engine/base/c_market_data/c_candlestick.pyx +255 -0
  46. algo_engine/base/c_market_data/c_internal.c +14059 -0
  47. algo_engine/base/c_market_data/c_internal.cp315-win_amd64.pyd +0 -0
  48. algo_engine/base/c_market_data/c_internal.pxd +14 -0
  49. algo_engine/base/c_market_data/c_internal.pyi +47 -0
  50. algo_engine/base/c_market_data/c_internal.pyx +41 -0
  51. algo_engine/base/c_market_data/c_market_data.c +30420 -0
  52. algo_engine/base/c_market_data/c_market_data.cp315-win_amd64.pyd +0 -0
  53. algo_engine/base/c_market_data/c_market_data.h +1464 -0
  54. algo_engine/base/c_market_data/c_market_data.pxd +414 -0
  55. algo_engine/base/c_market_data/c_market_data.pyi +550 -0
  56. algo_engine/base/c_market_data/c_market_data.pyx +701 -0
  57. algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
  58. algo_engine/base/c_market_data/c_market_data_buffer.cp315-win_amd64.pyd +0 -0
  59. algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
  60. algo_engine/base/c_market_data/c_market_data_buffer.pxd +143 -0
  61. algo_engine/base/c_market_data/c_market_data_buffer.pyi +355 -0
  62. algo_engine/base/c_market_data/c_market_data_buffer.pyx +571 -0
  63. algo_engine/base/c_market_data/c_market_data_config.h +29 -0
  64. algo_engine/base/c_market_data/c_tick.c +44303 -0
  65. algo_engine/base/c_market_data/c_tick.cp315-win_amd64.pyd +0 -0
  66. algo_engine/base/c_market_data/c_tick.pxd +53 -0
  67. algo_engine/base/c_market_data/c_tick.pyi +466 -0
  68. algo_engine/base/c_market_data/c_tick.pyx +673 -0
  69. algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
  70. algo_engine/base/c_market_data/c_trade_utils.cp315-win_amd64.pyd +0 -0
  71. algo_engine/base/c_market_data/c_trade_utils.pxd +53 -0
  72. algo_engine/base/c_market_data/c_trade_utils.pyi +602 -0
  73. algo_engine/base/c_market_data/c_trade_utils.pyx +609 -0
  74. algo_engine/base/c_market_data/c_transaction.c +23558 -0
  75. algo_engine/base/c_market_data/c_transaction.cp315-win_amd64.pyd +0 -0
  76. algo_engine/base/c_market_data/c_transaction.pxd +27 -0
  77. algo_engine/base/c_market_data/c_transaction.pyi +433 -0
  78. algo_engine/base/c_market_data/c_transaction.pyx +460 -0
  79. algo_engine/base/console_utils.py +1070 -0
  80. algo_engine/base/finance_decimal.py +258 -0
  81. algo_engine/base/telemetrics.py +18 -0
  82. algo_engine/engine/__infra__.pxd +10 -0
  83. algo_engine/engine/__init__.pxd +10 -0
  84. algo_engine/engine/__init__.py +40 -0
  85. algo_engine/engine/algo_engine.py +904 -0
  86. algo_engine/engine/c_event_engine.c +16325 -0
  87. algo_engine/engine/c_event_engine.cp315-win_amd64.pyd +0 -0
  88. algo_engine/engine/c_event_engine.pxd +25 -0
  89. algo_engine/engine/c_event_engine.pyi +68 -0
  90. algo_engine/engine/c_market_engine.c +24258 -0
  91. algo_engine/engine/c_market_engine.cp315-win_amd64.pyd +0 -0
  92. algo_engine/engine/c_market_engine.pxd +87 -0
  93. algo_engine/engine/c_market_engine.pyi +357 -0
  94. algo_engine/engine/event_engine.py +53 -0
  95. algo_engine/engine/trade_engine.py +2037 -0
  96. algo_engine/exchange_profile/__infra__.pxd +100 -0
  97. algo_engine/exchange_profile/__init__.pxd +100 -0
  98. algo_engine/exchange_profile/__init__.py +53 -0
  99. algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
  100. algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
  101. algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
  102. algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
  103. algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
  104. algo_engine/exchange_profile/c_exchange_profile.cp315-win_amd64.pyd +0 -0
  105. algo_engine/exchange_profile/c_exchange_profile.pxd +336 -0
  106. algo_engine/exchange_profile/c_exchange_profile.pyi +883 -0
  107. algo_engine/exchange_profile/c_exchange_profile.pyx +1495 -0
  108. algo_engine/exchange_profile/c_profile_cn.c +7798 -0
  109. algo_engine/exchange_profile/c_profile_cn.cp315-win_amd64.pyd +0 -0
  110. algo_engine/exchange_profile/c_profile_cn.pxd +1 -0
  111. algo_engine/exchange_profile/c_profile_cn.pyi +3 -0
  112. algo_engine/exchange_profile/c_profile_cn.pyx +1 -0
  113. algo_engine/exchange_profile/c_profile_default.c +7798 -0
  114. algo_engine/exchange_profile/c_profile_default.cp315-win_amd64.pyd +0 -0
  115. algo_engine/exchange_profile/c_profile_default.pxd +1 -0
  116. algo_engine/exchange_profile/c_profile_default.pyi +3 -0
  117. algo_engine/exchange_profile/c_profile_default.pyx +1 -0
  118. algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
  119. algo_engine/exchange_profile/c_profile_dispatcher.cp315-win_amd64.pyd +0 -0
  120. algo_engine/exchange_profile/c_profile_dispatcher.pxd +1 -0
  121. algo_engine/exchange_profile/c_profile_dispatcher.pyi +3 -0
  122. algo_engine/exchange_profile/c_profile_dispatcher.pyx +1 -0
  123. algo_engine/includes/algo_engine/base/c_allocator_protocol.c +11608 -0
  124. algo_engine/includes/algo_engine/base/c_intern_string.c +5908 -0
  125. algo_engine/includes/algo_engine/base/c_market_data/c_candlestick.c +18996 -0
  126. algo_engine/includes/algo_engine/base/c_market_data/c_internal.c +14059 -0
  127. algo_engine/includes/algo_engine/base/c_market_data/c_market_data.c +30420 -0
  128. algo_engine/includes/algo_engine/base/c_market_data/c_market_data.h +1464 -0
  129. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
  130. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
  131. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_config.h +29 -0
  132. algo_engine/includes/algo_engine/base/c_market_data/c_tick.c +44303 -0
  133. algo_engine/includes/algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
  134. algo_engine/includes/algo_engine/base/c_market_data/c_transaction.c +23558 -0
  135. algo_engine/includes/algo_engine/engine/c_event_engine.c +16325 -0
  136. algo_engine/includes/algo_engine/engine/c_market_engine.c +24258 -0
  137. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
  138. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
  139. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
  140. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
  141. algo_engine/includes/algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
  142. algo_engine/includes/algo_engine/exchange_profile/c_profile_cn.c +7798 -0
  143. algo_engine/includes/algo_engine/exchange_profile/c_profile_default.c +7798 -0
  144. algo_engine/includes/algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
  145. algo_engine/monitor/__init__.py +15 -0
  146. algo_engine/monitor/advanced_data_interface.py +334 -0
  147. algo_engine/strategy/__init__.py +44 -0
  148. algo_engine/strategy/strategy_engine.py +441 -0
  149. algo_engine/utils/__init__.py +3 -0
  150. algo_engine/utils/commit_regularizer.py +49 -0
  151. algo_engine/utils/data_utils.py +296 -0
  152. pyalgoengine-0.12.3.dist-info/METADATA +142 -0
  153. pyalgoengine-0.12.3.dist-info/RECORD +156 -0
  154. pyalgoengine-0.12.3.dist-info/WHEEL +5 -0
  155. pyalgoengine-0.12.3.dist-info/licenses/LICENSE +21 -0
  156. pyalgoengine-0.12.3.dist-info/top_level.txt +2 -0
@@ -0,0 +1,162 @@
1
+ __package__ = 'algo_engine.apps.sim_input'
2
+
3
+ import ctypes
4
+ import dataclasses
5
+ import enum
6
+ import os
7
+ from typing import Literal
8
+
9
+ from . import LOGGER
10
+
11
+ LOGGER.getChild('Window')
12
+
13
+ # Define necessary Windows API functions and constants
14
+ user32 = ctypes.windll.user32
15
+ kernel32 = ctypes.windll.kernel32
16
+ psapi = ctypes.windll.psapi
17
+
18
+ # Define constants for access rights
19
+ PROCESS_QUERY_INFORMATION = 0x0400
20
+ PROCESS_VM_READ = 0x0010
21
+
22
+ # Define necessary Windows API function prototypes
23
+ WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.POINTER(ctypes.c_int))
24
+
25
+
26
+ @dataclasses.dataclass(frozen=True)
27
+ class WindowInfo:
28
+ window_name: str
29
+ pid: int
30
+ hwnd: int
31
+ executable_name: str
32
+ executable_path: str
33
+
34
+
35
+ class WindowState(enum.IntEnum):
36
+ SW_SHOWNORMAL = 1 # Show the window normally
37
+ SW_SHOWMINIMIZED = 2 # Minimize the window
38
+ SW_SHOWMAXIMIZED = 3 # Maximize the window
39
+ SW_SHOWNOACTIVATE = 4 # Show the window without activating it
40
+ SW_SHOW = 5 # Show the window and bring it to the foreground
41
+
42
+
43
+ # Function to get PID from window handle
44
+ def get_pid(hwnd):
45
+ pid = ctypes.c_ulong()
46
+ user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
47
+ return pid.value
48
+
49
+
50
+ # Function to retrieve the executable path and name of a process
51
+ def get_executable_info(pid):
52
+ # Open the process to get information
53
+ h_process = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid)
54
+ if not h_process:
55
+ return None, None
56
+
57
+ # Buffer to hold the path
58
+ path_buffer = ctypes.create_unicode_buffer(1024)
59
+
60
+ # Get the full executable path using GetModuleFileNameEx
61
+ if psapi.GetModuleFileNameExW(h_process, 0, path_buffer, ctypes.byref(ctypes.c_ulong(1024))):
62
+ executable_path = path_buffer.value
63
+ executable_name = os.path.basename(executable_path)
64
+ else:
65
+ executable_path = None
66
+ executable_name = None
67
+
68
+ # Close the process handle
69
+ kernel32.CloseHandle(h_process)
70
+
71
+ return executable_name, executable_path
72
+
73
+
74
+ # Function to check if a window is visible (including minimized)
75
+ def is_window_visible(hwnd):
76
+ return user32.IsWindowVisible(hwnd)
77
+
78
+
79
+ # Function to enumerate windows
80
+ def get_windows() -> list[WindowInfo]:
81
+ windows = []
82
+
83
+ def enum_windows_proc(hwnd, _):
84
+ # Only include visible windows
85
+ if not is_window_visible(hwnd):
86
+ return True
87
+
88
+ # Get window title (for name)
89
+ length = user32.GetWindowTextLengthW(hwnd)
90
+ if length > 0:
91
+ buffer = ctypes.create_unicode_buffer(length + 1)
92
+ user32.GetWindowTextW(hwnd, buffer, length + 1)
93
+ window_name = buffer.value
94
+ else:
95
+ window_name = "Untitled"
96
+
97
+ # Get the PID for the window
98
+ pid = get_pid(hwnd)
99
+
100
+ # Get executable name and path
101
+ executable_name, executable_path = get_executable_info(pid)
102
+
103
+ # Append to the windows list as a tuple (window_name, pid, hwnd, executable_name, executable_path)
104
+ windows.append(WindowInfo(window_name=window_name, pid=pid, hwnd=hwnd, executable_name=executable_name, executable_path=executable_path))
105
+ return True
106
+
107
+ # Enumerate all windows (including child windows)
108
+ user32.EnumWindows(WNDENUMPROC(enum_windows_proc), 0)
109
+
110
+ return windows
111
+
112
+
113
+ def find_window(name: str = None, executable: str = None) -> list[WindowInfo]:
114
+ windows = get_windows()
115
+ matched = []
116
+
117
+ for window in windows:
118
+ if name is not None and name.lower() not in window.name.lower():
119
+ continue
120
+
121
+ if executable is not None and executable.lower() not in window.executable_name.lower():
122
+ continue
123
+
124
+ matched.append(window)
125
+
126
+ return matched
127
+
128
+
129
+ # Function to set the window action (top, maximize, minimize)
130
+ def set_window(window_info: WindowInfo, action: Literal['top', 'maximize', 'minimize', 'max', 'min']):
131
+ hwnd = window_info.hwnd
132
+
133
+ match action:
134
+ case "top":
135
+ # Bring the window to the front (top)
136
+ user32.ShowWindow(hwnd, WindowState.SW_SHOWNORMAL)
137
+ user32.SetForegroundWindow(hwnd)
138
+ case "maximize" | 'max':
139
+ # Maximize the window
140
+ user32.ShowWindow(hwnd, WindowState.SW_SHOWMAXIMIZED)
141
+ case "minimize" | 'min':
142
+ # Minimize the window
143
+ user32.ShowWindow(hwnd, WindowState.SW_SHOWMINIMIZED)
144
+ case _:
145
+ raise ValueError(f"Unknown action: {action}")
146
+
147
+
148
+ def main():
149
+ # Example usage
150
+ windows = get_windows()
151
+ for _ in windows:
152
+ LOGGER.debug(_)
153
+
154
+ firefox = find_window(executable="firefox")[0]
155
+ LOGGER.info(firefox)
156
+
157
+ set_window(window_info=firefox, action='top')
158
+ set_window(window_info=firefox, action='maximize')
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()
@@ -0,0 +1,19 @@
1
+ import logging
2
+
3
+ from .. import LOGGER
4
+
5
+ LOGGER = LOGGER.getChild('BackTest')
6
+
7
+
8
+ def set_logger(logger: logging.Logger):
9
+ global LOGGER
10
+ LOGGER = logger
11
+
12
+ replay.LOGGER = LOGGER.getChild('Replay')
13
+ sim_match.LOGGER = LOGGER.getChild('SimMatch')
14
+
15
+
16
+ from .replay import PyDataScope, MarketDateCallable, MarketDataLoader, MarketDataBulkLoader, Replay, SimpleReplay, ProgressReplay
17
+ from .sim_match import SimMatch
18
+
19
+ __all__ = ['PyDataScope', 'MarketDateCallable', 'MarketDataLoader', 'MarketDataBulkLoader', 'Replay', 'SimpleReplay', 'ProgressReplay', 'SimMatch']
@@ -0,0 +1,51 @@
1
+ __package__ = 'algo_engine.backtest'
2
+
3
+ import datetime
4
+ from collections.abc import Callable
5
+
6
+ import event_engine
7
+
8
+ from ..engine import TOPIC, MarketDataService, Balance, RiskProfile, PositionManagementService
9
+ from ..engine.algo_engine import AlgoRegistry, AlgoEngine
10
+ from ..strategy import EventDMA
11
+ from ..strategy.strategy_engine import StrategyEngine
12
+
13
+
14
+ def test_stop(code=0):
15
+ EVENT_ENGINE.stop()
16
+ # noinspection PyUnresolvedReferences, PyProtectedMember
17
+ # `import os`
18
+ # `os._exit(code)`
19
+
20
+
21
+ def test_start(start_date: datetime.date, end_date: datetime.date, data_loader: Callable, **kwargs):
22
+ EVENT_ENGINE.start()
23
+ STRATEGY_ENGINE.back_test(
24
+ start_date=start_date,
25
+ end_date=end_date,
26
+ data_loader=data_loader,
27
+ **kwargs
28
+ )
29
+
30
+
31
+ # in backtest, the global objects is newly inited to separate from production
32
+ EVENT_ENGINE = event_engine.EventEngine()
33
+ MDS = MarketDataService()
34
+ ALGO_REGISTRY = AlgoRegistry()
35
+ ALGO_ENGINE = AlgoEngine(mds=MDS, registry=ALGO_REGISTRY)
36
+
37
+ BALANCE = Balance()
38
+ RISK_PROFILE = RiskProfile(mds=MDS, balance=BALANCE)
39
+ DMA = EventDMA(event_engine=EVENT_ENGINE, mds=MDS, risk_profile=RISK_PROFILE)
40
+ POSITION_TRACKER = PositionManagementService(dma=DMA, algo_engine=ALGO_ENGINE)
41
+ STRATEGY_ENGINE = StrategyEngine(event_engine=EVENT_ENGINE, position_tracker=POSITION_TRACKER)
42
+ BALANCE.add(strategy=STRATEGY_ENGINE, position_tracker=POSITION_TRACKER)
43
+
44
+ EVENT_ENGINE.register_handler(topic=TOPIC.realtime, handler=MDS.on_market_data)
45
+ EVENT_ENGINE.register_handler(topic=TOPIC.on_report, handler=BALANCE.on_report)
46
+ EVENT_ENGINE.register_handler(topic=TOPIC.on_order, handler=BALANCE.on_order)
47
+ STRATEGY_ENGINE.register()
48
+
49
+ MDS.synthetic_orderbook = True
50
+
51
+ __all__ = ['BALANCE', 'RISK_PROFILE', 'DMA', 'POSITION_TRACKER', 'STRATEGY_ENGINE', 'BALANCE', 'EVENT_ENGINE', 'MDS']
@@ -0,0 +1,179 @@
1
+ import uuid
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+
7
+ class TradeMetrics(object):
8
+ def __init__(self):
9
+ self.trades = {}
10
+ self.trade_batch = []
11
+
12
+ self.exposure = 0.
13
+ self.total_pnl = 0.
14
+ self.total_cash_flow = 0.
15
+
16
+ self.current_pnl = 0.
17
+ self.current_cash_flow = 0.
18
+ self.current_trade_batch = {'cash_flow': 0., 'pnl': 0., 'turnover': 0., 'trades': []}
19
+ self.market_price = None
20
+
21
+ def update(self, market_price: float):
22
+ self.market_price = market_price
23
+ self.total_pnl = self.exposure * market_price + self.total_cash_flow
24
+ self.current_pnl = self.exposure * market_price + self.current_cash_flow
25
+ self.current_trade_batch['pnl'] = self.exposure * market_price + self.current_trade_batch['cash_flow']
26
+
27
+ def add_trades(self, side: int, price: float, timestamp: float, volume: float = None, trade_id: int | str = None):
28
+ assert side in {1, -1}, f"trade side must in {1, -1}, got {side}."
29
+ assert volume is None or volume >= 0, "volume must be positive."
30
+
31
+ if volume is None:
32
+ if self.exposure * side < 0:
33
+ volume = abs(self.exposure)
34
+ elif self.exposure * side > 0:
35
+ volume = 0.
36
+ else:
37
+ volume = 1.
38
+
39
+ if trade_id is None:
40
+ trade_id = uuid.uuid4().int
41
+ elif trade_id in self.trades:
42
+ return
43
+
44
+ # split the trades
45
+ if (target_exposure := self.exposure + volume * side) * self.exposure < 0:
46
+ self.add_trades(side=side, volume=abs(self.exposure), price=price, timestamp=timestamp, trade_id=f'{trade_id}.0')
47
+ volume = volume - abs(self.exposure)
48
+ trade_id = f'{trade_id}.1'
49
+
50
+ self.exposure += volume * side
51
+ self.total_cash_flow -= volume * side * price
52
+ self.total_pnl = self.exposure * price + self.total_cash_flow
53
+ self.current_cash_flow -= volume * side * price
54
+ self.current_pnl = self.exposure * price + self.current_cash_flow
55
+ self.market_price = price
56
+
57
+ self.trades[trade_id] = trade_log = dict(
58
+ side=side,
59
+ volume=volume,
60
+ timestamp=timestamp,
61
+ price=price,
62
+ exposure=self.exposure,
63
+ cash_flow=self.current_cash_flow,
64
+ pnl=self.current_pnl
65
+ )
66
+
67
+ if 'init_side' not in self.current_trade_batch:
68
+ self.current_trade_batch['init_side'] = side
69
+
70
+ self.current_trade_batch['cash_flow'] -= volume * side * price
71
+ self.current_trade_batch['pnl'] = self.exposure * price + self.current_trade_batch['cash_flow']
72
+ self.current_trade_batch['turnover'] += abs(volume) * price
73
+ self.current_trade_batch['trades'].append(trade_log)
74
+
75
+ if not self.exposure:
76
+ self.trade_batch.append(self.current_trade_batch)
77
+ self.current_trade_batch = {'cash_flow': 0., 'pnl': 0., 'turnover': 0., 'trades': []}
78
+ self.current_pnl = self.current_cash_flow = 0.
79
+
80
+ def add_trades_batch(self, trade_logs: pd.DataFrame):
81
+ for timestamp, row in trade_logs.iterrows(): # type: float, dict
82
+ side = row['side']
83
+ price = row['current_price']
84
+ volume = row['signal']
85
+ self.add_trades(side=side, volume=volume, price=price, timestamp=timestamp)
86
+
87
+ def clear(self):
88
+ self.trades.clear()
89
+ self.trade_batch.clear()
90
+
91
+ self.exposure = 0.
92
+ self.total_pnl = 0.
93
+ self.total_cash_flow = 0.
94
+
95
+ self.current_pnl = 0.
96
+ self.current_cash_flow = 0.
97
+ self.current_trade_batch = {'cash_flow': 0., 'pnl': 0., 'turnover': 0., 'trades': []}
98
+ self.market_price = None
99
+
100
+ @property
101
+ def summary(self):
102
+ info_dict = dict(
103
+ total_gain=0.,
104
+ total_loss=0.,
105
+ trade_count=0,
106
+ win_count=0,
107
+ lose_count=0,
108
+ turnover=0.,
109
+ )
110
+
111
+ for trade_batch in self.trade_batch:
112
+ if trade_batch['pnl'] > 0:
113
+ info_dict['total_gain'] += trade_batch['pnl']
114
+ info_dict['trade_count'] += 1
115
+ info_dict['win_count'] += 1
116
+ info_dict['turnover'] += trade_batch['turnover']
117
+ else:
118
+ info_dict['total_loss'] += trade_batch['pnl']
119
+ info_dict['trade_count'] += 1
120
+ info_dict['lose_count'] += 1
121
+ info_dict['turnover'] += trade_batch['turnover']
122
+
123
+ info_dict['win_rate'] = info_dict['win_count'] / info_dict['trade_count'] if info_dict['trade_count'] else 0.
124
+ info_dict['average_gain'] = info_dict['total_gain'] / info_dict['win_count'] / self.market_price if info_dict['win_count'] else 0.
125
+ info_dict['average_loss'] = info_dict['total_loss'] / info_dict['lose_count'] / self.market_price if info_dict['lose_count'] else 0.
126
+ info_dict['gain_loss_ratio'] = -info_dict['average_gain'] / info_dict['average_loss'] if info_dict['average_loss'] else 1.
127
+ info_dict['long_avg_pnl'] = np.average([_['pnl'] for _ in long_trades]) / self.market_price if (long_trades := [_ for _ in self.trade_batch if _['init_side'] == 1]) else np.nan
128
+ info_dict['short_avg_pnl'] = np.average([_['pnl'] for _ in short_trades]) / self.market_price if (short_trades := [_ for _ in self.trade_batch if _['init_side'] == -1]) else np.nan
129
+ info_dict['ttl_pnl.no_leverage'] = np.sum([trade_batch['pnl'] for trade_batch in self.trade_batch])
130
+ info_dict['net_pnl.optimistic'] = info_dict['ttl_pnl.no_leverage'] - (0.00034 + 0.000023) / 2 * info_dict['turnover']
131
+
132
+ return info_dict
133
+
134
+ @property
135
+ def info(self):
136
+ trade_info = []
137
+ trade_index = []
138
+ for batch_id, trade_batch in enumerate(self.trade_batch):
139
+ for trade_id, trade_dict in enumerate(trade_batch['trades']):
140
+ trade_info.append(
141
+ dict(
142
+ timestamp=trade_dict['timestamp'],
143
+ side=trade_dict['side'],
144
+ volume=trade_dict['volume'],
145
+ price=trade_dict['price'],
146
+ exposure=trade_dict['exposure'],
147
+ pnl=trade_dict['pnl']
148
+ )
149
+ )
150
+ trade_index.append((f'batch.{batch_id}', f'trade.{trade_id}'))
151
+
152
+ df = pd.DataFrame(trade_info, index=trade_index)
153
+ return df
154
+
155
+ def to_string(self) -> str:
156
+ metric_info = self.summary
157
+
158
+ fmt_dict = {
159
+ 'total_gain': f'{metric_info["total_gain"]:,.3f}',
160
+ 'total_loss': f'{metric_info["total_loss"]:,.3f}',
161
+ 'trade_count': f'{metric_info["trade_count"]:,}',
162
+ 'win_count': f'{metric_info["win_count"]:,}',
163
+ 'lose_count': f'{metric_info["lose_count"]:,}',
164
+ 'turnover': f'{metric_info["turnover"]:,.3f}',
165
+ 'win_rate': f'{metric_info["win_rate"]:.2%}',
166
+ 'average_gain': f'{metric_info["average_gain"]:,.4%}',
167
+ 'average_loss': f'{metric_info["average_loss"]:,.4%}',
168
+ 'long_avg_pnl': f'{metric_info["long_avg_pnl"]:,.4%}',
169
+ 'short_avg_pnl': f'{metric_info["short_avg_pnl"]:,.4%}',
170
+ 'gain_loss_ratio': f'{metric_info["gain_loss_ratio"]:,.3%}'
171
+ }
172
+
173
+ info_str = (f'Trade Metrics Report:'
174
+ f'\n'
175
+ f'{pd.Series(fmt_dict).to_string()}'
176
+ f'\n'
177
+ f'{self.info.to_string()}')
178
+
179
+ return info_str