jgtcore 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jgtcore/__init__.py +87 -0
- jgtcore/core.py +607 -0
- jgtcore/example.py +85 -0
- jgtcore/test_core.py +210 -0
- jgtcore-0.1.0.dist-info/LICENSE +21 -0
- jgtcore-0.1.0.dist-info/METADATA +116 -0
- jgtcore-0.1.0.dist-info/RECORD +9 -0
- jgtcore-0.1.0.dist-info/WHEEL +5 -0
- jgtcore-0.1.0.dist-info/top_level.txt +1 -0
jgtcore/__init__.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
jgtcore - Core library functions extracted from jgtutils
|
|
5
|
+
|
|
6
|
+
This module provides the core configuration, settings, and utility functions
|
|
7
|
+
from jgtutils without CLI dependencies, making them suitable for use in
|
|
8
|
+
other libraries and applications.
|
|
9
|
+
|
|
10
|
+
Main Functions:
|
|
11
|
+
- Configuration: readconfig(), get_config(), get_config_value()
|
|
12
|
+
- Settings: load_settings(), get_settings(), get_setting()
|
|
13
|
+
- Environment: setup_environment(), export_env_if_any()
|
|
14
|
+
- Utilities: str_to_datetime(), is_market_open(), print_exception()
|
|
15
|
+
- Trading: read_fx_str_from_config(), is_demo_mode()
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .core import (
|
|
19
|
+
# Core configuration functions
|
|
20
|
+
readconfig,
|
|
21
|
+
load_settings,
|
|
22
|
+
get_settings,
|
|
23
|
+
|
|
24
|
+
# Simple API wrappers
|
|
25
|
+
get_config,
|
|
26
|
+
get_setting,
|
|
27
|
+
setup_environment,
|
|
28
|
+
get_config_value,
|
|
29
|
+
is_demo_mode,
|
|
30
|
+
|
|
31
|
+
# Environment helpers
|
|
32
|
+
load_arg_from_jgt_env,
|
|
33
|
+
load_arg_default_from_settings,
|
|
34
|
+
export_env_if_any,
|
|
35
|
+
|
|
36
|
+
# Utility functions
|
|
37
|
+
str_to_datetime,
|
|
38
|
+
print_exception,
|
|
39
|
+
is_market_open,
|
|
40
|
+
dt_from_last_week_as_datetime,
|
|
41
|
+
dt_from_last_week_as_string_fxformat,
|
|
42
|
+
|
|
43
|
+
# Trading helpers
|
|
44
|
+
read_fx_str_from_config,
|
|
45
|
+
|
|
46
|
+
# Helper functions for advanced use
|
|
47
|
+
update_settings,
|
|
48
|
+
load_arg_default_from_settings_if_exist,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
__version__ = "1.0.0"
|
|
52
|
+
__author__ = "JGWill"
|
|
53
|
+
__description__ = "Core library functions extracted from jgtutils"
|
|
54
|
+
|
|
55
|
+
# Export the main functions that external packages are likely to use
|
|
56
|
+
__all__ = [
|
|
57
|
+
# Configuration functions
|
|
58
|
+
'readconfig',
|
|
59
|
+
'load_settings',
|
|
60
|
+
'get_settings',
|
|
61
|
+
|
|
62
|
+
# Simple API wrappers
|
|
63
|
+
'get_config',
|
|
64
|
+
'get_setting',
|
|
65
|
+
'setup_environment',
|
|
66
|
+
'get_config_value',
|
|
67
|
+
'is_demo_mode',
|
|
68
|
+
|
|
69
|
+
# Environment helpers
|
|
70
|
+
'load_arg_from_jgt_env',
|
|
71
|
+
'load_arg_default_from_settings',
|
|
72
|
+
'export_env_if_any',
|
|
73
|
+
|
|
74
|
+
# Utility functions
|
|
75
|
+
'str_to_datetime',
|
|
76
|
+
'print_exception',
|
|
77
|
+
'is_market_open',
|
|
78
|
+
'dt_from_last_week_as_datetime',
|
|
79
|
+
'dt_from_last_week_as_string_fxformat',
|
|
80
|
+
|
|
81
|
+
# Trading helpers
|
|
82
|
+
'read_fx_str_from_config',
|
|
83
|
+
|
|
84
|
+
# Helper functions
|
|
85
|
+
'update_settings',
|
|
86
|
+
'load_arg_default_from_settings_if_exist',
|
|
87
|
+
]
|
jgtcore/core.py
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright 2019 Gehtsoft USA LLC
|
|
4
|
+
# Copyright 2023 JGWill (extended/variations)
|
|
5
|
+
|
|
6
|
+
# Licensed under the license derived from the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
# http://fxcodebase.com/licenses/open-source/license.html
|
|
12
|
+
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
jgtcore.core - Core library functions extracted from jgtutils
|
|
21
|
+
Provides configuration, settings, and utility functions without CLI dependencies.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import ruamel.yaml
|
|
26
|
+
yaml = ruamel.yaml.YAML()
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
import traceback
|
|
31
|
+
from datetime import datetime, time, timezone, timedelta
|
|
32
|
+
from typing import Dict, Tuple, Optional, Any
|
|
33
|
+
|
|
34
|
+
# Global variables for caching
|
|
35
|
+
settings: Dict[str, Any] = {}
|
|
36
|
+
_JGT_CONFIG_JSON_SECRET: Optional[str] = None
|
|
37
|
+
|
|
38
|
+
# Core Configuration Functions
|
|
39
|
+
|
|
40
|
+
def _load_settings_from_path(path: str) -> Dict[str, Any]:
|
|
41
|
+
"""Load settings from a JSON file path."""
|
|
42
|
+
if os.path.exists(path):
|
|
43
|
+
with open(path, 'r') as f:
|
|
44
|
+
loaded_data = json.load(f)
|
|
45
|
+
return loaded_data
|
|
46
|
+
return {}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _load_settings_from_path_yaml(path: str, key: Optional[str] = None) -> Dict[str, Any]:
|
|
50
|
+
"""Load settings from a YAML file path, optionally extracting a specific key."""
|
|
51
|
+
if os.path.exists(path):
|
|
52
|
+
with open(path, 'r') as f:
|
|
53
|
+
if key is not None:
|
|
54
|
+
try:
|
|
55
|
+
yaml_value = yaml.load(f)
|
|
56
|
+
except yaml.YAMLError as exc:
|
|
57
|
+
print(exc)
|
|
58
|
+
if yaml_value is not None and key in yaml_value:
|
|
59
|
+
return yaml_value[key]
|
|
60
|
+
else:
|
|
61
|
+
return {}
|
|
62
|
+
yaml_data = yaml.load(f)
|
|
63
|
+
if yaml_data is None:
|
|
64
|
+
return {}
|
|
65
|
+
return yaml_data
|
|
66
|
+
return {}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def update_settings(old_settings: Dict[str, Any], new_settings: Dict[str, Any], keys: list = ['patterns']) -> None:
|
|
70
|
+
"""Update old_settings with new_settings, handling special keys separately."""
|
|
71
|
+
# if our old settings has a key in our keys list, then we will update it on their own
|
|
72
|
+
# (meaning we will not merge it directly but update it independently)
|
|
73
|
+
for key in keys:
|
|
74
|
+
if key in old_settings:
|
|
75
|
+
if new_settings is not None and key in new_settings:
|
|
76
|
+
test_if_key_not_none = new_settings[key]
|
|
77
|
+
if test_if_key_not_none is not None:
|
|
78
|
+
old_settings[key].update(new_settings[key])
|
|
79
|
+
# remove the key from the new settings
|
|
80
|
+
new_settings.pop(key)
|
|
81
|
+
if new_settings is not None:
|
|
82
|
+
old_settings.update(new_settings)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _settings_loaded(_settings: Dict[str, Any]) -> None:
|
|
86
|
+
"""Hook called when settings are loaded. Override for custom behavior."""
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def load_settings(custom_path: Optional[str] = None, old: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Load settings from multiple locations in order of precedence.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
custom_path: Optional custom path to settings file
|
|
96
|
+
old: Existing settings to merge with
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Merged settings dictionary
|
|
100
|
+
"""
|
|
101
|
+
system_settings_path = os.path.join('/etc', 'jgt', 'settings.json')
|
|
102
|
+
home_settings_path = os.path.join(os.path.expanduser('~'), '.jgt', 'settings.json')
|
|
103
|
+
current_settings_path = os.path.join(os.getcwd(), '.jgt', 'settings.json')
|
|
104
|
+
yaml_current_settings_path = os.path.join(os.getcwd(), '.jgt', 'settings.yml')
|
|
105
|
+
jgt_yaml_current_settings_path = os.path.join(os.getcwd(), 'jgt.yml')
|
|
106
|
+
jubook_jgt_yaml_current_settings_path = os.path.join(os.getcwd(), '_config.yml')
|
|
107
|
+
|
|
108
|
+
_settings = {}
|
|
109
|
+
if old is not None:
|
|
110
|
+
_settings = old
|
|
111
|
+
|
|
112
|
+
# Load system settings
|
|
113
|
+
system_settings = _load_settings_from_path(system_settings_path)
|
|
114
|
+
update_settings(_settings, system_settings)
|
|
115
|
+
|
|
116
|
+
# Load from env JGT_SETTINGS_SYSTEM if exists
|
|
117
|
+
if 'JGT_SETTINGS_SYSTEM' in os.environ:
|
|
118
|
+
env_settings_system = json.loads(os.environ['JGT_SETTINGS_SYSTEM'])
|
|
119
|
+
update_settings(_settings, env_settings_system)
|
|
120
|
+
|
|
121
|
+
# Load user settings
|
|
122
|
+
user_settings = _load_settings_from_path(home_settings_path)
|
|
123
|
+
update_settings(_settings, user_settings)
|
|
124
|
+
|
|
125
|
+
# Load from env JGT_SETTINGS or JGT_SETTINGS_USER if exists
|
|
126
|
+
if 'JGT_SETTINGS' in os.environ:
|
|
127
|
+
env_settings_user = json.loads(os.environ['JGT_SETTINGS'])
|
|
128
|
+
update_settings(_settings, env_settings_user)
|
|
129
|
+
|
|
130
|
+
if 'JGT_SETTINGS_USER' in os.environ:
|
|
131
|
+
env_settings_user = json.loads(os.environ['JGT_SETTINGS_USER'])
|
|
132
|
+
update_settings(_settings, env_settings_user)
|
|
133
|
+
|
|
134
|
+
# Load current directory settings
|
|
135
|
+
current_settings = _load_settings_from_path(current_settings_path)
|
|
136
|
+
update_settings(_settings, current_settings)
|
|
137
|
+
|
|
138
|
+
current_settings_yaml = _load_settings_from_path_yaml(yaml_current_settings_path)
|
|
139
|
+
update_settings(_settings, current_settings_yaml)
|
|
140
|
+
|
|
141
|
+
jubook_jgt_current_settings_yaml = _load_settings_from_path_yaml(jubook_jgt_yaml_current_settings_path, key='jgt')
|
|
142
|
+
update_settings(_settings, jubook_jgt_current_settings_yaml)
|
|
143
|
+
|
|
144
|
+
jgt_current_settings_yaml = _load_settings_from_path_yaml(jgt_yaml_current_settings_path)
|
|
145
|
+
update_settings(_settings, jgt_current_settings_yaml)
|
|
146
|
+
|
|
147
|
+
# Load custom settings if provided
|
|
148
|
+
if custom_path is not None and custom_path != '':
|
|
149
|
+
custom_settings = {}
|
|
150
|
+
if '.json' in custom_path:
|
|
151
|
+
custom_settings = _load_settings_from_path(custom_path)
|
|
152
|
+
else:
|
|
153
|
+
if '.yml' in custom_path:
|
|
154
|
+
custom_settings = _load_settings_from_path_yaml(custom_path)
|
|
155
|
+
update_settings(_settings, custom_settings)
|
|
156
|
+
|
|
157
|
+
# Load process-level settings from environment
|
|
158
|
+
if 'JGT_SETTINGS_PROCESS' in os.environ:
|
|
159
|
+
env_settings_process = json.loads(os.environ['JGT_SETTINGS_PROCESS'])
|
|
160
|
+
update_settings(_settings, env_settings_process)
|
|
161
|
+
|
|
162
|
+
_settings_loaded(_settings)
|
|
163
|
+
|
|
164
|
+
return _settings
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_settings(custom_path: Optional[str] = None) -> Dict[str, Any]:
|
|
168
|
+
"""
|
|
169
|
+
Get cached settings, loading them if necessary.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
custom_path: Optional custom path to settings file
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Settings dictionary
|
|
176
|
+
"""
|
|
177
|
+
global settings
|
|
178
|
+
if settings is None or len(settings) == 0:
|
|
179
|
+
settings = load_settings(custom_path=custom_path)
|
|
180
|
+
return settings
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def load_arg_default_from_settings(argname: str, default_value: Any, alias: Optional[str] = None,
|
|
184
|
+
from_jgt_env: bool = False, exclude_env_alias: bool = False) -> Any:
|
|
185
|
+
"""
|
|
186
|
+
Load argument default value from settings.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
argname: Argument name to look up
|
|
190
|
+
default_value: Default value if not found
|
|
191
|
+
alias: Alternative name to check
|
|
192
|
+
from_jgt_env: Whether to load from JGT environment
|
|
193
|
+
exclude_env_alias: Whether to exclude alias when loading from env
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Setting value or default
|
|
197
|
+
"""
|
|
198
|
+
global settings
|
|
199
|
+
if settings is None or len(settings) == 0:
|
|
200
|
+
settings = load_settings()
|
|
201
|
+
|
|
202
|
+
_value = settings.get(argname, default_value)
|
|
203
|
+
if alias is not None and _value == default_value:
|
|
204
|
+
_value = settings.get(alias, default_value) # try alias might be used
|
|
205
|
+
|
|
206
|
+
if from_jgt_env:
|
|
207
|
+
_alias = None if exclude_env_alias else alias
|
|
208
|
+
_value = load_arg_from_jgt_env(argname, _alias)
|
|
209
|
+
|
|
210
|
+
return _value
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def load_arg_from_jgt_env(argname: str, alias: Optional[str] = None) -> Optional[str]:
|
|
214
|
+
"""
|
|
215
|
+
Load argument value from JGT environment variables.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
argname: Argument name to look up
|
|
219
|
+
alias: Alternative name to check
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Environment variable value or None
|
|
223
|
+
"""
|
|
224
|
+
_value = None
|
|
225
|
+
# Note: load_env() is from jgtenv, but we'll just check the environment directly
|
|
226
|
+
if argname in os.environ:
|
|
227
|
+
_value = os.getenv(argname, None)
|
|
228
|
+
if alias is not None and _value is None:
|
|
229
|
+
_value = os.getenv(alias, None)
|
|
230
|
+
return _value
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def load_arg_default_from_settings_if_exist(argname: str, alias: Optional[str] = None) -> Optional[Any]:
|
|
234
|
+
"""
|
|
235
|
+
Load argument default from settings only if it exists.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
argname: Argument name to look up
|
|
239
|
+
alias: Alternative name to check
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Setting value or None
|
|
243
|
+
"""
|
|
244
|
+
global settings
|
|
245
|
+
if settings is None or len(settings) == 0:
|
|
246
|
+
settings = load_settings()
|
|
247
|
+
|
|
248
|
+
_value = settings.get(argname, None)
|
|
249
|
+
if alias is not None and _value == None:
|
|
250
|
+
_value = settings.get(alias, None) # try alias might be used
|
|
251
|
+
return _value
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def export_env_if_any(config: Dict[str, Any]) -> None:
|
|
255
|
+
"""Export certain config values to environment variables if they exist."""
|
|
256
|
+
# if has a key : "keep_bid_ask" and if yes and set to "true", export an env variable "JGT_KEEP_BID_ASK" to "1"
|
|
257
|
+
if 'keep_bid_ask' in config and config['keep_bid_ask'] == True:
|
|
258
|
+
os.environ['JGT_KEEP_BID_ASK'] = '1'
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _set_demo_credential(config: Dict[str, Any], demo: bool = False) -> None:
|
|
262
|
+
"""Set demo credentials in config if demo mode is enabled."""
|
|
263
|
+
if demo:
|
|
264
|
+
config["user_id"] = config["user_id_demo"]
|
|
265
|
+
config["password"] = config["password_demo"]
|
|
266
|
+
config["account"] = config["account_demo"]
|
|
267
|
+
config["connection"] = "Demo"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def readconfig(json_config_str: Optional[str] = None, config_file: str = 'config.json',
|
|
271
|
+
export_env: bool = False, config_file_path_env_name: str = 'JGT_CONFIG_PATH',
|
|
272
|
+
config_values_env_name: str = 'JGT_CONFIG', force_read_json: bool = False,
|
|
273
|
+
demo: bool = False, use_demo_json_config: bool = False) -> Dict[str, Any]:
|
|
274
|
+
"""
|
|
275
|
+
Read configuration from various sources.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
json_config_str: JSON string containing config
|
|
279
|
+
config_file: Config file name
|
|
280
|
+
export_env: Whether to export config to environment variables
|
|
281
|
+
config_file_path_env_name: Environment variable name for config file path
|
|
282
|
+
config_values_env_name: Environment variable name for config JSON
|
|
283
|
+
force_read_json: Force reading from default JSON file
|
|
284
|
+
demo: Whether to use demo credentials
|
|
285
|
+
use_demo_json_config: Whether to use demo-specific config file
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Configuration dictionary
|
|
289
|
+
"""
|
|
290
|
+
global _JGT_CONFIG_JSON_SECRET
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
home_dir = os.path.expanduser("~")
|
|
294
|
+
except:
|
|
295
|
+
home_dir = os.environ["HOME"]
|
|
296
|
+
if home_dir == "":
|
|
297
|
+
home_dir = os.environ["HOME"]
|
|
298
|
+
|
|
299
|
+
# demo_config are assumed to be $HOME/.jgt/config_demo.json
|
|
300
|
+
if demo and use_demo_json_config:
|
|
301
|
+
config_file = os.path.join(home_dir, '.jgt/config_demo.json')
|
|
302
|
+
# check if exist, advise and raise exception if not
|
|
303
|
+
if not os.path.exists(config_file):
|
|
304
|
+
print(f"Configuration not found. create : {config_file} or we will try to use the _demo in the usual config.json")
|
|
305
|
+
config = readconfig(force_read_json=True)
|
|
306
|
+
_set_demo_credential(config, demo)
|
|
307
|
+
return config
|
|
308
|
+
|
|
309
|
+
# force_read_json are assumed to be $HOME/.jgt/config.json
|
|
310
|
+
if force_read_json:
|
|
311
|
+
config_file = os.path.join(home_dir, '.jgt/config.json')
|
|
312
|
+
# check if exist, advise and raise exception if not
|
|
313
|
+
if not os.path.exists(config_file):
|
|
314
|
+
raise Exception(f"Configuration not found. create : {config_file})")
|
|
315
|
+
# load and return config
|
|
316
|
+
with open(config_file, 'r') as f:
|
|
317
|
+
config = json.load(f)
|
|
318
|
+
if export_env:
|
|
319
|
+
export_env_if_any(config)
|
|
320
|
+
_set_demo_credential(config, demo)
|
|
321
|
+
return config
|
|
322
|
+
|
|
323
|
+
# Try reading config from JSON string first
|
|
324
|
+
if json_config_str is not None:
|
|
325
|
+
config = json.loads(json_config_str)
|
|
326
|
+
_JGT_CONFIG_JSON_SECRET = json_config_str
|
|
327
|
+
if export_env:
|
|
328
|
+
export_env_if_any(config)
|
|
329
|
+
_set_demo_credential(config, demo)
|
|
330
|
+
return config
|
|
331
|
+
|
|
332
|
+
# Try cached config
|
|
333
|
+
if _JGT_CONFIG_JSON_SECRET is not None:
|
|
334
|
+
config = json.loads(_JGT_CONFIG_JSON_SECRET)
|
|
335
|
+
if export_env:
|
|
336
|
+
export_env_if_any(config)
|
|
337
|
+
_set_demo_credential(config, demo)
|
|
338
|
+
return config
|
|
339
|
+
|
|
340
|
+
config = None
|
|
341
|
+
|
|
342
|
+
# if file does not exist try set the path to the file in the HOME
|
|
343
|
+
if not os.path.exists(config_file):
|
|
344
|
+
config_file = os.path.join(home_dir, config_file)
|
|
345
|
+
|
|
346
|
+
if os.path.exists(config_file):
|
|
347
|
+
with open(config_file, 'r') as f:
|
|
348
|
+
config = json.load(f)
|
|
349
|
+
if export_env:
|
|
350
|
+
export_env_if_any(config)
|
|
351
|
+
_set_demo_credential(config, demo)
|
|
352
|
+
return config
|
|
353
|
+
else:
|
|
354
|
+
config_file = os.path.join(home_dir, config_file)
|
|
355
|
+
if os.path.isfile(config_file):
|
|
356
|
+
with open(config_file, 'r') as f:
|
|
357
|
+
config = json.load(f)
|
|
358
|
+
else:
|
|
359
|
+
# If config file still not found, try reading from environment variable
|
|
360
|
+
config_json_str = os.getenv('JGT_CONFIG_JSON_SECRET')
|
|
361
|
+
if config_json_str:
|
|
362
|
+
config = json.loads(config_json_str)
|
|
363
|
+
if export_env:
|
|
364
|
+
export_env_if_any(config)
|
|
365
|
+
_set_demo_credential(config, demo)
|
|
366
|
+
return config
|
|
367
|
+
|
|
368
|
+
# if file dont exist, try loading from env var JGT_CONFIG
|
|
369
|
+
if not os.path.exists(config_file):
|
|
370
|
+
config_json_str = os.getenv(config_values_env_name)
|
|
371
|
+
|
|
372
|
+
if config_json_str:
|
|
373
|
+
config = json.loads(config_json_str)
|
|
374
|
+
if export_env:
|
|
375
|
+
export_env_if_any(config)
|
|
376
|
+
else:
|
|
377
|
+
# if not found, try loading from env var JGT_CONFIG_PATH
|
|
378
|
+
config_file = os.getenv(config_file_path_env_name)
|
|
379
|
+
if config_file:
|
|
380
|
+
with open(config_file, 'r') as f:
|
|
381
|
+
config = json.load(f)
|
|
382
|
+
if export_env:
|
|
383
|
+
export_env_if_any(config)
|
|
384
|
+
|
|
385
|
+
# Read config file
|
|
386
|
+
if config is None:
|
|
387
|
+
if config_file is not None and os.path.exists(config_file):
|
|
388
|
+
with open(config_file, 'r') as file:
|
|
389
|
+
config = json.load(file)
|
|
390
|
+
|
|
391
|
+
if config is None:
|
|
392
|
+
# Last attempt to read
|
|
393
|
+
try:
|
|
394
|
+
another_config = "config.json"
|
|
395
|
+
if not os.path.exists(another_config):
|
|
396
|
+
another_config = os.path.join(os.path.expanduser('~'), '.jgt', 'config.json')
|
|
397
|
+
|
|
398
|
+
if not os.path.exists(another_config):
|
|
399
|
+
another_config = "/etc/jgt/config.json"
|
|
400
|
+
with open(another_config, 'r') as file:
|
|
401
|
+
config = json.load(file)
|
|
402
|
+
except:
|
|
403
|
+
pass
|
|
404
|
+
|
|
405
|
+
if config is None:
|
|
406
|
+
try:
|
|
407
|
+
with open("/home/jgi/.jgt/config.json", 'r') as file:
|
|
408
|
+
config = json.load(file)
|
|
409
|
+
except:
|
|
410
|
+
pass
|
|
411
|
+
if config is None:
|
|
412
|
+
try:
|
|
413
|
+
with open("/etc/jgt/config.json", 'r') as file:
|
|
414
|
+
config = json.load(file)
|
|
415
|
+
except:
|
|
416
|
+
pass
|
|
417
|
+
|
|
418
|
+
if config is None:
|
|
419
|
+
raise Exception(f"Configuration not found. Please provide a config file or set the JGT_CONFIG environment variable to the JSON config string. (config_file={config_file})")
|
|
420
|
+
|
|
421
|
+
if export_env:
|
|
422
|
+
export_env_if_any(config)
|
|
423
|
+
_set_demo_credential(config, demo)
|
|
424
|
+
return config
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# Utility Functions
|
|
428
|
+
|
|
429
|
+
def str_to_datetime(date_str: str) -> Optional[datetime]:
|
|
430
|
+
"""
|
|
431
|
+
Convert string to datetime using multiple format attempts.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
date_str: Date string to parse
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
datetime object or None if parsing fails
|
|
438
|
+
"""
|
|
439
|
+
formats = ['%m.%d.%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y/%m/%d', '%Y-%m-%d']
|
|
440
|
+
|
|
441
|
+
for fmt in formats:
|
|
442
|
+
try:
|
|
443
|
+
return datetime.strptime(date_str, fmt)
|
|
444
|
+
except ValueError:
|
|
445
|
+
continue
|
|
446
|
+
return None
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def print_exception(exception: Exception) -> None:
|
|
450
|
+
"""Print exception with traceback."""
|
|
451
|
+
print("Exception: {0}\n{1}".format(exception, traceback.format_exc()))
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def is_market_open(current_time: Optional[datetime] = None, exit_cli_if_closed: bool = False,
|
|
455
|
+
market_closed_callback: Optional[callable] = None) -> bool:
|
|
456
|
+
"""
|
|
457
|
+
Check if forex market is currently open.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
current_time: Time to check (defaults to now)
|
|
461
|
+
exit_cli_if_closed: Whether to exit if market is closed (not recommended for library use)
|
|
462
|
+
market_closed_callback: Callback to call if market is closed
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
True if market is open
|
|
466
|
+
"""
|
|
467
|
+
if current_time is None:
|
|
468
|
+
current_time = datetime.utcnow()
|
|
469
|
+
|
|
470
|
+
# Define market open and close times
|
|
471
|
+
market_open_time = time(21, 0) # 21:00 UTC
|
|
472
|
+
market_close_time = time(21, 15) # 21:15 UTC
|
|
473
|
+
|
|
474
|
+
# Get the current day of the week (0=Monday, 6=Sunday)
|
|
475
|
+
current_day = current_time.weekday()
|
|
476
|
+
|
|
477
|
+
current_time_utc = current_time.time()
|
|
478
|
+
# Check if the market is open
|
|
479
|
+
if current_day == 6: # Sunday
|
|
480
|
+
if current_time_utc >= market_open_time:
|
|
481
|
+
return True
|
|
482
|
+
elif current_day == 4: # Friday
|
|
483
|
+
if current_time_utc < market_close_time:
|
|
484
|
+
return True
|
|
485
|
+
elif 0 <= current_day < 4: # Monday to Thursday
|
|
486
|
+
return True
|
|
487
|
+
|
|
488
|
+
if market_closed_callback is not None:
|
|
489
|
+
market_closed_callback()
|
|
490
|
+
if exit_cli_if_closed:
|
|
491
|
+
print("Market is closed.")
|
|
492
|
+
sys.exit(1) # Simplified exit code for library use
|
|
493
|
+
return False
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def dt_from_last_week_as_datetime() -> datetime:
|
|
497
|
+
"""Get datetime from last week."""
|
|
498
|
+
today = datetime.now()
|
|
499
|
+
last_week = today - timedelta(days=7)
|
|
500
|
+
return last_week
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def dt_from_last_week_as_string_fxformat() -> str:
|
|
504
|
+
"""Get datetime from last week as FX format string."""
|
|
505
|
+
last_week = dt_from_last_week_as_datetime()
|
|
506
|
+
_str = last_week.strftime('%m.%d.%Y')
|
|
507
|
+
return _str + " 00:00:00"
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def read_fx_str_from_config(demo: bool = False) -> Tuple[str, str, str, str, str]:
|
|
511
|
+
"""
|
|
512
|
+
Read FX connection parameters from config.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
demo: Whether to use demo credentials
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
Tuple of (user_id, password, url, connection, account)
|
|
519
|
+
"""
|
|
520
|
+
config = readconfig(demo=demo)
|
|
521
|
+
if config["connection"] == "Real" and demo: # Make sure we have our demo credentials
|
|
522
|
+
_set_demo_credential(config, True)
|
|
523
|
+
str_user_id = config['user_id']
|
|
524
|
+
str_password = config['password']
|
|
525
|
+
str_url = config['url']
|
|
526
|
+
str_connection = "Real" if not demo else "Demo"
|
|
527
|
+
str_account = config['account']
|
|
528
|
+
return str_user_id, str_password, str_url, str_connection, str_account
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
# Simple API wrappers for external packages
|
|
532
|
+
|
|
533
|
+
def get_config(demo: bool = False, export_env: bool = False) -> Dict[str, Any]:
|
|
534
|
+
"""
|
|
535
|
+
Simple configuration loader for external packages.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
demo: Whether to use demo credentials
|
|
539
|
+
export_env: Whether to export config to environment variables
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
Configuration dictionary
|
|
543
|
+
"""
|
|
544
|
+
return readconfig(demo=demo, export_env=export_env)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def get_setting(key: str, default: Any = None, custom_path: Optional[str] = None) -> Any:
|
|
548
|
+
"""
|
|
549
|
+
Get a single setting value.
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
key: Setting key to retrieve
|
|
553
|
+
default: Default value if key not found
|
|
554
|
+
custom_path: Optional custom path to settings file
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
Setting value or default
|
|
558
|
+
"""
|
|
559
|
+
settings = get_settings(custom_path=custom_path)
|
|
560
|
+
return settings.get(key, default)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def setup_environment(demo: bool = False, custom_settings_path: Optional[str] = None) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
564
|
+
"""
|
|
565
|
+
One-call setup for external packages.
|
|
566
|
+
Sets up configuration and settings, exports environment variables.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
demo: Whether to use demo credentials
|
|
570
|
+
custom_settings_path: Optional custom path to settings file
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
Tuple of (config_dict, settings_dict)
|
|
574
|
+
"""
|
|
575
|
+
config = readconfig(demo=demo, export_env=True)
|
|
576
|
+
settings = get_settings(custom_path=custom_settings_path)
|
|
577
|
+
return config, settings
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def get_config_value(key: str, default: Any = None, demo: bool = False) -> Any:
|
|
581
|
+
"""
|
|
582
|
+
Get a single configuration value.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
key: Configuration key to retrieve
|
|
586
|
+
default: Default value if key not found
|
|
587
|
+
demo: Whether to use demo credentials
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
Configuration value or default
|
|
591
|
+
"""
|
|
592
|
+
config = readconfig(demo=demo)
|
|
593
|
+
return config.get(key, default)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def is_demo_mode() -> bool:
|
|
597
|
+
"""
|
|
598
|
+
Check if running in demo mode based on current configuration.
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
True if demo mode is active
|
|
602
|
+
"""
|
|
603
|
+
try:
|
|
604
|
+
config = readconfig()
|
|
605
|
+
return config.get('connection', '').lower() == 'demo'
|
|
606
|
+
except:
|
|
607
|
+
return False
|
jgtcore/example.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Example usage of jgtcore library
|
|
5
|
+
|
|
6
|
+
This demonstrates how to use the extracted core functions
|
|
7
|
+
from jgtutils without CLI dependencies.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
# Add the current directory to Python path so we can import jgtcore
|
|
14
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
15
|
+
|
|
16
|
+
import core as jgtcore
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
"""Example usage of jgtcore functions."""
|
|
21
|
+
|
|
22
|
+
print("=== jgtcore Example Usage ===\n")
|
|
23
|
+
|
|
24
|
+
# 1. Simple setup for external packages
|
|
25
|
+
print("1. Setting up environment...")
|
|
26
|
+
try:
|
|
27
|
+
config, settings = jgtcore.setup_environment(demo=True)
|
|
28
|
+
print(f" ✓ Config loaded with {len(config)} keys")
|
|
29
|
+
print(f" ✓ Settings loaded with {len(settings)} keys")
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f" ✗ Setup failed: {e}")
|
|
32
|
+
|
|
33
|
+
# 2. Get specific configuration values
|
|
34
|
+
print("\n2. Getting configuration values...")
|
|
35
|
+
try:
|
|
36
|
+
url = jgtcore.get_config_value('url', 'default-url')
|
|
37
|
+
print(f" URL: {url}")
|
|
38
|
+
|
|
39
|
+
demo_mode = jgtcore.is_demo_mode()
|
|
40
|
+
print(f" Demo mode: {demo_mode}")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f" ✗ Config access failed: {e}")
|
|
43
|
+
|
|
44
|
+
# 3. Get specific settings
|
|
45
|
+
print("\n3. Getting settings...")
|
|
46
|
+
try:
|
|
47
|
+
timeframe = jgtcore.get_setting('timeframe', 'H1')
|
|
48
|
+
instrument = jgtcore.get_setting('instrument', 'EUR/USD')
|
|
49
|
+
print(f" Default timeframe: {timeframe}")
|
|
50
|
+
print(f" Default instrument: {instrument}")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f" ✗ Settings access failed: {e}")
|
|
53
|
+
|
|
54
|
+
# 4. Utility functions
|
|
55
|
+
print("\n4. Utility functions...")
|
|
56
|
+
|
|
57
|
+
# Date/time utilities
|
|
58
|
+
last_week = jgtcore.dt_from_last_week_as_string_fxformat()
|
|
59
|
+
print(f" Last week (FX format): {last_week}")
|
|
60
|
+
|
|
61
|
+
# Market status
|
|
62
|
+
market_open = jgtcore.is_market_open()
|
|
63
|
+
print(f" Market open: {market_open}")
|
|
64
|
+
|
|
65
|
+
# String to datetime conversion
|
|
66
|
+
test_date = "2024-01-15 10:30:00"
|
|
67
|
+
parsed_date = jgtcore.str_to_datetime(test_date)
|
|
68
|
+
print(f" Parsed date '{test_date}': {parsed_date}")
|
|
69
|
+
|
|
70
|
+
# 5. Trading connection parameters
|
|
71
|
+
print("\n5. Trading connection...")
|
|
72
|
+
try:
|
|
73
|
+
user_id, password, url, connection, account = jgtcore.read_fx_str_from_config(demo=True)
|
|
74
|
+
print(f" Connection: {connection}")
|
|
75
|
+
print(f" URL: {url}")
|
|
76
|
+
print(f" Account: {account}")
|
|
77
|
+
print(f" User ID: {user_id[:3]}... (hidden)")
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f" ✗ Trading config failed: {e}")
|
|
80
|
+
|
|
81
|
+
print("\n=== Example completed ===")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
main()
|
jgtcore/test_core.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Test script for jgtcore library
|
|
5
|
+
|
|
6
|
+
Tests the core functionality extracted from jgtutils
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
12
|
+
import json
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
# Add jgtcore to path
|
|
16
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
17
|
+
|
|
18
|
+
import core
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_str_to_datetime():
|
|
22
|
+
"""Test string to datetime conversion."""
|
|
23
|
+
print("Testing str_to_datetime...")
|
|
24
|
+
|
|
25
|
+
test_cases = [
|
|
26
|
+
("2024-01-15 10:30:00", True),
|
|
27
|
+
("01.15.2024 10:30:00", True),
|
|
28
|
+
("2024/01/15", True),
|
|
29
|
+
("invalid-date", False),
|
|
30
|
+
("", False)
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
for date_str, should_succeed in test_cases:
|
|
34
|
+
result = core.str_to_datetime(date_str)
|
|
35
|
+
if should_succeed:
|
|
36
|
+
assert result is not None, f"Expected {date_str} to parse successfully"
|
|
37
|
+
assert isinstance(result, datetime), f"Expected datetime object for {date_str}"
|
|
38
|
+
print(f" ✓ {date_str} -> {result}")
|
|
39
|
+
else:
|
|
40
|
+
assert result is None, f"Expected {date_str} to fail parsing"
|
|
41
|
+
print(f" ✓ {date_str} -> None (as expected)")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_market_open():
|
|
45
|
+
"""Test market open functionality."""
|
|
46
|
+
print("Testing is_market_open...")
|
|
47
|
+
|
|
48
|
+
# Test with current time
|
|
49
|
+
result = core.is_market_open()
|
|
50
|
+
print(f" Current market status: {'Open' if result else 'Closed'}")
|
|
51
|
+
|
|
52
|
+
# Test with specific times (without exit behavior)
|
|
53
|
+
test_time = datetime(2024, 1, 15, 10, 0) # Monday 10:00 UTC
|
|
54
|
+
result = core.is_market_open(test_time)
|
|
55
|
+
print(f" Monday 10:00 UTC: {'Open' if result else 'Closed'}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_datetime_helpers():
|
|
59
|
+
"""Test datetime helper functions."""
|
|
60
|
+
print("Testing datetime helpers...")
|
|
61
|
+
|
|
62
|
+
last_week_dt = core.dt_from_last_week_as_datetime()
|
|
63
|
+
last_week_str = core.dt_from_last_week_as_string_fxformat()
|
|
64
|
+
|
|
65
|
+
assert isinstance(last_week_dt, datetime), "Expected datetime object"
|
|
66
|
+
assert isinstance(last_week_str, str), "Expected string"
|
|
67
|
+
assert "00:00:00" in last_week_str, "Expected time suffix in FX format"
|
|
68
|
+
|
|
69
|
+
print(f" ✓ Last week datetime: {last_week_dt}")
|
|
70
|
+
print(f" ✓ Last week FX format: {last_week_str}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_settings_with_temp_file():
|
|
74
|
+
"""Test settings loading with temporary files."""
|
|
75
|
+
print("Testing settings loading...")
|
|
76
|
+
|
|
77
|
+
# Create temporary settings file
|
|
78
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
79
|
+
test_settings = {
|
|
80
|
+
"instrument": "GBP/USD",
|
|
81
|
+
"timeframe": "M15",
|
|
82
|
+
"test_value": 42
|
|
83
|
+
}
|
|
84
|
+
json.dump(test_settings, f)
|
|
85
|
+
temp_path = f.name
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Test loading settings from custom path
|
|
89
|
+
settings = core.load_settings(custom_path=temp_path)
|
|
90
|
+
|
|
91
|
+
assert "instrument" in settings, "Expected instrument in settings"
|
|
92
|
+
assert settings["instrument"] == "GBP/USD", "Expected correct instrument value"
|
|
93
|
+
assert settings["timeframe"] == "M15", "Expected correct timeframe value"
|
|
94
|
+
assert settings["test_value"] == 42, "Expected correct test value"
|
|
95
|
+
|
|
96
|
+
print(f" ✓ Loaded settings: {len(settings)} keys")
|
|
97
|
+
print(f" ✓ Instrument: {settings['instrument']}")
|
|
98
|
+
print(f" ✓ Timeframe: {settings['timeframe']}")
|
|
99
|
+
|
|
100
|
+
# Test get_settings caching
|
|
101
|
+
cached_settings = core.get_settings()
|
|
102
|
+
print(f" ✓ Cached settings: {len(cached_settings)} keys")
|
|
103
|
+
|
|
104
|
+
finally:
|
|
105
|
+
# Clean up
|
|
106
|
+
os.unlink(temp_path)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_config_loading():
|
|
110
|
+
"""Test configuration loading."""
|
|
111
|
+
print("Testing configuration loading...")
|
|
112
|
+
|
|
113
|
+
# Create temporary config file
|
|
114
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
115
|
+
test_config = {
|
|
116
|
+
"url": "https://test.example.com",
|
|
117
|
+
"user_id": "test_user",
|
|
118
|
+
"password": "test_pass",
|
|
119
|
+
"account": "test_account",
|
|
120
|
+
"connection": "Demo",
|
|
121
|
+
"user_id_demo": "demo_user",
|
|
122
|
+
"password_demo": "demo_pass",
|
|
123
|
+
"account_demo": "demo_account"
|
|
124
|
+
}
|
|
125
|
+
json.dump(test_config, f)
|
|
126
|
+
temp_path = f.name
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
# Test direct JSON string loading
|
|
130
|
+
config_str = json.dumps(test_config)
|
|
131
|
+
config = core.readconfig(json_config_str=config_str)
|
|
132
|
+
|
|
133
|
+
assert config["url"] == "https://test.example.com", "Expected correct URL"
|
|
134
|
+
assert config["user_id"] == "test_user", "Expected correct user ID"
|
|
135
|
+
|
|
136
|
+
print(f" ✓ Config from JSON string: {len(config)} keys")
|
|
137
|
+
|
|
138
|
+
# Test demo mode
|
|
139
|
+
demo_config = core.readconfig(json_config_str=config_str, demo=True)
|
|
140
|
+
assert demo_config["connection"] == "Demo", "Expected demo connection"
|
|
141
|
+
assert demo_config["user_id"] == "demo_user", "Expected demo user ID"
|
|
142
|
+
|
|
143
|
+
print(f" ✓ Demo mode: {demo_config['connection']}")
|
|
144
|
+
|
|
145
|
+
finally:
|
|
146
|
+
# Clean up
|
|
147
|
+
os.unlink(temp_path)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_api_wrappers():
|
|
151
|
+
"""Test simple API wrapper functions."""
|
|
152
|
+
print("Testing API wrappers...")
|
|
153
|
+
|
|
154
|
+
# Create test config
|
|
155
|
+
test_config = {
|
|
156
|
+
"url": "https://api.example.com",
|
|
157
|
+
"connection": "Demo",
|
|
158
|
+
"test_key": "test_value"
|
|
159
|
+
}
|
|
160
|
+
config_str = json.dumps(test_config)
|
|
161
|
+
|
|
162
|
+
# Test get_config
|
|
163
|
+
config = core.get_config()
|
|
164
|
+
print(f" ✓ get_config returned {len(config)} keys")
|
|
165
|
+
|
|
166
|
+
# Test get_config_value with JSON string
|
|
167
|
+
os.environ['JGT_CONFIG_JSON_SECRET'] = config_str
|
|
168
|
+
value = core.get_config_value('test_key', 'default')
|
|
169
|
+
# Clean up environment
|
|
170
|
+
del os.environ['JGT_CONFIG_JSON_SECRET']
|
|
171
|
+
|
|
172
|
+
print(f" ✓ get_config_value: {value}")
|
|
173
|
+
|
|
174
|
+
# Test is_demo_mode
|
|
175
|
+
demo_mode = core.is_demo_mode()
|
|
176
|
+
print(f" ✓ is_demo_mode: {demo_mode}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def main():
|
|
180
|
+
"""Run all tests."""
|
|
181
|
+
print("=== jgtcore Test Suite ===\n")
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
test_str_to_datetime()
|
|
185
|
+
print()
|
|
186
|
+
|
|
187
|
+
test_market_open()
|
|
188
|
+
print()
|
|
189
|
+
|
|
190
|
+
test_datetime_helpers()
|
|
191
|
+
print()
|
|
192
|
+
|
|
193
|
+
test_settings_with_temp_file()
|
|
194
|
+
print()
|
|
195
|
+
|
|
196
|
+
test_config_loading()
|
|
197
|
+
print()
|
|
198
|
+
|
|
199
|
+
test_api_wrappers()
|
|
200
|
+
|
|
201
|
+
print("\n=== All tests passed! ===")
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
print(f"\n❌ Test failed: {e}")
|
|
205
|
+
core.print_exception(e)
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Guillaume Descoteaux-Isabelle
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: jgtcore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core library for JGT utilities - configuration, settings, and utility functions
|
|
5
|
+
Author-email: Guillaume Isabelle <jgi@jgwill.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jgwill/jgtcore
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/jgwill/jgtcore/issues
|
|
9
|
+
Project-URL: Documentation, https://github.com/jgwill/jgtcore#readme
|
|
10
|
+
Project-URL: Source Code, https://github.com/jgwill/jgtcore
|
|
11
|
+
Keywords: configuration,settings,utilities,jgt
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Requires-Python: >=3.7
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: python-dotenv
|
|
24
|
+
Requires-Dist: ruamel.yaml (>=0.18.6)
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest ; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov ; extra == 'dev'
|
|
28
|
+
Requires-Dist: black ; extra == 'dev'
|
|
29
|
+
Requires-Dist: isort ; extra == 'dev'
|
|
30
|
+
Requires-Dist: flake8 ; extra == 'dev'
|
|
31
|
+
|
|
32
|
+
# jgtcore
|
|
33
|
+
|
|
34
|
+
Core library for JGT utilities - configuration, settings, and utility functions.
|
|
35
|
+
|
|
36
|
+
This is a pure Python library extracted from `jgtutils` to provide clean programmatic access to JGT configuration and settings without CLI dependencies.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install jgtcore
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
### Simple Configuration Access
|
|
47
|
+
```python
|
|
48
|
+
import jgtcore
|
|
49
|
+
|
|
50
|
+
# Load configuration
|
|
51
|
+
config = jgtcore.get_config()
|
|
52
|
+
demo_config = jgtcore.get_config(demo=True)
|
|
53
|
+
|
|
54
|
+
# Get specific config values
|
|
55
|
+
user_id = jgtcore.get_config_value('user_id')
|
|
56
|
+
quotes_count = jgtcore.get_config_value('quotes_count', 1000)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Settings Management
|
|
60
|
+
```python
|
|
61
|
+
import jgtcore
|
|
62
|
+
|
|
63
|
+
# Get all settings
|
|
64
|
+
settings = jgtcore.get_settings()
|
|
65
|
+
|
|
66
|
+
# Get specific setting with default
|
|
67
|
+
instrument = jgtcore.get_setting('instrument', 'EUR/USD')
|
|
68
|
+
timeframes = jgtcore.get_setting('_timeframes', 'D1')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Environment Setup
|
|
72
|
+
```python
|
|
73
|
+
import jgtcore
|
|
74
|
+
|
|
75
|
+
# One-call environment setup
|
|
76
|
+
config, settings = jgtcore.setup_environment(demo=True)
|
|
77
|
+
|
|
78
|
+
# Check demo mode
|
|
79
|
+
if jgtcore.is_demo_mode():
|
|
80
|
+
print("Running in demo mode")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Advanced Usage
|
|
84
|
+
```python
|
|
85
|
+
from jgtcore import readconfig, load_settings
|
|
86
|
+
|
|
87
|
+
# Load with specific options
|
|
88
|
+
config = readconfig(demo=True, export_env=True)
|
|
89
|
+
settings = load_settings(custom_path="/path/to/settings.json")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuration Files
|
|
93
|
+
|
|
94
|
+
- **config.json**: Trading credentials and connection settings
|
|
95
|
+
- **settings.json**: Application settings and preferences
|
|
96
|
+
|
|
97
|
+
See `examples/` directory for sample configuration files.
|
|
98
|
+
|
|
99
|
+
## File Locations
|
|
100
|
+
|
|
101
|
+
### config.json lookup order:
|
|
102
|
+
1. Current directory: `config.json`
|
|
103
|
+
2. User home: `~/.jgt/config.json`
|
|
104
|
+
3. System: `/etc/jgt/config.json`
|
|
105
|
+
4. Environment variables: `JGT_CONFIG`, `JGT_CONFIG_PATH`
|
|
106
|
+
|
|
107
|
+
### settings.json lookup order:
|
|
108
|
+
1. System: `/etc/jgt/settings.json`
|
|
109
|
+
2. User home: `~/.jgt/settings.json`
|
|
110
|
+
3. Current directory: `.jgt/settings.json`
|
|
111
|
+
4. YAML variants: `jgt.yml`, `_config.yml`
|
|
112
|
+
5. Environment variables: `JGT_SETTINGS`
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT License
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
jgtcore/__init__.py,sha256=dqVduCjlUX03mQIORp8Jf_NZuRhaQ4l8Eb76pNAIWfs,2159
|
|
2
|
+
jgtcore/core.py,sha256=hOfj_SK8yBMvroWmSQSm9hcGEFb5l7g_m-LEIuCcHAQ,21409
|
|
3
|
+
jgtcore/example.py,sha256=FnHo1NR0zFllAhk4Z590w-ZtlXdA9QH0oht4MPotViY,2631
|
|
4
|
+
jgtcore/test_core.py,sha256=xqCvRNa2MQrSoaLa6ACLZrTvkf4Bxx_RIk5fwlflBjo,6432
|
|
5
|
+
jgtcore-0.1.0.dist-info/LICENSE,sha256=uCA_HysYVK9qIMbDEFjcDYhCoKTg19AJhKhM71EKSGA,1086
|
|
6
|
+
jgtcore-0.1.0.dist-info/METADATA,sha256=GCdtDp84Sr5YOO56rknyrUSyjD3lXl3kTkCU7Pt4NgI,3234
|
|
7
|
+
jgtcore-0.1.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
|
8
|
+
jgtcore-0.1.0.dist-info/top_level.txt,sha256=Chh0dpqpr0NjebGncKO5fsd_FaQzPyUb0Q0WTTfuPsA,8
|
|
9
|
+
jgtcore-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jgtcore
|