jgtfx2console 0.4.25__tar.gz → 0.4.27__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. {jgtfx2console-0.4.25/jgtfx2console.egg-info → jgtfx2console-0.4.27}/PKG-INFO +1 -1
  2. jgtfx2console-0.4.27/jgtfx2console/LiveChartDataExport.py +431 -0
  3. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/__init__.py +1 -1
  4. jgtfx2console-0.4.27/jgtfx2console/config_generator.py +74 -0
  5. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27/jgtfx2console.egg-info}/PKG-INFO +1 -1
  6. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console.egg-info/SOURCES.txt +2 -0
  7. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/pyproject.toml +1 -1
  8. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/LICENSE +0 -0
  9. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/README.md +0 -0
  10. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/README.rst +0 -0
  11. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/EachRowListener.py +0 -0
  12. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/ForexConnect.py +0 -0
  13. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/LiveHistory.py +0 -0
  14. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/ResponseListener.py +0 -0
  15. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/SessionStatusListener.py +0 -0
  16. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/TableListener.py +0 -0
  17. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/TableManagerListener.py +0 -0
  18. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/__init__.py +0 -0
  19. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/common.py +0 -0
  20. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/errors.py +0 -0
  21. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/forexconnect/x-pyd.py +0 -0
  22. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console/fxcli2console.py +0 -0
  23. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console.egg-info/dependency_links.txt +0 -0
  24. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console.egg-info/entry_points.txt +0 -0
  25. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console.egg-info/requires.txt +0 -0
  26. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/jgtfx2console.egg-info/top_level.txt +0 -0
  27. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/setup.cfg +0 -0
  28. {jgtfx2console-0.4.25 → jgtfx2console-0.4.27}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jgtfx2console
3
- Version: 0.4.25
3
+ Version: 0.4.27
4
4
  Summary: PDS Services
5
5
  Home-page: https://github.com/jgwill/jgtfx2console
6
6
  Author: GUillaume Isabelle
@@ -0,0 +1,431 @@
1
+ import numpy as np
2
+ import time
3
+ import argparse
4
+ import os
5
+ import sys
6
+
7
+ from jgtutils import jgtcommon
8
+
9
+
10
+ path_from_where_we_call = os.getcwd()
11
+
12
+ sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
13
+
14
+ c_script_dir_name = os.path.dirname(__file__)
15
+ abs_path_of_this_file = os.path.abspath(c_script_dir_name)
16
+ print("abs_path_of_this_file: "+abs_path_of_this_file)
17
+ print("c_script_dir_name: "+c_script_dir_name)
18
+ path1 = os.path.join(c_script_dir_name, '.')
19
+ print("path1: "+path1)
20
+ abs_path1 = os.path.abspath(path1)
21
+ print("abs_path1: "+abs_path1)
22
+ sys.path.append(abs_path1)
23
+ #cd to : abs_path_of_this_file #@STCIssue Wont go further that CONNECTED if we are not in that directory.
24
+ os.chdir(abs_path_of_this_file)
25
+
26
+ from forexconnect import fxcorepy, ForexConnect
27
+ from forexconnect import Common, LiveHistoryCreator
28
+
29
+ from dateutil import parser
30
+
31
+ import xml.etree.ElementTree as ET
32
+
33
+ import common_samples
34
+ import csv
35
+ import pytz
36
+ from datetime import timedelta, datetime
37
+
38
+ delimiter = None
39
+ datetime_separator = None
40
+ format_decimal_places = None
41
+ timezone = None
42
+
43
+
44
+ def parse_args():
45
+ arg_parser = argparse.ArgumentParser(description='Process command parameters.')
46
+ arg_parser.add_argument('-p',
47
+ metavar="PASSWORD",
48
+ required=False,
49
+ help='Your password.')
50
+ arg_parser.add_argument('-config', metavar="CONFIG_FILE", default='jgtfxliveconfig.xml',
51
+ help='Config file')
52
+
53
+ args = arg_parser.parse_args()
54
+ return args
55
+
56
+
57
+ verbose = 0
58
+ order_request_id = ""
59
+
60
+
61
+ class SaveNewBar:
62
+ def __init__(self, symbol, bars):
63
+ self.symbol = symbol
64
+ self.bars = bars
65
+
66
+ def save_bar(self, instrument, history, filename):
67
+ global delimiter
68
+ global datetime_separator
69
+ global format_decimal_places
70
+ global timezone
71
+
72
+ if timezone == 'Local':
73
+ time_difference = -time.timezone
74
+ else:
75
+ tz = pytz.timezone(timezone)
76
+ time_difference = tz.utcoffset(datetime.now).total_seconds()
77
+
78
+ hist = history[:-1].tail(1)
79
+ with open(filename, "a", newline="") as file:
80
+ writer = csv.writer(file, delimiter=delimiter)
81
+ last_complete_data_frame = hist
82
+ dtime = str(last_complete_data_frame.index.values[0])
83
+
84
+ dtime = dtime.replace('T', ' ')
85
+ dt = parser.parse(dtime)
86
+ dt = dt+timedelta(0, time_difference)
87
+ dt = str(dt)
88
+ dtime = dt.replace(' ', datetime_separator)
89
+ out = [dtime]
90
+ str_prices = dtime + ", "
91
+ for price_name in last_complete_data_frame:
92
+ price_entry = last_complete_data_frame.get(price_name)
93
+ price_value = price_entry.values[0]
94
+ str_prices += price_name + "=" + str(price_value) + ", "
95
+ price = price_entry.values[0]
96
+ if format_decimal_places and price_name != "Volume":
97
+ if instrument.find("JPY") >= 0:
98
+ out.append("%.3f" % price)
99
+ else:
100
+ out.append("%.5f" % price)
101
+ else:
102
+ out.append(price)
103
+ writer.writerow(out)
104
+ file.close()
105
+ print("New bar saved to "+filename+": "+str_prices[0: -2])
106
+
107
+ return
108
+
109
+
110
+ order_created_count = 0
111
+
112
+
113
+ def find_in_tree(tree, node):
114
+ found = tree.find(node)
115
+ if found is None:
116
+ found = []
117
+ return found
118
+
119
+
120
+ def on_request_completed(request_id, response):
121
+ del request_id, response
122
+ global order_created_count
123
+ order_created_count += 1
124
+ return True
125
+
126
+
127
+ def on_changed(live_history, instrument):
128
+ def _on_changed(table_listener, row_id, row):
129
+ del table_listener, row_id
130
+ try:
131
+ if row.table_type == fxcorepy.O2GTableType.OFFERS and row.instrument == instrument:
132
+ live_history.add_or_update(row)
133
+ except Exception as e:
134
+ common_samples.print_exception(e)
135
+ return
136
+
137
+ return _on_changed
138
+
139
+
140
+ def on_bar_added(instrument, filename):
141
+ def _on_bar_added(history):
142
+ snb = SaveNewBar(instrument, history[:-1])
143
+ snb.save_bar(instrument, history, filename)
144
+
145
+ return _on_bar_added
146
+
147
+
148
+ def session_status_changed(fx, live_history, instrument, str_user_id, str_password, str_url, str_connection,
149
+ reconnect_on_disconnected):
150
+ offers_listener = None
151
+ first_call = reconnect_on_disconnected
152
+ orders_listener = None
153
+
154
+ def _session_status_changed(session, status):
155
+ nonlocal offers_listener
156
+ nonlocal first_call
157
+ nonlocal orders_listener
158
+ if not first_call:
159
+ common_samples.session_status_changed(session.trading_session_descriptors, status)
160
+ else:
161
+ first_call = False
162
+ if status == fxcorepy.AO2GSessionStatus.O2GSessionStatus.CONNECTED:
163
+ offers = fx.get_table(ForexConnect.OFFERS)
164
+ if live_history is not None:
165
+ on_changed_callback = on_changed(live_history, instrument)
166
+ offers_listener = Common.subscribe_table_updates(offers, on_change_callback=on_changed_callback)
167
+ elif status == fxcorepy.AO2GSessionStatus.O2GSessionStatus.DISCONNECTING or \
168
+ status == fxcorepy.AO2GSessionStatus.O2GSessionStatus.RECONNECTING or \
169
+ status == fxcorepy.AO2GSessionStatus.O2GSessionStatus.SESSION_LOST:
170
+ if orders_listener is not None:
171
+ orders_listener.unsubscribe()
172
+ orders_listener = None
173
+ if offers_listener is not None:
174
+ offers_listener.unsubscribe()
175
+ offers_listener = None
176
+ elif status == fxcorepy.AO2GSessionStatus.O2GSessionStatus.DISCONNECTED and reconnect_on_disconnected:
177
+ fx.session.login(str_user_id, str_password, str_url, str_connection)
178
+
179
+ return _session_status_changed
180
+
181
+
182
+ def check_params(instr, tf, offer):
183
+ if not instr:
184
+ raise Exception(
185
+ "The instrument is empty")
186
+
187
+ if not tf:
188
+ raise Exception(
189
+ "The timeframe is empty")
190
+
191
+ if not offer:
192
+ raise Exception(
193
+ "The instrument '{0}' is not valid".format(instr))
194
+
195
+
196
+ def parse_xml(config_file):
197
+ try:
198
+ jgtconf=jgtcommon.readconfig()
199
+ #print(jgtconf)
200
+ except:
201
+ raise Exception(
202
+ "The JGT configuration file '$HOME/config.json' does not exist")
203
+ try:
204
+ os.stat(config_file)
205
+ except OSError:
206
+ raise Exception(
207
+ "The configuration file '{0}' does not exist".format(config_file))
208
+
209
+ xmlfile = open(config_file, "r")
210
+ conf = ET.parse(xmlfile)
211
+ root = conf.getroot()
212
+
213
+ settings = find_in_tree(root, "Settings")
214
+
215
+
216
+ str_user_id = find_in_tree(settings, "Login").text
217
+ if str_user_id == '0' or str_user_id == '' or str_user_id is None:
218
+ str_user_id =jgtconf.get('user_id')
219
+
220
+ str_url = find_in_tree(settings, "Url").text
221
+ str_connection = find_in_tree(settings, "Connection").text
222
+ if str_connection == '' or str_connection is None or str_connection=='0':
223
+ str_connection = os.getenv('connection') or jgtconf.get('connection')
224
+ str_session_id = find_in_tree(settings, "SessionID").text
225
+ str_pin = find_in_tree(settings, "Pin").text
226
+ delim = find_in_tree(settings, "Delimiter").text
227
+ output_dir = find_in_tree(settings, "OutputDir").text
228
+ #test if our path is relative
229
+ if output_dir and output_dir[0] != '/':
230
+ output_dir = os.path.join(path_from_where_we_call, output_dir)
231
+ #print("relative output_dir modified: "+output_dir)
232
+ if output_dir == '' or output_dir is None or output_dir=='0':
233
+ output_dir = os.getenv('JGTPY_DATA') or jgtconf.get('JGTPY_DATA')
234
+ dt_separator = find_in_tree(settings, "DateTimeSeparator").text
235
+
236
+ if dt_separator == '' or dt_separator is None:
237
+ dt_separator = ' '
238
+ fdp = find_in_tree(settings, "FormatDecimalPlaces").text
239
+ tzone = find_in_tree(settings, "Timezone").text
240
+
241
+ if tzone != 'EST' and tzone != 'UTC' and tzone != 'Local':
242
+ print('Timezone is not recognized, using UTC')
243
+ tzone = 'UTC' #Default timezone
244
+
245
+ print("=============================")
246
+ print("User ID: " + str_user_id)
247
+ print("URL: " + str_url)
248
+ print("Connection: " + str_connection)
249
+ #print("Session ID: " + str_session_id)
250
+ #print("Pin: " + str_pin)
251
+ #print("Delimiter: " + delim)
252
+ print("Output Directory: " + output_dir)
253
+ print("DateTime Separator: " + dt_separator)
254
+ print("Format Decimal Places: " + fdp)
255
+ print("Timezone: " + tzone)
256
+
257
+ print("=============================")
258
+
259
+ if output_dir:
260
+ if not os.path.exists(output_dir):
261
+ raise Exception(
262
+ "The output directory '{0}' does not exist. (try writting an absolute path in the config or use 0 so it will read the JGTPY_DATA environment variable)".format(output_dir))
263
+
264
+ if fdp == "Y" or fdp == "y":
265
+ fdp = True
266
+ else:
267
+ fdp = False
268
+
269
+ data = []
270
+
271
+ for elem in settings.findall("History"):
272
+ data2 = [find_in_tree(elem, "Instrument").text]
273
+ data2.append(find_in_tree(elem, "Timeframe").text)
274
+ if output_dir:
275
+ ifn = find_in_tree(elem, "Filename").text
276
+ data2.append(os.path.join(output_dir, ifn))
277
+ else:
278
+ data2.append(find_in_tree(elem, "Filename").text)
279
+ data2.append(find_in_tree(elem, "NumBars").text)
280
+ data2.append(find_in_tree(elem, "Headers").text)
281
+ data.append(data2)
282
+
283
+ if len(data) == 0:
284
+ raise Exception(
285
+ "No instruments in the config file are present")
286
+
287
+ return str_user_id, str_url, str_connection, str_session_id, str_pin, \
288
+ delim, output_dir, dt_separator, fdp, tzone, data
289
+
290
+
291
+ def set_init_history(fx, lhc, instr, tf, filename, numbars, str_user_id, str_password, str_url, str_connection):
292
+ lhc.append(LiveHistoryCreator(tf))
293
+ last_index = len(lhc)-1
294
+ on_bar_added_callback = on_bar_added(instr, filename)
295
+ lhc[last_index].subscribe(on_bar_added_callback)
296
+ session_status_changed_callback = session_status_changed(fx, lhc[last_index], instr, str_user_id,
297
+ str_password, str_url, str_connection, True)
298
+ session_status_changed_callback(fx.session, fx.session.session_status)
299
+ fx.set_session_status_listener(session_status_changed_callback)
300
+
301
+ nd_array_history = fx.get_history(instr, tf, None, None, int(numbars)+2)
302
+
303
+ lhc[last_index].history = nd_array_history
304
+
305
+ return lhc, nd_array_history
306
+
307
+
308
+ def get_time_difference(tzone):
309
+ if tzone == 'Local':
310
+ time_difference = -time.timezone
311
+ else:
312
+ tz = pytz.timezone(tzone)
313
+ time_difference = tz.utcoffset(datetime.now).total_seconds()
314
+ return time_difference
315
+
316
+
317
+ def save_old_history(instr, filename, headers, nd_array_history, time_difference, dt_separator):
318
+ header = ['DateTime', 'Bid Open', 'Bid High', 'Bid Low', 'Bid Close', 'Ask Open', 'Ask High',
319
+ 'Ask Low', 'Ask Close', 'Volume']
320
+ with open(filename, "w", newline="") as file:
321
+ writer = csv.writer(file, delimiter=delimiter)
322
+ if headers:
323
+ head = [headers]
324
+ writer.writerow(head)
325
+ else:
326
+ writer.writerow(header)
327
+ for i in range(1, len(nd_array_history)-1):
328
+ last_complete_data_frame = nd_array_history[i:i+1]
329
+ str2 = str(last_complete_data_frame[0])
330
+ str2 = str2.replace('(', '')
331
+ str2 = str2.replace(')', '')
332
+ str2 = str2.replace("'", '')
333
+ str2 = str2.replace(' ', '')
334
+ str2 = str2.split(',')
335
+ array = np.array(str2)
336
+ array[0] = array[0].replace('T', ' ')
337
+ dt = parser.parse(array[0])
338
+ dt = dt+timedelta(0, time_difference)
339
+ dt = str(dt)
340
+ array[0] = dt.replace(' ', dt_separator)
341
+ out = [array[0]]
342
+ for i2 in range(1, len(array)-1):
343
+ price = float(array[i2])
344
+ if format_decimal_places:
345
+ if instr.find("JPY") >= 0:
346
+ out.append("%.3f" % price)
347
+ else:
348
+ out.append("%.5f" % price)
349
+ else:
350
+ out.append(price)
351
+ out.append(array[len(array)-1])
352
+ writer.writerow(out)
353
+
354
+ def main():
355
+ global delimiter
356
+ global datetime_separator
357
+ global format_decimal_places
358
+ global timezone
359
+
360
+ args = parse_args()
361
+ config_file = args.config
362
+ if not os.path.exists(config_file):
363
+ config_file=os.path.join(path_from_where_we_call,config_file)
364
+
365
+ jgtconf=jgtcommon.readconfig()
366
+
367
+ if args.p:
368
+ str_password = args.p
369
+ else:
370
+ str_password = jgtconf.get('password') or os.getenv('password')
371
+
372
+ if str_password is None:
373
+ raise Exception("Password is required. Use -p option or export password=mypasswd")
374
+ sys.exit("Password is required. Use -p option or export password=mypasswd")
375
+
376
+ str_user_id, str_url, str_connection, str_session_id, str_pin, delimiter, output_dir, \
377
+ datetime_separator, format_decimal_places, timezone, data = parse_xml(config_file)
378
+
379
+ # print("cwd: "+os.getcwd())
380
+ # os.chdir(abs_path_of_this_file)
381
+ # print("cwd: "+os.getcwd())
382
+ with ForexConnect() as fx:
383
+ try:
384
+ print("Connecting to: user id:"+str_user_id+", url:"+str_url+", Connection:"+str_connection)
385
+ try:
386
+ fx.login(str_user_id, str_password, str_url,
387
+ str_connection, str_session_id, str_pin,
388
+ common_samples.session_status_changed)
389
+ except Exception as e:
390
+ print("Exception: " + str(e))
391
+ raise Exception(
392
+ "Login failed. Invalid parameters")
393
+
394
+ lhc = []
395
+ for param in data:
396
+ offer = Common.get_offer(fx, param[0])
397
+
398
+ check_params(param[0], param[1], offer)
399
+
400
+ tf = ForexConnect.get_timeframe(fx, param[1])
401
+ if not tf:
402
+ raise Exception(
403
+ "The timeframe '{0}' is not valid".format(param[1]))
404
+
405
+ lhc, nd_array_history = set_init_history(fx, lhc, param[0], param[1], param[2], param[3],
406
+ str_user_id, str_password, str_url, str_connection)
407
+
408
+ time_difference = get_time_difference(timezone)
409
+
410
+ save_old_history(param[0], param[2], param[4], nd_array_history, time_difference, datetime_separator)
411
+
412
+ print("Old history saved to "+param[2])
413
+
414
+ while True:
415
+ time.sleep(60)
416
+
417
+ except Exception as e:
418
+ common_samples.print_exception(e)
419
+ try:
420
+ fx.set_session_status_listener(session_status_changed(fx, None, None, str_user_id, str_password,
421
+ str_url, str_connection, False))
422
+ fx.logout()
423
+ except Exception as e:
424
+ common_samples.print_exception(e)
425
+
426
+
427
+ if __name__ == "__main__":
428
+ main()
429
+ print("")
430
+ input("Done! Press enter key to exit\n")
431
+
@@ -42,6 +42,6 @@ with warnings.catch_warnings():
42
42
  # your code here
43
43
 
44
44
 
45
- __version__ = "0.4.25"
45
+ __version__ = "0.4.27"
46
46
 
47
47
 
@@ -0,0 +1,74 @@
1
+ import argparse
2
+
3
+
4
+ import os
5
+ import xml.etree.ElementTree as ET
6
+
7
+ def generate_config(instruments, timeframes, nb_bar=500, default_headers="DateTime,Bid Open,Bid Close,Ask High,Ask Low,Volume", data_dir=None):
8
+ # Split the CSV strings into lists
9
+ instruments = instruments.split(',')
10
+ timeframes = timeframes.split(',')
11
+
12
+ # Create the root element
13
+ config = ET.Element('configuration')
14
+
15
+ # Create the Settings element
16
+ settings = ET.SubElement(config, 'Settings')
17
+
18
+ # Add the Login, OutputDir, and Url elements
19
+ ET.SubElement(settings, 'Login').text = '0'#os.getenv('user_id','0')
20
+ ET.SubElement(settings, 'OutputDir').text = data_dir or os.getenv('JGTPY_DATA')
21
+ ET.SubElement(settings, 'Url').text = os.getenv('url','https://www.fxcorporate.com/Hosts.jsp')
22
+
23
+ # Add the other elements
24
+ ET.SubElement(settings, 'Connection').text = os.getenv('connection','Real')
25
+ ET.SubElement(settings, 'SessionID').text = ''
26
+ ET.SubElement(settings, 'Pin').text = ''
27
+ ET.SubElement(settings, 'Delimiter').text = ','
28
+ ET.SubElement(settings, 'DateTimeSeparator').text = ' '
29
+ ET.SubElement(settings, 'FormatDecimalPlaces').text = 'Y'
30
+ ET.SubElement(settings, 'Timezone').text = 'UTC'
31
+
32
+ # Add the History elements
33
+ for instrument in instruments:
34
+ for timeframe in timeframes:
35
+ history = ET.SubElement(settings, 'History')
36
+ ET.SubElement(history, 'Instrument').text = instrument
37
+ ET.SubElement(history, 'Timeframe').text = timeframe
38
+ ifn = instrument.replace("/","-")
39
+ ET.SubElement(history, 'Filename').text = f'{ifn}_{timeframe}.csv'
40
+ ET.SubElement(history, 'NumBars').text = str(nb_bar)
41
+ ET.SubElement(history, 'Headers').text = default_headers
42
+
43
+ # Return the XML as a string
44
+ return ET.tostring(config, encoding='utf8', method='xml').decode()
45
+
46
+ def main():
47
+ import argparse
48
+ parser = argparse.ArgumentParser(description='Generate a configuration file for the ptoLiveChartDataExport')
49
+ parser.add_argument('-i','--instruments', help='The list of instruments to export (comma-separated)')
50
+ parser.add_argument('-t','--timeframes', help='The list of timeframes to export (comma-separated)')
51
+ parser.add_argument('-o','--outxml', type=str,default="jgtfxliveconfig.xml", help='Output XML file')
52
+
53
+ #data_dir = os.getenv('JGTPY_DATA') or if --data_dir
54
+ parser.add_argument('-d','--data_dir', help='The directory where the data will be saved')
55
+
56
+
57
+
58
+ args = parser.parse_args()
59
+ instruments = args.instruments
60
+ timeframes = args.timeframes
61
+ data_dir = args.data_dir or os.getenv('JGTPY_DATA')
62
+ outxml = args.outxml
63
+
64
+ # Generate the configuration
65
+ config = generate_config(instruments, timeframes, data_dir=data_dir)
66
+
67
+ # Print the configuration
68
+ print(config)
69
+ with open(outxml, 'w') as f:
70
+ f.write(config)
71
+ print("Configuration file written to:", outxml)
72
+
73
+ if __name__ == '__main__':
74
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jgtfx2console
3
- Version: 0.4.25
3
+ Version: 0.4.27
4
4
  Summary: PDS Services
5
5
  Home-page: https://github.com/jgwill/jgtfx2console
6
6
  Author: GUillaume Isabelle
@@ -4,7 +4,9 @@ README.rst
4
4
  pyproject.toml
5
5
  setup.cfg
6
6
  setup.py
7
+ jgtfx2console/LiveChartDataExport.py
7
8
  jgtfx2console/__init__.py
9
+ jgtfx2console/config_generator.py
8
10
  jgtfx2console/fxcli2console.py
9
11
  jgtfx2console.egg-info/PKG-INFO
10
12
  jgtfx2console.egg-info/SOURCES.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jgtfx2console"
7
- version = "0.4.25"
7
+ version = "0.4.27"
8
8
  authors = [
9
9
  { name="Guillaume Isabelle", email="jgi@jgwill.com" },
10
10
  ]
File without changes
File without changes
File without changes
File without changes