algorembrant commited on
Commit
5cbffcd
·
verified ·
1 Parent(s): da20eca

Upload 16 files

Browse files
.gitignore ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules/
2
+
3
+ # Python
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ *.so
8
+ .Python
9
+ venv/
10
+ env/
11
+ ENV/
12
+ *.egg-info/
13
+ .eggs/
14
+
15
+ # Node
16
+ node_modules/
17
+ npm-debug.log*
18
+ yarn-debug.log*
19
+ yarn-error.log*
20
+ .pnp
21
+ .pnp.js
22
+
23
+ # React Build
24
+ build/
25
+ .DS_Store
26
+ .env.local
27
+ .env.development.local
28
+ .env.test.local
29
+ .env.production.local
30
+
31
+ # IDEs
32
+ .vscode/
33
+ .idea/
34
+ *.swp
35
+ *.swo
36
+ *~
37
+
38
+ # MT5
39
+ *.ex5
40
+ *.log
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
STRUCTURE.md ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Project Structure
2
+
3
+ ```text
4
+ backtest-terminal/
5
+ ├── backend/
6
+ │ ├── backtest_bridge.py
7
+ │ ├── mt5_server.py
8
+ │ └── requirements.txt
9
+ ├── trading-terminal-ui/
10
+ │ ├── public/
11
+ │ │ └── index.html
12
+ │ ├── src/
13
+ │ │ ├── App.js
14
+ │ │ ├── App.jsx
15
+ │ │ ├── index.css
16
+ │ │ └── index.js
17
+ │ ├── .gitignore
18
+ │ ├── package-lock.json
19
+ │ └── package.json
20
+ ├── .gitignore
21
+ ├── backtest_report.json
22
+ ├── install.bat
23
+ ├── LICENSE
24
+ └── TECHSTACK.md
25
+ ```
TECHSTACK.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Techstack
2
+
3
+ Audit of **backtest-terminal** project files (excluding environment and cache):
4
+
5
+ | File Type | Count | Size (KB) |
6
+ | :--- | :--- | :--- |
7
+ | (no extension) | 3 | 12.0 |
8
+ | JSON (.json) | 3 | 671.3 |
9
+ | JavaScript (.js) | 2 | 12.2 |
10
+ | Python (.py) | 2 | 15.5 |
11
+ | Batch File (.bat) | 1 | 0 |
12
+ | CSS (.css) | 1 | 0.5 |
13
+ | HTML (.html) | 1 | 0.5 |
14
+ | JSX (React) (.jsx) | 1 | 12.2 |
15
+ | Plain Text (.txt) | 1 | 0.1 |
16
+ | **Total** | **15** | **724.4** |
backend/backtest_bridge.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import MetaTrader5 as mt5
2
+ import json
3
+ import os
4
+ from datetime import datetime
5
+ import pandas as pd
6
+
7
+ class BacktestBridge:
8
+ """Bridge to run MQL5 Expert Advisors and extract backtest results"""
9
+
10
+ def __init__(self):
11
+ self.mt5_path = None
12
+
13
+ def init_mt5(self):
14
+ """Initialize MT5 connection"""
15
+ if not mt5.initialize():
16
+ print(f"MT5 initialization failed: {mt5.last_error()}")
17
+ return False
18
+
19
+ # Get MT5 terminal path
20
+ terminal_info = mt5.terminal_info()
21
+ self.mt5_path = terminal_info.path
22
+ print(f"MT5 Path: {self.mt5_path}")
23
+ return True
24
+
25
+ def compile_ea(self, mq5_file_path):
26
+ """
27
+ Compile MQL5 Expert Advisor
28
+ Note: This requires MetaEditor CLI or manual compilation
29
+ """
30
+ # Check if file exists
31
+ if not os.path.exists(mq5_file_path):
32
+ return {'success': False, 'error': 'MQ5 file not found'}
33
+
34
+ # For now, assume EA is already compiled
35
+ # You need to manually compile in MetaEditor or use MetaEditor CLI
36
+ ex5_path = mq5_file_path.replace('.mq5', '.ex5')
37
+
38
+ if not os.path.exists(ex5_path):
39
+ return {
40
+ 'success': False,
41
+ 'error': 'EX5 file not found. Please compile in MetaEditor first.'
42
+ }
43
+
44
+ return {'success': True, 'ex5_path': ex5_path}
45
+
46
+ def get_history_deals(self, from_date, to_date):
47
+ """Get historical deals from MT5"""
48
+ deals = mt5.history_deals_get(from_date, to_date)
49
+
50
+ if deals is None:
51
+ return []
52
+
53
+ deals_list = []
54
+ for deal in deals:
55
+ deals_list.append({
56
+ 'ticket': deal.ticket,
57
+ 'order': deal.order,
58
+ 'time': deal.time,
59
+ 'type': 'BUY' if deal.type == 0 else 'SELL',
60
+ 'entry': 'IN' if deal.entry == 0 else 'OUT',
61
+ 'symbol': deal.symbol,
62
+ 'volume': deal.volume,
63
+ 'price': deal.price,
64
+ 'profit': deal.profit,
65
+ 'commission': deal.commission,
66
+ 'swap': deal.swap,
67
+ 'comment': deal.comment
68
+ })
69
+
70
+ return deals_list
71
+
72
+ def get_history_orders(self, from_date, to_date):
73
+ """Get historical orders from MT5"""
74
+ orders = mt5.history_orders_get(from_date, to_date)
75
+
76
+ if orders is None:
77
+ return []
78
+
79
+ orders_list = []
80
+ for order in orders:
81
+ orders_list.append({
82
+ 'ticket': order.ticket,
83
+ 'time_setup': order.time_setup,
84
+ 'time_done': order.time_done,
85
+ 'type': order.type,
86
+ 'state': order.state,
87
+ 'symbol': order.symbol,
88
+ 'volume_initial': order.volume_initial,
89
+ 'volume_current': order.volume_current,
90
+ 'price_open': order.price_open,
91
+ 'price_current': order.price_current,
92
+ 'sl': order.sl,
93
+ 'tp': order.tp,
94
+ 'comment': order.comment
95
+ })
96
+
97
+ return orders_list
98
+
99
+ def analyze_backtest_results(self, from_date, to_date):
100
+ """Analyze backtest results and calculate statistics"""
101
+ deals = self.get_history_deals(from_date, to_date)
102
+
103
+ if not deals:
104
+ return {'error': 'No deals found in the specified period'}
105
+
106
+ # Convert to DataFrame for easier analysis
107
+ df = pd.DataFrame(deals)
108
+
109
+ # Calculate statistics
110
+ total_trades = len(df[df['entry'] == 'OUT'])
111
+ winning_trades = len(df[(df['entry'] == 'OUT') & (df['profit'] > 0)])
112
+ losing_trades = len(df[(df['entry'] == 'OUT') & (df['profit'] < 0)])
113
+
114
+ total_profit = df[df['entry'] == 'OUT']['profit'].sum()
115
+ total_commission = df[df['entry'] == 'OUT']['commission'].sum()
116
+ total_swap = df[df['entry'] == 'OUT']['swap'].sum()
117
+
118
+ net_profit = total_profit + total_commission + total_swap
119
+
120
+ win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0
121
+
122
+ # Calculate max drawdown
123
+ df_out = df[df['entry'] == 'OUT'].copy()
124
+ df_out['cumulative_profit'] = df_out['profit'].cumsum()
125
+ df_out['peak'] = df_out['cumulative_profit'].cummax()
126
+ df_out['drawdown'] = df_out['peak'] - df_out['cumulative_profit']
127
+ max_drawdown = df_out['drawdown'].max()
128
+
129
+ # Profit factor
130
+ gross_profit = df[(df['entry'] == 'OUT') & (df['profit'] > 0)]['profit'].sum()
131
+ gross_loss = abs(df[(df['entry'] == 'OUT') & (df['profit'] < 0)]['profit'].sum())
132
+ profit_factor = (gross_profit / gross_loss) if gross_loss > 0 else 0
133
+
134
+ return {
135
+ 'total_trades': total_trades,
136
+ 'winning_trades': winning_trades,
137
+ 'losing_trades': losing_trades,
138
+ 'win_rate': round(win_rate, 2),
139
+ 'total_profit': round(total_profit, 2),
140
+ 'total_commission': round(total_commission, 2),
141
+ 'total_swap': round(total_swap, 2),
142
+ 'net_profit': round(net_profit, 2),
143
+ 'max_drawdown': round(max_drawdown, 2),
144
+ 'profit_factor': round(profit_factor, 2),
145
+ 'gross_profit': round(gross_profit, 2),
146
+ 'gross_loss': round(gross_loss, 2),
147
+ 'deals': deals
148
+ }
149
+
150
+ def export_backtest_report(self, from_date, to_date, output_file='backtest_report.json'):
151
+ """Export backtest results to JSON file"""
152
+ results = self.analyze_backtest_results(from_date, to_date)
153
+
154
+ with open(output_file, 'w') as f:
155
+ json.dump(results, f, indent=2, default=str)
156
+
157
+ print(f"Backtest report exported to {output_file}")
158
+ return results
159
+
160
+ # Example usage
161
+ if __name__ == "__main__":
162
+ bridge = BacktestBridge()
163
+
164
+ if bridge.init_mt5():
165
+ # Define backtest period
166
+ from_date = datetime(2024, 1, 1)
167
+ to_date = datetime.now()
168
+
169
+ # Analyze results
170
+ results = bridge.analyze_backtest_results(from_date, to_date)
171
+
172
+ print("\n=== BACKTEST RESULTS ===")
173
+ print(f"Total Trades: {results.get('total_trades', 0)}")
174
+ print(f"Win Rate: {results.get('win_rate', 0)}%")
175
+ print(f"Net Profit: ${results.get('net_profit', 0)}")
176
+ print(f"Max Drawdown: ${results.get('max_drawdown', 0)}")
177
+ print(f"Profit Factor: {results.get('profit_factor', 0)}")
178
+
179
+ # Export to JSON
180
+ bridge.export_backtest_report(from_date, to_date)
181
+
182
+ mt5.shutdown()
backend/mt5_server.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import MetaTrader5 as mt5
4
+ from datetime import datetime
5
+ import websockets
6
+ import numpy as np
7
+
8
+ class MT5Server:
9
+ def __init__(self):
10
+ self.clients = set()
11
+ self.running = False
12
+
13
+ async def init_mt5(self):
14
+ """Initialize MT5 connection"""
15
+ if not mt5.initialize():
16
+ print(f"MT5 initialization failed: {mt5.last_error()}")
17
+ return False
18
+ print(f"MT5 initialized. Version: {mt5.version()}")
19
+ return True
20
+
21
+ def get_rates(self, symbol, timeframe, count=500):
22
+ """Fetch historical rates from MT5"""
23
+ rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
24
+ if rates is None:
25
+ return None
26
+
27
+ return [{
28
+ 'time': int(rate[0]) * 1000, # Convert to milliseconds
29
+ 'open': float(rate[1]),
30
+ 'high': float(rate[2]),
31
+ 'low': float(rate[3]),
32
+ 'close': float(rate[4]),
33
+ 'volume': int(rate[5])
34
+ } for rate in rates]
35
+
36
+ def get_tick(self, symbol):
37
+ """Get latest tick data"""
38
+ tick = mt5.symbol_info_tick(symbol)
39
+ if tick is None:
40
+ return None
41
+
42
+ return {
43
+ 'time': tick.time * 1000,
44
+ 'bid': tick.bid,
45
+ 'ask': tick.ask,
46
+ 'last': tick.last,
47
+ 'volume': tick.volume
48
+ }
49
+
50
+ def get_positions(self):
51
+ """Get open positions"""
52
+ positions = mt5.positions_get()
53
+ if positions is None:
54
+ return []
55
+
56
+ return [{
57
+ 'ticket': pos.ticket,
58
+ 'symbol': pos.symbol,
59
+ 'type': 'BUY' if pos.type == 0 else 'SELL',
60
+ 'volume': pos.volume,
61
+ 'price_open': pos.price_open,
62
+ 'price_current': pos.price_current,
63
+ 'profit': pos.profit,
64
+ 'sl': pos.sl,
65
+ 'tp': pos.tp
66
+ } for pos in positions]
67
+
68
+ def place_order(self, symbol, order_type, volume, price=None, sl=None, tp=None):
69
+ """Place market or pending order"""
70
+ symbol_info = mt5.symbol_info(symbol)
71
+ if symbol_info is None:
72
+ return {'success': False, 'error': 'Symbol not found'}
73
+
74
+ if not symbol_info.visible:
75
+ if not mt5.symbol_select(symbol, True):
76
+ return {'success': False, 'error': 'Failed to select symbol'}
77
+
78
+ point = symbol_info.point
79
+
80
+ request = {
81
+ "action": mt5.TRADE_ACTION_DEAL,
82
+ "symbol": symbol,
83
+ "volume": volume,
84
+ "type": mt5.ORDER_TYPE_BUY if order_type == 'BUY' else mt5.ORDER_TYPE_SELL,
85
+ "deviation": 20,
86
+ "magic": 234000,
87
+ "comment": "Trading Terminal",
88
+ "type_time": mt5.ORDER_TIME_GTC,
89
+ "type_filling": mt5.ORDER_FILLING_IOC,
90
+ }
91
+
92
+ if price:
93
+ request["price"] = price
94
+
95
+ if sl:
96
+ request["sl"] = sl
97
+
98
+ if tp:
99
+ request["tp"] = tp
100
+
101
+ result = mt5.order_send(request)
102
+
103
+ if result.retcode != mt5.TRADE_RETCODE_DONE:
104
+ return {'success': False, 'error': f'Order failed: {result.comment}'}
105
+
106
+ return {
107
+ 'success': True,
108
+ 'ticket': result.order,
109
+ 'volume': result.volume,
110
+ 'price': result.price
111
+ }
112
+
113
+ def close_position(self, ticket):
114
+ """Close position by ticket"""
115
+ positions = mt5.positions_get(ticket=ticket)
116
+ if not positions:
117
+ return {'success': False, 'error': 'Position not found'}
118
+
119
+ position = positions[0]
120
+
121
+ request = {
122
+ "action": mt5.TRADE_ACTION_DEAL,
123
+ "symbol": position.symbol,
124
+ "volume": position.volume,
125
+ "type": mt5.ORDER_TYPE_SELL if position.type == 0 else mt5.ORDER_TYPE_BUY,
126
+ "position": ticket,
127
+ "deviation": 20,
128
+ "magic": 234000,
129
+ "comment": "Close position",
130
+ "type_time": mt5.ORDER_TIME_GTC,
131
+ "type_filling": mt5.ORDER_FILLING_IOC,
132
+ }
133
+
134
+ result = mt5.order_send(request)
135
+
136
+ if result.retcode != mt5.TRADE_RETCODE_DONE:
137
+ return {'success': False, 'error': f'Close failed: {result.comment}'}
138
+
139
+ return {'success': True, 'ticket': ticket}
140
+
141
+ async def handle_client(self, websocket, path):
142
+ """Handle WebSocket client connections"""
143
+ self.clients.add(websocket)
144
+ print(f"Client connected. Total clients: {len(self.clients)}")
145
+
146
+ try:
147
+ async for message in websocket:
148
+ data = json.loads(message)
149
+ action = data.get('action')
150
+
151
+ if action == 'get_rates':
152
+ symbol = data.get('symbol', 'XAUUSDc')
153
+ timeframe_map = {
154
+ 'M1': mt5.TIMEFRAME_M1,
155
+ 'M5': mt5.TIMEFRAME_M5,
156
+ 'M15': mt5.TIMEFRAME_M15,
157
+ 'M30': mt5.TIMEFRAME_M30,
158
+ 'H1': mt5.TIMEFRAME_H1,
159
+ 'H4': mt5.TIMEFRAME_H4,
160
+ 'D1': mt5.TIMEFRAME_D1
161
+ }
162
+ timeframe = timeframe_map.get(data.get('timeframe', 'M15'), mt5.TIMEFRAME_M15)
163
+ count = data.get('count', 500)
164
+
165
+ rates = self.get_rates(symbol, timeframe, count)
166
+ await websocket.send(json.dumps({
167
+ 'type': 'rates',
168
+ 'data': rates
169
+ }))
170
+
171
+ elif action == 'get_tick':
172
+ symbol = data.get('symbol', 'XAUUSDc')
173
+ tick = self.get_tick(symbol)
174
+ await websocket.send(json.dumps({
175
+ 'type': 'tick',
176
+ 'data': tick
177
+ }))
178
+
179
+ elif action == 'get_positions':
180
+ positions = self.get_positions()
181
+ await websocket.send(json.dumps({
182
+ 'type': 'positions',
183
+ 'data': positions
184
+ }))
185
+
186
+ elif action == 'place_order':
187
+ result = self.place_order(
188
+ data.get('symbol'),
189
+ data.get('order_type'),
190
+ data.get('volume'),
191
+ data.get('price'),
192
+ data.get('sl'),
193
+ data.get('tp')
194
+ )
195
+ await websocket.send(json.dumps({
196
+ 'type': 'order_result',
197
+ 'data': result
198
+ }))
199
+
200
+ elif action == 'close_position':
201
+ result = self.close_position(data.get('ticket'))
202
+ await websocket.send(json.dumps({
203
+ 'type': 'close_result',
204
+ 'data': result
205
+ }))
206
+
207
+ except websockets.exceptions.ConnectionClosed:
208
+ pass
209
+ finally:
210
+ self.clients.remove(websocket)
211
+ print(f"Client disconnected. Total clients: {len(self.clients)}")
212
+
213
+ async def broadcast_ticks(self):
214
+ """Broadcast real-time ticks to all connected clients"""
215
+ symbols = ['XAUUSDc'] # Add more symbols as needed
216
+
217
+ while self.running:
218
+ for symbol in symbols:
219
+ tick = self.get_tick(symbol)
220
+ if tick and self.clients:
221
+ message = json.dumps({
222
+ 'type': 'tick_update',
223
+ 'symbol': symbol,
224
+ 'data': tick
225
+ })
226
+ await asyncio.gather(
227
+ *[client.send(message) for client in self.clients],
228
+ return_exceptions=True
229
+ )
230
+ await asyncio.sleep(0.1) # Update every 100ms
231
+
232
+ async def start(self):
233
+ """Start the WebSocket server"""
234
+ if not await self.init_mt5():
235
+ return
236
+
237
+ self.running = True
238
+
239
+ # Start WebSocket server
240
+ server = await websockets.serve(self.handle_client, "localhost", 8765)
241
+ print("WebSocket server started on ws://localhost:8765")
242
+
243
+ # Start broadcasting ticks
244
+ await self.broadcast_ticks()
245
+
246
+ if __name__ == "__main__":
247
+ server = MT5Server()
248
+ asyncio.run(server.start())
backend/requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ MetaTrader5==5.0.45
2
+ websockets==12.0
3
+ pandas==2.1.4
4
+ numpy==1.26.2
backtest_report.json ADDED
@@ -0,0 +1,394 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "total_trades": 13,
3
+ "winning_trades": 5,
4
+ "losing_trades": 8,
5
+ "win_rate": 38.46,
6
+ "total_profit": 113.7,
7
+ "total_commission": 0.0,
8
+ "total_swap": 0.0,
9
+ "net_profit": 113.7,
10
+ "max_drawdown": 15.6,
11
+ "profit_factor": 4.96,
12
+ "gross_profit": 142.4,
13
+ "gross_loss": 28.7,
14
+ "deals": [
15
+ {
16
+ "ticket": 578723459,
17
+ "order": 0,
18
+ "time": 1766968573,
19
+ "type": "SELL",
20
+ "entry": "IN",
21
+ "symbol": "",
22
+ "volume": 0.0,
23
+ "price": 0.0,
24
+ "profit": 100.0,
25
+ "commission": 0.0,
26
+ "swap": 0.0,
27
+ "comment": "D-ALLINT-USC-INT-352783368197"
28
+ },
29
+ {
30
+ "ticket": 580337778,
31
+ "order": 620573573,
32
+ "time": 1766979732,
33
+ "type": "BUY",
34
+ "entry": "IN",
35
+ "symbol": "XAUUSDc",
36
+ "volume": 0.01,
37
+ "price": 4514.934,
38
+ "profit": 0.0,
39
+ "commission": 0.0,
40
+ "swap": 0.0,
41
+ "comment": "VP_Algo"
42
+ },
43
+ {
44
+ "ticket": 580342883,
45
+ "order": 620578905,
46
+ "time": 1766979808,
47
+ "type": "SELL",
48
+ "entry": "OUT",
49
+ "symbol": "XAUUSDc",
50
+ "volume": 0.01,
51
+ "price": 4515.392,
52
+ "profit": 0.5,
53
+ "commission": 0.0,
54
+ "swap": 0.0,
55
+ "comment": ""
56
+ },
57
+ {
58
+ "ticket": 588526608,
59
+ "order": 629060201,
60
+ "time": 1767053978,
61
+ "type": "BUY",
62
+ "entry": "IN",
63
+ "symbol": "XAUUSDc",
64
+ "volume": 0.01,
65
+ "price": 4347.824,
66
+ "profit": 0.0,
67
+ "commission": 0.0,
68
+ "swap": 0.0,
69
+ "comment": ""
70
+ },
71
+ {
72
+ "ticket": 588530081,
73
+ "order": 629064107,
74
+ "time": 1767054032,
75
+ "type": "SELL",
76
+ "entry": "OUT",
77
+ "symbol": "XAUUSDc",
78
+ "volume": 0.01,
79
+ "price": 4346.818,
80
+ "profit": -1.0,
81
+ "commission": 0.0,
82
+ "swap": 0.0,
83
+ "comment": "[sl 4346.818]"
84
+ },
85
+ {
86
+ "ticket": 588535145,
87
+ "order": 629069232,
88
+ "time": 1767054071,
89
+ "type": "BUY",
90
+ "entry": "IN",
91
+ "symbol": "XAUUSDc",
92
+ "volume": 0.01,
93
+ "price": 4346.317,
94
+ "profit": 0.0,
95
+ "commission": 0.0,
96
+ "swap": 0.0,
97
+ "comment": ""
98
+ },
99
+ {
100
+ "ticket": 588595427,
101
+ "order": 0,
102
+ "time": 1767055151,
103
+ "type": "SELL",
104
+ "entry": "OUT",
105
+ "symbol": "XAUUSDc",
106
+ "volume": 0.01,
107
+ "price": 4343.795,
108
+ "profit": -2.5,
109
+ "commission": 0.0,
110
+ "swap": 0.0,
111
+ "comment": "[sl 4343.79500]"
112
+ },
113
+ {
114
+ "ticket": 601258658,
115
+ "order": 642405265,
116
+ "time": 1767315535,
117
+ "type": "BUY",
118
+ "entry": "IN",
119
+ "symbol": "XAUUSDc",
120
+ "volume": 0.01,
121
+ "price": 4354.362,
122
+ "profit": 0.0,
123
+ "commission": 0.0,
124
+ "swap": 0.0,
125
+ "comment": ""
126
+ },
127
+ {
128
+ "ticket": 601276711,
129
+ "order": 0,
130
+ "time": 1767315824,
131
+ "type": "SELL",
132
+ "entry": "OUT",
133
+ "symbol": "XAUUSDc",
134
+ "volume": 0.01,
135
+ "price": 4351.078,
136
+ "profit": -3.3,
137
+ "commission": 0.0,
138
+ "swap": 0.0,
139
+ "comment": "[sl 4351.07800]"
140
+ },
141
+ {
142
+ "ticket": 607246461,
143
+ "order": 648712225,
144
+ "time": 1767571920,
145
+ "type": "BUY",
146
+ "entry": "IN",
147
+ "symbol": "XAUUSDc",
148
+ "volume": 0.01,
149
+ "price": 4378.567,
150
+ "profit": 0.0,
151
+ "commission": 0.0,
152
+ "swap": 0.0,
153
+ "comment": "Range Breakout Buy"
154
+ },
155
+ {
156
+ "ticket": 607281279,
157
+ "order": 648748642,
158
+ "time": 1767572371,
159
+ "type": "BUY",
160
+ "entry": "IN",
161
+ "symbol": "XAUUSDc",
162
+ "volume": 0.01,
163
+ "price": 4381.582,
164
+ "profit": 0.0,
165
+ "commission": 0.0,
166
+ "swap": 0.0,
167
+ "comment": ""
168
+ },
169
+ {
170
+ "ticket": 607376045,
171
+ "order": 0,
172
+ "time": 1767573440,
173
+ "type": "SELL",
174
+ "entry": "OUT",
175
+ "symbol": "XAUUSDc",
176
+ "volume": 0.01,
177
+ "price": 4394.923,
178
+ "profit": 13.3,
179
+ "commission": 0.0,
180
+ "swap": 0.0,
181
+ "comment": "[tp 4394.92300]"
182
+ },
183
+ {
184
+ "ticket": 610689356,
185
+ "order": 652309428,
186
+ "time": 1767610937,
187
+ "type": "SELL",
188
+ "entry": "OUT",
189
+ "symbol": "XAUUSDc",
190
+ "volume": 0.01,
191
+ "price": 4433.214,
192
+ "profit": 54.6,
193
+ "commission": 0.0,
194
+ "swap": 0.0,
195
+ "comment": "Range Breakout Buy"
196
+ },
197
+ {
198
+ "ticket": 620907374,
199
+ "order": 662951034,
200
+ "time": 1767744180,
201
+ "type": "SELL",
202
+ "entry": "IN",
203
+ "symbol": "XAUUSDc",
204
+ "volume": 0.01,
205
+ "price": 4491.423,
206
+ "profit": 0.0,
207
+ "commission": 0.0,
208
+ "swap": 0.0,
209
+ "comment": "Range Breakout Sell"
210
+ },
211
+ {
212
+ "ticket": 620933920,
213
+ "order": 0,
214
+ "time": 1767744663,
215
+ "type": "BUY",
216
+ "entry": "OUT",
217
+ "symbol": "XAUUSDc",
218
+ "volume": 0.01,
219
+ "price": 4494.081,
220
+ "profit": -2.7,
221
+ "commission": 0.0,
222
+ "swap": 0.0,
223
+ "comment": "[sl 4494.08100]"
224
+ },
225
+ {
226
+ "ticket": 628261457,
227
+ "order": 670596444,
228
+ "time": 1767830580,
229
+ "type": "BUY",
230
+ "entry": "IN",
231
+ "symbol": "XAUUSDc",
232
+ "volume": 0.01,
233
+ "price": 4465.5019999999995,
234
+ "profit": 0.0,
235
+ "commission": 0.0,
236
+ "swap": 0.0,
237
+ "comment": "Range Breakout Buy"
238
+ },
239
+ {
240
+ "ticket": 628277670,
241
+ "order": 670613032,
242
+ "time": 1767830754,
243
+ "type": "SELL",
244
+ "entry": "OUT",
245
+ "symbol": "XAUUSDc",
246
+ "volume": 0.01,
247
+ "price": 4462.901,
248
+ "profit": -2.6,
249
+ "commission": 0.0,
250
+ "swap": 0.0,
251
+ "comment": "[sl 4462.901]"
252
+ },
253
+ {
254
+ "ticket": 636550803,
255
+ "order": 679232561,
256
+ "time": 1767917700,
257
+ "type": "SELL",
258
+ "entry": "IN",
259
+ "symbol": "XAUUSDc",
260
+ "volume": 0.01,
261
+ "price": 4471.601,
262
+ "profit": 0.0,
263
+ "commission": 0.0,
264
+ "swap": 0.0,
265
+ "comment": "Range Breakout Sell"
266
+ },
267
+ {
268
+ "ticket": 636551187,
269
+ "order": 679232900,
270
+ "time": 1767917701,
271
+ "type": "SELL",
272
+ "entry": "IN",
273
+ "symbol": "XAUUSDc",
274
+ "volume": 0.01,
275
+ "price": 4471.5740000000005,
276
+ "profit": 0.0,
277
+ "commission": 0.0,
278
+ "swap": 0.0,
279
+ "comment": ""
280
+ },
281
+ {
282
+ "ticket": 638748995,
283
+ "order": 0,
284
+ "time": 1767941180,
285
+ "type": "BUY",
286
+ "entry": "OUT",
287
+ "symbol": "XAUUSDc",
288
+ "volume": 0.01,
289
+ "price": 4476.754,
290
+ "profit": -5.2,
291
+ "commission": 0.0,
292
+ "swap": 0.0,
293
+ "comment": "[sl 4476.75400]"
294
+ },
295
+ {
296
+ "ticket": 638749010,
297
+ "order": 0,
298
+ "time": 1767941180,
299
+ "type": "BUY",
300
+ "entry": "OUT",
301
+ "symbol": "XAUUSDc",
302
+ "volume": 0.01,
303
+ "price": 4476.735,
304
+ "profit": -5.1,
305
+ "commission": 0.0,
306
+ "swap": 0.0,
307
+ "comment": "[sl 4476.73500]"
308
+ },
309
+ {
310
+ "ticket": 664419807,
311
+ "order": 699190077,
312
+ "time": 1768336462,
313
+ "type": "BUY",
314
+ "entry": "IN",
315
+ "symbol": "XAUUSDc",
316
+ "volume": 0.01,
317
+ "price": 4571.919,
318
+ "profit": 0.0,
319
+ "commission": 0.0,
320
+ "swap": 0.0,
321
+ "comment": ""
322
+ },
323
+ {
324
+ "ticket": 664845365,
325
+ "order": 709205437,
326
+ "time": 1768348981,
327
+ "type": "BUY",
328
+ "entry": "IN",
329
+ "symbol": "XAUUSDc",
330
+ "volume": 0.01,
331
+ "price": 4600.441,
332
+ "profit": 0.0,
333
+ "commission": 0.0,
334
+ "swap": 0.0,
335
+ "comment": "Range Breakout Buy"
336
+ },
337
+ {
338
+ "ticket": 665166396,
339
+ "order": 0,
340
+ "time": 1768352542,
341
+ "type": "SELL",
342
+ "entry": "OUT",
343
+ "symbol": "XAUUSDc",
344
+ "volume": 0.01,
345
+ "price": 4614.71,
346
+ "profit": 42.8,
347
+ "commission": 0.0,
348
+ "swap": 0.0,
349
+ "comment": "[tp 4614.71000]"
350
+ },
351
+ {
352
+ "ticket": 666704758,
353
+ "order": 0,
354
+ "time": 1768366884,
355
+ "type": "SELL",
356
+ "entry": "OUT",
357
+ "symbol": "XAUUSDc",
358
+ "volume": 0.01,
359
+ "price": 4631.589,
360
+ "profit": 31.2,
361
+ "commission": 0.0,
362
+ "swap": 0.0,
363
+ "comment": "[tp 4631.58900]"
364
+ },
365
+ {
366
+ "ticket": 684828173,
367
+ "order": 730182325,
368
+ "time": 1768522098,
369
+ "type": "SELL",
370
+ "entry": "IN",
371
+ "symbol": "XAUUSDc",
372
+ "volume": 0.01,
373
+ "price": 4604.909,
374
+ "profit": 0.0,
375
+ "commission": 0.0,
376
+ "swap": 0.0,
377
+ "comment": ""
378
+ },
379
+ {
380
+ "ticket": 684898688,
381
+ "order": 0,
382
+ "time": 1768522974,
383
+ "type": "BUY",
384
+ "entry": "OUT",
385
+ "symbol": "XAUUSDc",
386
+ "volume": 0.01,
387
+ "price": 4611.201,
388
+ "profit": -6.3,
389
+ "commission": 0.0,
390
+ "swap": 0.0,
391
+ "comment": "[sl 4611.20100]"
392
+ }
393
+ ]
394
+ }
trading-terminal-ui/.gitignore ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules/
2
+
3
+ # Python
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ *.so
8
+ .Python
9
+ venv/
10
+ env/
11
+ ENV/
12
+ *.egg-info/
13
+ .eggs/
14
+
15
+ # Node
16
+ node_modules/
17
+ npm-debug.log*
18
+ yarn-debug.log*
19
+ yarn-error.log*
20
+ .pnp
21
+ .pnp.js
22
+
23
+ # React Build
24
+ build/
25
+ .DS_Store
26
+ .env.local
27
+ .env.development.local
28
+ .env.test.local
29
+ .env.production.local
30
+
31
+ # IDEs
32
+ .vscode/
33
+ .idea/
34
+ *.swp
35
+ *.swo
36
+ *~
37
+
38
+ # MT5
39
+ *.ex5
40
+ *.log
trading-terminal-ui/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
trading-terminal-ui/package.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "trading-terminal-ui",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "lucide-react": "^0.263.1",
7
+ "react": "^18.2.0",
8
+ "react-dom": "^18.2.0",
9
+ "react-scripts": "5.0.1",
10
+ "recharts": "^2.15.4"
11
+ },
12
+ "scripts": {
13
+ "start": "react-scripts start",
14
+ "build": "react-scripts build",
15
+ "test": "react-scripts test",
16
+ "eject": "react-scripts eject"
17
+ },
18
+ "eslintConfig": {
19
+ "extends": [
20
+ "react-app"
21
+ ]
22
+ },
23
+ "browserslist": {
24
+ "production": [
25
+ ">0.2%",
26
+ "not dead",
27
+ "not op_mini all"
28
+ ],
29
+ "development": [
30
+ "last 1 chrome version",
31
+ "last 1 firefox version",
32
+ "last 1 safari version"
33
+ ]
34
+ }
35
+ }
trading-terminal-ui/public/index.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="theme-color" content="#000000" />
7
+ <meta name="description" content="Professional MT5 Trading Terminal" />
8
+ <title>Trading Terminal</title>
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ </head>
11
+ <body>
12
+ <noscript>You need to enable JavaScript to run this app.</noscript>
13
+ <div id="root"></div>
14
+ </body>
15
+ </html>
trading-terminal-ui/src/App.js ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
3
+ import { TrendingUp, TrendingDown, X } from 'lucide-react';
4
+
5
+ const TradingTerminal = () => {
6
+ const [ws, setWs] = useState(null);
7
+ const [connected, setConnected] = useState(false);
8
+ const [symbol, setSymbol] = useState('XAUUSDc');
9
+ const [timeframe, setTimeframe] = useState('M15');
10
+ const [chartData, setChartData] = useState([]);
11
+ const [tick, setTick] = useState(null);
12
+ const [positions, setPositions] = useState([]);
13
+ const [volume, setVolume] = useState(0.01);
14
+ const [sl, setSl] = useState('');
15
+ const [tp, setTp] = useState('');
16
+
17
+ useEffect(() => {
18
+ const socket = new WebSocket('ws://localhost:8765');
19
+
20
+ socket.onopen = () => {
21
+ setConnected(true);
22
+ console.log('Connected to MT5 server');
23
+
24
+ socket.send(JSON.stringify({
25
+ action: 'get_rates',
26
+ symbol: symbol,
27
+ timeframe: timeframe,
28
+ count: 500
29
+ }));
30
+
31
+ socket.send(JSON.stringify({ action: 'get_positions' }));
32
+ };
33
+
34
+ socket.onmessage = (event) => {
35
+ const message = JSON.parse(event.data);
36
+
37
+ if (message.type === 'rates') {
38
+ setChartData(message.data || []);
39
+ } else if (message.type === 'tick_update' || message.type === 'tick') {
40
+ setTick(message.data);
41
+ } else if (message.type === 'positions') {
42
+ setPositions(message.data || []);
43
+ } else if (message.type === 'order_result') {
44
+ if (message.data.success) {
45
+ alert('Order placed successfully!');
46
+ socket.send(JSON.stringify({ action: 'get_positions' }));
47
+ } else {
48
+ alert('Order failed: ' + message.data.error);
49
+ }
50
+ } else if (message.type === 'close_result') {
51
+ if (message.data.success) {
52
+ alert('Position closed!');
53
+ socket.send(JSON.stringify({ action: 'get_positions' }));
54
+ } else {
55
+ alert('Close failed: ' + message.data.error);
56
+ }
57
+ }
58
+ };
59
+
60
+ socket.onerror = (error) => {
61
+ console.error('WebSocket error:', error);
62
+ setConnected(false);
63
+ };
64
+
65
+ socket.onclose = () => {
66
+ setConnected(false);
67
+ console.log('Disconnected from MT5 server');
68
+ };
69
+
70
+ setWs(socket);
71
+
72
+ return () => {
73
+ socket.close();
74
+ };
75
+ }, []);
76
+
77
+ const placeOrder = (type) => {
78
+ if (!ws || !connected) return;
79
+
80
+ ws.send(JSON.stringify({
81
+ action: 'place_order',
82
+ symbol: symbol,
83
+ order_type: type,
84
+ volume: parseFloat(volume),
85
+ sl: sl ? parseFloat(sl) : null,
86
+ tp: tp ? parseFloat(tp) : null
87
+ }));
88
+ };
89
+
90
+ const closePosition = (ticket) => {
91
+ if (!ws || !connected) return;
92
+
93
+ ws.send(JSON.stringify({
94
+ action: 'close_position',
95
+ ticket: ticket
96
+ }));
97
+ };
98
+
99
+ const changeTimeframe = (tf) => {
100
+ setTimeframe(tf);
101
+ if (ws && connected) {
102
+ ws.send(JSON.stringify({
103
+ action: 'get_rates',
104
+ symbol: symbol,
105
+ timeframe: tf,
106
+ count: 500
107
+ }));
108
+ }
109
+ };
110
+
111
+ const formatPrice = (price) => {
112
+ return price ? price.toFixed(2) : '-';
113
+ };
114
+
115
+ const totalPnL = positions.reduce((sum, pos) => sum + pos.profit, 0);
116
+
117
+ return (
118
+ <div className="w-full h-screen bg-neutral-950 text-neutral-200 flex flex-col">
119
+ <div className="h-12 bg-neutral-900 border-b border-yellow-600/20 flex items-center justify-between px-4">
120
+ <div className="flex items-center gap-4">
121
+ <div className="flex items-center gap-2">
122
+ <div className={`w-2 h-2 rounded-full ${connected ? 'bg-yellow-500' : 'bg-neutral-600'}`}></div>
123
+ <span className="text-sm font-medium text-yellow-500">{symbol}</span>
124
+ </div>
125
+
126
+ {tick && (
127
+ <div className="flex items-center gap-4 text-sm">
128
+ <span className="text-neutral-400">BID: <span className="text-yellow-500 font-mono">{formatPrice(tick.bid)}</span></span>
129
+ <span className="text-neutral-400">ASK: <span className="text-yellow-500 font-mono">{formatPrice(tick.ask)}</span></span>
130
+ </div>
131
+ )}
132
+ </div>
133
+
134
+ <div className="flex items-center gap-2">
135
+ <span className={`text-sm font-mono ${totalPnL >= 0 ? 'text-green-500' : 'text-red-500'}`}>
136
+ P&L: ${totalPnL.toFixed(2)}
137
+ </span>
138
+ </div>
139
+ </div>
140
+
141
+ <div className="flex-1 flex overflow-hidden">
142
+ <div className="flex-1 flex flex-col">
143
+ <div className="h-10 bg-neutral-900 border-b border-yellow-600/20 flex items-center px-4 gap-2">
144
+ {['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1'].map(tf => (
145
+ <button
146
+ key={tf}
147
+ onClick={() => changeTimeframe(tf)}
148
+ className={`px-3 py-1 text-xs font-medium transition-colors ${
149
+ timeframe === tf
150
+ ? 'bg-yellow-600 text-neutral-950'
151
+ : 'text-neutral-400 hover:text-yellow-500'
152
+ }`}
153
+ >
154
+ {tf}
155
+ </button>
156
+ ))}
157
+ </div>
158
+
159
+ <div className="flex-1 bg-neutral-950 p-4">
160
+ {chartData.length > 0 ? (
161
+ <ResponsiveContainer width="100%" height="100%">
162
+ <LineChart data={chartData}>
163
+ <XAxis
164
+ dataKey="time"
165
+ tickFormatter={(time) => new Date(time).toLocaleTimeString()}
166
+ stroke="#525252"
167
+ style={{ fontSize: 10 }}
168
+ />
169
+ <YAxis
170
+ domain={['auto', 'auto']}
171
+ stroke="#525252"
172
+ style={{ fontSize: 10 }}
173
+ />
174
+ <Tooltip
175
+ contentStyle={{
176
+ backgroundColor: '#171717',
177
+ border: '1px solid #ca8a04',
178
+ borderRadius: 0,
179
+ fontSize: 12
180
+ }}
181
+ labelFormatter={(time) => new Date(time).toLocaleString()}
182
+ />
183
+ <Line
184
+ type="monotone"
185
+ dataKey="close"
186
+ stroke="#ca8a04"
187
+ dot={false}
188
+ strokeWidth={1}
189
+ />
190
+ </LineChart>
191
+ </ResponsiveContainer>
192
+ ) : (
193
+ <div className="flex items-center justify-center h-full text-neutral-600">
194
+ Loading chart data...
195
+ </div>
196
+ )}
197
+ </div>
198
+ </div>
199
+
200
+ <div className="w-80 bg-neutral-900 border-l border-yellow-600/20 flex flex-col">
201
+ <div className="p-4 border-b border-yellow-600/20">
202
+ <div className="text-xs font-medium text-yellow-500 mb-3">NEW ORDER</div>
203
+
204
+ <div className="space-y-2">
205
+ <div>
206
+ <label className="text-xs text-neutral-400">Volume</label>
207
+ <input
208
+ type="number"
209
+ step="0.01"
210
+ value={volume}
211
+ onChange={(e) => setVolume(e.target.value)}
212
+ className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600"
213
+ />
214
+ </div>
215
+
216
+ <div>
217
+ <label className="text-xs text-neutral-400">Stop Loss</label>
218
+ <input
219
+ type="number"
220
+ step="0.01"
221
+ value={sl}
222
+ onChange={(e) => setSl(e.target.value)}
223
+ placeholder="Optional"
224
+ className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600 placeholder:text-neutral-700"
225
+ />
226
+ </div>
227
+
228
+ <div>
229
+ <label className="text-xs text-neutral-400">Take Profit</label>
230
+ <input
231
+ type="number"
232
+ step="0.01"
233
+ value={tp}
234
+ onChange={(e) => setTp(e.target.value)}
235
+ placeholder="Optional"
236
+ className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600 placeholder:text-neutral-700"
237
+ />
238
+ </div>
239
+
240
+ <div className="flex gap-2 pt-2">
241
+ <button
242
+ onClick={() => placeOrder('BUY')}
243
+ className="flex-1 bg-green-700 hover:bg-green-600 text-white py-2 text-sm font-medium transition-colors flex items-center justify-center gap-1"
244
+ disabled={!connected}
245
+ >
246
+ <TrendingUp size={14} />
247
+ BUY
248
+ </button>
249
+ <button
250
+ onClick={() => placeOrder('SELL')}
251
+ className="flex-1 bg-red-700 hover:bg-red-600 text-white py-2 text-sm font-medium transition-colors flex items-center justify-center gap-1"
252
+ disabled={!connected}
253
+ >
254
+ <TrendingDown size={14} />
255
+ SELL
256
+ </button>
257
+ </div>
258
+ </div>
259
+ </div>
260
+
261
+ <div className="flex-1 overflow-auto">
262
+ <div className="p-4">
263
+ <div className="text-xs font-medium text-yellow-500 mb-3">POSITIONS ({positions.length})</div>
264
+
265
+ {positions.length === 0 ? (
266
+ <div className="text-xs text-neutral-600 text-center py-8">No open positions</div>
267
+ ) : (
268
+ <div className="space-y-2">
269
+ {positions.map((pos) => (
270
+ <div key={pos.ticket} className="bg-neutral-950 border border-yellow-600/20 p-3">
271
+ <div className="flex items-start justify-between mb-2">
272
+ <div>
273
+ <div className="text-xs font-medium text-neutral-200">{pos.symbol}</div>
274
+ <div className={`text-xs ${pos.type === 'BUY' ? 'text-green-500' : 'text-red-500'}`}>
275
+ {pos.type} {pos.volume}
276
+ </div>
277
+ </div>
278
+ <button
279
+ onClick={() => closePosition(pos.ticket)}
280
+ className="text-neutral-400 hover:text-red-500 transition-colors"
281
+ >
282
+ <X size={14} />
283
+ </button>
284
+ </div>
285
+
286
+ <div className="space-y-1 text-xs">
287
+ <div className="flex justify-between text-neutral-400">
288
+ <span>Entry:</span>
289
+ <span className="font-mono">{formatPrice(pos.price_open)}</span>
290
+ </div>
291
+ <div className="flex justify-between text-neutral-400">
292
+ <span>Current:</span>
293
+ <span className="font-mono">{formatPrice(pos.price_current)}</span>
294
+ </div>
295
+ <div className="flex justify-between">
296
+ <span className="text-neutral-400">P&L:</span>
297
+ <span className={`font-mono font-medium ${pos.profit >= 0 ? 'text-green-500' : 'text-red-500'}`}>
298
+ ${pos.profit.toFixed(2)}
299
+ </span>
300
+ </div>
301
+ </div>
302
+ </div>
303
+ ))}
304
+ </div>
305
+ )}
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ );
312
+ };
313
+
314
+ export default TradingTerminal;
trading-terminal-ui/src/App.jsx ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
3
+ import { TrendingUp, TrendingDown, X, Activity } from 'lucide-react';
4
+
5
+ const TradingTerminal = () => {
6
+ const [ws, setWs] = useState(null);
7
+ const [connected, setConnected] = useState(false);
8
+ const [symbol, setSymbol] = useState('XAUUSDc');
9
+ const [timeframe, setTimeframe] = useState('M15');
10
+ const [chartData, setChartData] = useState([]);
11
+ const [tick, setTick] = useState(null);
12
+ const [positions, setPositions] = useState([]);
13
+ const [volume, setVolume] = useState(0.01);
14
+ const [sl, setSl] = useState('');
15
+ const [tp, setTp] = useState('');
16
+
17
+ useEffect(() => {
18
+ const socket = new WebSocket('ws://localhost:8765');
19
+
20
+ socket.onopen = () => {
21
+ setConnected(true);
22
+ console.log('Connected to MT5 server');
23
+
24
+ socket.send(JSON.stringify({
25
+ action: 'get_rates',
26
+ symbol: symbol,
27
+ timeframe: timeframe,
28
+ count: 500
29
+ }));
30
+
31
+ socket.send(JSON.stringify({ action: 'get_positions' }));
32
+ };
33
+
34
+ socket.onmessage = (event) => {
35
+ const message = JSON.parse(event.data);
36
+
37
+ if (message.type === 'rates') {
38
+ setChartData(message.data || []);
39
+ } else if (message.type === 'tick_update' || message.type === 'tick') {
40
+ setTick(message.data);
41
+ } else if (message.type === 'positions') {
42
+ setPositions(message.data || []);
43
+ } else if (message.type === 'order_result') {
44
+ if (message.data.success) {
45
+ alert('Order placed successfully!');
46
+ socket.send(JSON.stringify({ action: 'get_positions' }));
47
+ } else {
48
+ alert('Order failed: ' + message.data.error);
49
+ }
50
+ } else if (message.type === 'close_result') {
51
+ if (message.data.success) {
52
+ alert('Position closed!');
53
+ socket.send(JSON.stringify({ action: 'get_positions' }));
54
+ } else {
55
+ alert('Close failed: ' + message.data.error);
56
+ }
57
+ }
58
+ };
59
+
60
+ socket.onerror = (error) => {
61
+ console.error('WebSocket error:', error);
62
+ setConnected(false);
63
+ };
64
+
65
+ socket.onclose = () => {
66
+ setConnected(false);
67
+ console.log('Disconnected from MT5 server');
68
+ };
69
+
70
+ setWs(socket);
71
+
72
+ return () => {
73
+ socket.close();
74
+ };
75
+ }, []);
76
+
77
+ const placeOrder = (type) => {
78
+ if (!ws || !connected) return;
79
+
80
+ ws.send(JSON.stringify({
81
+ action: 'place_order',
82
+ symbol: symbol,
83
+ order_type: type,
84
+ volume: parseFloat(volume),
85
+ sl: sl ? parseFloat(sl) : null,
86
+ tp: tp ? parseFloat(tp) : null
87
+ }));
88
+ };
89
+
90
+ const closePosition = (ticket) => {
91
+ if (!ws || !connected) return;
92
+
93
+ ws.send(JSON.stringify({
94
+ action: 'close_position',
95
+ ticket: ticket
96
+ }));
97
+ };
98
+
99
+ const changeTimeframe = (tf) => {
100
+ setTimeframe(tf);
101
+ if (ws && connected) {
102
+ ws.send(JSON.stringify({
103
+ action: 'get_rates',
104
+ symbol: symbol,
105
+ timeframe: tf,
106
+ count: 500
107
+ }));
108
+ }
109
+ };
110
+
111
+ const formatPrice = (price) => {
112
+ return price ? price.toFixed(2) : '-';
113
+ };
114
+
115
+ const totalPnL = positions.reduce((sum, pos) => sum + pos.profit, 0);
116
+
117
+ return (
118
+ <div className="w-full h-screen bg-neutral-950 text-neutral-200 flex flex-col">
119
+ {/* Header */}
120
+ <div className="h-12 bg-neutral-900 border-b border-yellow-600/20 flex items-center justify-between px-4">
121
+ <div className="flex items-center gap-4">
122
+ <div className="flex items-center gap-2">
123
+ <div className={`w-2 h-2 rounded-full ${connected ? 'bg-yellow-500' : 'bg-neutral-600'}`}></div>
124
+ <span className="text-sm font-medium text-yellow-500">{symbol}</span>
125
+ </div>
126
+
127
+ {tick && (
128
+ <div className="flex items-center gap-4 text-sm">
129
+ <span className="text-neutral-400">BID: <span className="text-yellow-500 font-mono">{formatPrice(tick.bid)}</span></span>
130
+ <span className="text-neutral-400">ASK: <span className="text-yellow-500 font-mono">{formatPrice(tick.ask)}</span></span>
131
+ </div>
132
+ )}
133
+ </div>
134
+
135
+ <div className="flex items-center gap-2">
136
+ <span className={`text-sm font-mono ${totalPnL >= 0 ? 'text-green-500' : 'text-red-500'}`}>
137
+ P&L: ${totalPnL.toFixed(2)}
138
+ </span>
139
+ </div>
140
+ </div>
141
+
142
+ {/* Main Content */}
143
+ <div className="flex-1 flex overflow-hidden">
144
+ {/* Chart Area */}
145
+ <div className="flex-1 flex flex-col">
146
+ {/* Timeframe Selector */}
147
+ <div className="h-10 bg-neutral-900 border-b border-yellow-600/20 flex items-center px-4 gap-2">
148
+ {['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1'].map(tf => (
149
+ <button
150
+ key={tf}
151
+ onClick={() => changeTimeframe(tf)}
152
+ className={`px-3 py-1 text-xs font-medium transition-colors ${
153
+ timeframe === tf
154
+ ? 'bg-yellow-600 text-neutral-950'
155
+ : 'text-neutral-400 hover:text-yellow-500'
156
+ }`}
157
+ >
158
+ {tf}
159
+ </button>
160
+ ))}
161
+ </div>
162
+
163
+ {/* Chart */}
164
+ <div className="flex-1 bg-neutral-950 p-4">
165
+ {chartData.length > 0 ? (
166
+ <ResponsiveContainer width="100%" height="100%">
167
+ <LineChart data={chartData}>
168
+ <XAxis
169
+ dataKey="time"
170
+ tickFormatter={(time) => new Date(time).toLocaleTimeString()}
171
+ stroke="#525252"
172
+ style={{ fontSize: 10 }}
173
+ />
174
+ <YAxis
175
+ domain={['auto', 'auto']}
176
+ stroke="#525252"
177
+ style={{ fontSize: 10 }}
178
+ />
179
+ <Tooltip
180
+ contentStyle={{
181
+ backgroundColor: '#171717',
182
+ border: '1px solid #ca8a04',
183
+ borderRadius: 0,
184
+ fontSize: 12
185
+ }}
186
+ labelFormatter={(time) => new Date(time).toLocaleString()}
187
+ />
188
+ <Line
189
+ type="monotone"
190
+ dataKey="close"
191
+ stroke="#ca8a04"
192
+ dot={false}
193
+ strokeWidth={1}
194
+ />
195
+ </LineChart>
196
+ </ResponsiveContainer>
197
+ ) : (
198
+ <div className="flex items-center justify-center h-full text-neutral-600">
199
+ Loading chart data...
200
+ </div>
201
+ )}
202
+ </div>
203
+ </div>
204
+
205
+ {/* Right Panel */}
206
+ <div className="w-80 bg-neutral-900 border-l border-yellow-600/20 flex flex-col">
207
+ {/* Trade Panel */}
208
+ <div className="p-4 border-b border-yellow-600/20">
209
+ <div className="text-xs font-medium text-yellow-500 mb-3">NEW ORDER</div>
210
+
211
+ <div className="space-y-2">
212
+ <div>
213
+ <label className="text-xs text-neutral-400">Volume</label>
214
+ <input
215
+ type="number"
216
+ step="0.01"
217
+ value={volume}
218
+ onChange={(e) => setVolume(e.target.value)}
219
+ className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600"
220
+ />
221
+ </div>
222
+
223
+ <div>
224
+ <label className="text-xs text-neutral-400">Stop Loss</label>
225
+ <input
226
+ type="number"
227
+ step="0.01"
228
+ value={sl}
229
+ onChange={(e) => setSl(e.target.value)}
230
+ placeholder="Optional"
231
+ className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600 placeholder:text-neutral-700"
232
+ />
233
+ </div>
234
+
235
+ <div>
236
+ <label className="text-xs text-neutral-400">Take Profit</label>
237
+ <input
238
+ type="number"
239
+ step="0.01"
240
+ value={tp}
241
+ onChange={(e) => setTp(e.target.value)}
242
+ placeholder="Optional"
243
+ className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600 placeholder:text-neutral-700"
244
+ />
245
+ </div>
246
+
247
+ <div className="flex gap-2 pt-2">
248
+ <button
249
+ onClick={() => placeOrder('BUY')}
250
+ className="flex-1 bg-green-700 hover:bg-green-600 text-white py-2 text-sm font-medium transition-colors flex items-center justify-center gap-1"
251
+ disabled={!connected}
252
+ >
253
+ <TrendingUp size={14} />
254
+ BUY
255
+ </button>
256
+ <button
257
+ onClick={() => placeOrder('SELL')}
258
+ className="flex-1 bg-red-700 hover:bg-red-600 text-white py-2 text-sm font-medium transition-colors flex items-center justify-center gap-1"
259
+ disabled={!connected}
260
+ >
261
+ <TrendingDown size={14} />
262
+ SELL
263
+ </button>
264
+ </div>
265
+ </div>
266
+ </div>
267
+
268
+ {/* Positions Panel */}
269
+ <div className="flex-1 overflow-auto">
270
+ <div className="p-4">
271
+ <div className="text-xs font-medium text-yellow-500 mb-3">POSITIONS ({positions.length})</div>
272
+
273
+ {positions.length === 0 ? (
274
+ <div className="text-xs text-neutral-600 text-center py-8">No open positions</div>
275
+ ) : (
276
+ <div className="space-y-2">
277
+ {positions.map((pos) => (
278
+ <div key={pos.ticket} className="bg-neutral-950 border border-yellow-600/20 p-3">
279
+ <div className="flex items-start justify-between mb-2">
280
+ <div>
281
+ <div className="text-xs font-medium text-neutral-200">{pos.symbol}</div>
282
+ <div className={`text-xs ${pos.type === 'BUY' ? 'text-green-500' : 'text-red-500'}`}>
283
+ {pos.type} {pos.volume}
284
+ </div>
285
+ </div>
286
+ <button
287
+ onClick={() => closePosition(pos.ticket)}
288
+ className="text-neutral-400 hover:text-red-500 transition-colors"
289
+ >
290
+ <X size={14} />
291
+ </button>
292
+ </div>
293
+
294
+ <div className="space-y-1 text-xs">
295
+ <div className="flex justify-between text-neutral-400">
296
+ <span>Entry:</span>
297
+ <span className="font-mono">{formatPrice(pos.price_open)}</span>
298
+ </div>
299
+ <div className="flex justify-between text-neutral-400">
300
+ <span>Current:</span>
301
+ <span className="font-mono">{formatPrice(pos.price_current)}</span>
302
+ </div>
303
+ <div className="flex justify-between">
304
+ <span className="text-neutral-400">P&L:</span>
305
+ <span className={`font-mono font-medium ${pos.profit >= 0 ? 'text-green-500' : 'text-red-500'}`}>
306
+ ${pos.profit.toFixed(2)}
307
+ </span>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ ))}
312
+ </div>
313
+ )}
314
+ </div>
315
+ </div>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ );
320
+ };
321
+
322
+ export default TradingTerminal;
trading-terminal-ui/src/index.css ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
10
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
11
+ sans-serif;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ overflow: hidden;
15
+ }
16
+
17
+ code {
18
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
19
+ monospace;
20
+ }
21
+
22
+ #root {
23
+ height: 100vh;
24
+ width: 100vw;
25
+ }
trading-terminal-ui/src/index.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import './index.css';
4
+ import App from './App';
5
+
6
+ const root = ReactDOM.createRoot(document.getElementById('root'));
7
+ root.render(
8
+ <React.StrictMode>
9
+ <App />
10
+ </React.StrictMode>
11
+ );