Skip to content

Commit 3d4158e

Browse files
author
rob
committed
Merge branch 'develop'
2 parents 3876877 + 6efd5ad commit 3d4158e

19 files changed

+792
-731
lines changed

data/futures/csvconfig/spreadcosts.csv

+582-582
Large diffs are not rendered by default.

examples/introduction/simplesystem.py

+27-27
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
my_system = System([my_rules], data)
4040
print(my_system)
4141

42-
print(my_system.rules.get_raw_forecast("EDOLLAR", "ewmac").tail(5))
42+
print(my_system.rules.get_raw_forecast("SOFR", "ewmac").tail(5))
4343
"""
4444
Define a TradingRule
4545
"""
@@ -59,7 +59,7 @@
5959
print(my_rules.trading_rules()["ewmac32"])
6060

6161
my_system = System([my_rules], data)
62-
my_system.rules.get_raw_forecast("EDOLLAR", "ewmac32").tail(5)
62+
my_system.rules.get_raw_forecast("SOFR", "ewmac32").tail(5)
6363

6464
from sysdata.config.configdata import Config
6565

@@ -69,25 +69,25 @@
6969
empty_rules = Rules()
7070
my_config.trading_rules = dict(ewmac8=ewmac_8, ewmac32=ewmac_32)
7171
my_system = System([empty_rules], data, my_config)
72-
my_system.rules.get_raw_forecast("EDOLLAR", "ewmac32").tail(5)
72+
my_system.rules.get_raw_forecast("SOFR", "ewmac32").tail(5)
7373

7474
from systems.forecast_scale_cap import ForecastScaleCap
7575

7676
# we can estimate these ourselves
77-
my_config.instruments = ["US10", "EDOLLAR", "CORN", "SP500_micro"]
77+
my_config.instruments = ["US10", "SOFR", "CORN", "SP500_micro"]
7878
my_config.use_forecast_scale_estimates = True
7979

8080
fcs = ForecastScaleCap()
8181
my_system = System([fcs, my_rules], data, my_config)
8282
my_config.forecast_scalar_estimate["pool_instruments"] = False
83-
print(my_system.forecastScaleCap.get_forecast_scalar("EDOLLAR", "ewmac32").tail(5))
83+
print(my_system.forecastScaleCap.get_forecast_scalar("SOFR", "ewmac32").tail(5))
8484

8585
# or we can use the values from the book
8686
my_config.forecast_scalars = dict(ewmac8=5.3, ewmac32=2.65)
8787
my_config.use_forecast_scale_estimates = False
8888
fcs = ForecastScaleCap()
8989
my_system = System([fcs, my_rules], data, my_config)
90-
print(my_system.forecastScaleCap.get_capped_forecast("EDOLLAR", "ewmac32").tail(5))
90+
print(my_system.forecastScaleCap.get_capped_forecast("SOFR", "ewmac32").tail(5))
9191
"""
9292
combine some rules
9393
"""
@@ -97,8 +97,8 @@
9797
# defaults
9898
combiner = ForecastCombine()
9999
my_system = System([fcs, my_rules, combiner], data, my_config)
100-
print(my_system.combForecast.get_forecast_weights("EDOLLAR").tail(5))
101-
print(my_system.combForecast.get_forecast_diversification_multiplier("EDOLLAR").tail(5))
100+
print(my_system.combForecast.get_forecast_weights("SOFR").tail(5))
101+
print(my_system.combForecast.get_forecast_diversification_multiplier("SOFR").tail(5))
102102

103103
# estimates:
104104
from systems.accounts.accounts_stage import Account
@@ -135,7 +135,7 @@
135135
my_system = System(
136136
[fcs, empty_rules, combiner, raw_data, position_size], data, my_config
137137
) # no need for accounts if no estimation done
138-
my_system.combForecast.get_combined_forecast("EDOLLAR").tail(5)
138+
my_system.combForecast.get_combined_forecast("SOFR").tail(5)
139139

140140
# size positions
141141

@@ -146,13 +146,13 @@
146146

147147
my_system = System([fcs, my_rules, combiner, possizer, raw_data], data, my_config)
148148

149-
print(my_system.positionSize.get_price_volatility("EDOLLAR").tail(5))
150-
print(my_system.positionSize.get_block_value("EDOLLAR").tail(5))
151-
print(my_system.positionSize.get_underlying_price("EDOLLAR"))
152-
print(my_system.positionSize.get_instrument_value_vol("EDOLLAR").tail(5))
153-
print(my_system.positionSize.get_volatility_scalar("EDOLLAR").tail(5))
149+
print(my_system.positionSize.get_price_volatility("SOFR").tail(5))
150+
print(my_system.positionSize.get_block_value("SOFR").tail(5))
151+
print(my_system.positionSize.get_underlying_price("SOFR"))
152+
print(my_system.positionSize.get_instrument_value_vol("SOFR").tail(5))
153+
print(my_system.positionSize.get_volatility_scalar("SOFR").tail(5))
154154
print(my_system.positionSize.get_vol_target_dict())
155-
print(my_system.positionSize.get_subsystem_position("EDOLLAR").tail(5))
155+
print(my_system.positionSize.get_subsystem_position("SOFR").tail(5))
156156

157157
# portfolio - estimated
158158
from systems.portfolio import Portfolios
@@ -178,14 +178,14 @@
178178
portfolio = Portfolios()
179179
my_config.use_instrument_weight_estimates = False
180180
my_config.use_instrument_div_mult_estimates = False
181-
my_config.instrument_weights = dict(US10=0.1, EDOLLAR=0.4, CORN=0.3, SP500=0.2)
181+
my_config.instrument_weights = dict(US10=0.1, SOFR=0.4, CORN=0.3, SP500=0.2)
182182
my_config.instrument_div_multiplier = 1.5
183183

184184
my_system = System(
185185
[fcs, my_rules, combiner, possizer, portfolio, raw_data], data, my_config
186186
)
187187

188-
print(my_system.portfolio.get_notional_position("EDOLLAR").tail(5))
188+
print(my_system.portfolio.get_notional_position("SOFR").tail(5))
189189
"""
190190
Have we made some dosh?
191191
"""
@@ -207,7 +207,7 @@
207207
my_config = Config(
208208
dict(
209209
trading_rules=dict(ewmac8=ewmac_8, ewmac32=ewmac_32),
210-
instrument_weights=dict(US10=0.1, EDOLLAR=0.4, CORN=0.3, SP500_micro=0.2),
210+
instrument_weights=dict(US10=0.1, SOFR=0.4, CORN=0.3, SP500_micro=0.2),
211211
instrument_div_multiplier=1.5,
212212
forecast_scalars=dict(ewmac8=5.3, ewmac32=2.65),
213213
forecast_weights=dict(ewmac8=0.5, ewmac32=0.5),
@@ -231,7 +231,7 @@
231231
data,
232232
my_config,
233233
)
234-
print(my_system.portfolio.get_notional_position("EDOLLAR").tail(5))
234+
print(my_system.portfolio.get_notional_position("SOFR").tail(5))
235235
"""
236236
... or to import one
237237
"""
@@ -250,13 +250,13 @@
250250
data,
251251
my_config,
252252
)
253-
print(my_system.rules.get_raw_forecast("EDOLLAR", "ewmac32").tail(5))
254-
print(my_system.rules.get_raw_forecast("EDOLLAR", "ewmac8").tail(5))
255-
print(my_system.forecastScaleCap.get_capped_forecast("EDOLLAR", "ewmac32").tail(5))
256-
print(my_system.forecastScaleCap.get_forecast_scalar("EDOLLAR", "ewmac32"))
257-
print(my_system.combForecast.get_combined_forecast("EDOLLAR").tail(5))
258-
print(my_system.combForecast.get_forecast_weights("EDOLLAR").tail(5))
253+
print(my_system.rules.get_raw_forecast("SOFR", "ewmac32").tail(5))
254+
print(my_system.rules.get_raw_forecast("SOFR", "ewmac8").tail(5))
255+
print(my_system.forecastScaleCap.get_capped_forecast("SOFR", "ewmac32").tail(5))
256+
print(my_system.forecastScaleCap.get_forecast_scalar("SOFR", "ewmac32"))
257+
print(my_system.combForecast.get_combined_forecast("SOFR").tail(5))
258+
print(my_system.combForecast.get_forecast_weights("SOFR").tail(5))
259259

260-
print(my_system.positionSize.get_subsystem_position("EDOLLAR").tail(5))
260+
print(my_system.positionSize.get_subsystem_position("SOFR").tail(5))
261261

262-
print(my_system.portfolio.get_notional_position("EDOLLAR").tail(5))
262+
print(my_system.portfolio.get_notional_position("SOFR").tail(5))

sysbrokers/IB/client/ib_client.py

+14-22
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
IBInstrumentIdentity,
1212
)
1313

14-
from syscore.constants import arg_not_supplied
14+
from syscore.constants import arg_not_supplied, missing_contract
1515
from syscore.cache import Cache
1616
from syscore.exceptions import missingContract
1717

@@ -103,7 +103,7 @@ def log(self):
103103
return self._log
104104

105105
def error_handler(
106-
self, reqid: int, error_code: int, error_string: str, contract: ibContract
106+
self, reqid: int, error_code: int, error_string: str, ib_contract: ibContract
107107
):
108108
"""
109109
Error handler called from server
@@ -116,32 +116,22 @@ def error_handler(
116116
:return: success
117117
"""
118118

119-
msg = "Reqid %d: %d %s" % (reqid, error_code, error_string)
120-
121-
log_to_use = self._get_log_for_contract(contract)
119+
msg = "Reqid %d: %d %s for %s" % (
120+
reqid,
121+
error_code,
122+
error_string,
123+
str(ib_contract),
124+
)
122125

123126
iserror = error_code in IB_IS_ERROR
124127
if iserror:
125128
# Serious requires some action
126129
myerror_type = IB_ERROR_TYPES.get(error_code, "generic")
127-
self.broker_error(msg=msg, myerror_type=myerror_type, log=log_to_use)
130+
self.broker_error(msg=msg, myerror_type=myerror_type, log=self.log)
128131

129132
else:
130133
# just a general message
131-
self.broker_message(msg=msg, log=log_to_use)
132-
133-
def _get_log_for_contract(self, contract: ibContract) -> pst_logger:
134-
if contract is None:
135-
log_to_use = self.log.setup()
136-
else:
137-
ib_expiry_str = contract.lastTradeDateOrContractMonth
138-
instrument_code = self.get_instrument_code_from_broker_contract_object(
139-
contract
140-
)
141-
futures_contract = futuresContract(instrument_code, ib_expiry_str)
142-
log_to_use = futures_contract.specific_log(self.log)
143-
144-
return log_to_use
134+
self.broker_message(msg=msg, log=self.log)
145135

146136
def broker_error(self, msg, log, myerror_type):
147137
log.warn(msg)
@@ -205,6 +195,7 @@ def broker_identity_for_contract(
205195
ib_code=str(contract_details.contract.symbol),
206196
ib_multiplier=float(contract_details.contract.multiplier),
207197
ib_exchange=str(contract_details.contract.exchange),
198+
ib_valid_exchange=str(contract_details.validExchanges),
208199
)
209200

210201
def get_contract_details(
@@ -214,7 +205,6 @@ def get_contract_details(
214205
allow_multiple_contracts: bool = False,
215206
) -> Union[ibContractDetails, List[ibContractDetails]]:
216207

217-
"""CACHING HERE CAUSES TOO MANY ERRORS SO DON'T USE IT"""
218208
contract_details = self._get_contract_details(
219209
ib_contract_pattern, allow_expired=allow_expired
220210
)
@@ -226,7 +216,9 @@ def get_contract_details(
226216
return contract_details
227217

228218
elif len(contract_details) > 1:
229-
self.log.critical("Multiple contracts and only expected one")
219+
self.log.critical(
220+
"Multiple contracts and only expected one - returning the first"
221+
)
230222

231223
return contract_details[0]
232224

sysbrokers/IB/client/ib_contracts_client.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from copy import copy
22
from ib_insync import Contract
33

4+
from syscore.constants import missing_contract
45
from syscore.cache import Cache
56
from syscore.exceptions import missingData, missingContract
67
from sysbrokers.IB.client.ib_client import ibClient

sysbrokers/IB/config/ib_instrument_config.py

+67-12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
ibInstrumentConfigData,
88
)
99
from syscore.constants import missing_file, missing_instrument, arg_not_supplied
10+
from syscore.exceptions import missingData
1011
from syscore.fileutils import resolve_path_and_filename_for_package
1112
from syscore.genutils import return_another_value_if_nan
1213
from syslogdiag.log_to_screen import logtoscreen
@@ -104,6 +105,7 @@ class IBInstrumentIdentity:
104105
ib_code: str
105106
ib_multiplier: float
106107
ib_exchange: str
108+
ib_valid_exchange: str
107109

108110

109111
def get_instrument_code_from_broker_instrument_identity(
@@ -115,20 +117,35 @@ def get_instrument_code_from_broker_instrument_identity(
115117
ib_code = ib_instrument_identity.ib_code
116118
ib_multiplier = ib_instrument_identity.ib_multiplier
117119
ib_exchange = ib_instrument_identity.ib_exchange
120+
ib_valid_exchange = ib_instrument_identity.ib_valid_exchange
121+
122+
config_rows = _get_relevant_config_rows_from_broker_instrument_identity_fields(
123+
config=config,
124+
ib_code=ib_code,
125+
ib_multiplier=ib_multiplier,
126+
ib_exchange=ib_exchange,
127+
)
118128

119-
config_rows = config[
120-
(config.IBSymbol == ib_code)
121-
& (config.IBMultiplier == ib_multiplier)
122-
& (config.IBExchange == ib_exchange)
123-
]
124129
if len(config_rows) == 0:
125-
msg = "Broker symbol %s (%s, %f) not found in configuration file!" % (
126-
ib_code,
127-
ib_exchange,
128-
ib_multiplier,
129-
)
130-
log.critical(msg)
131-
raise Exception(msg)
130+
## try something else
131+
## might have a weird exchange, but the exchange we want is in validExchanges
132+
try:
133+
config_rows = _get_relevant_config_rows_from_broker_instrument_identity_using_multiple_valid_exchanges(
134+
config=config, ib_instrument_identity=ib_instrument_identity
135+
)
136+
137+
except:
138+
msg = (
139+
"Broker symbol %s (exchange:%s, valid_exchange:%s multiplier:%f) not found in configuration file!"
140+
% (
141+
ib_code,
142+
ib_exchange,
143+
ib_valid_exchange,
144+
ib_multiplier,
145+
)
146+
)
147+
log.critical(msg)
148+
raise Exception(msg)
132149

133150
if len(config_rows) > 1:
134151

@@ -142,6 +159,44 @@ def get_instrument_code_from_broker_instrument_identity(
142159
return config_rows.iloc[0].Instrument
143160

144161

162+
def _get_relevant_config_rows_from_broker_instrument_identity_using_multiple_valid_exchanges(
163+
config: IBconfig, ib_instrument_identity: IBInstrumentIdentity
164+
) -> pd.Series:
165+
166+
ib_code = ib_instrument_identity.ib_code
167+
ib_multiplier = ib_instrument_identity.ib_multiplier
168+
ib_valid_exchange = ib_instrument_identity.ib_valid_exchange
169+
170+
valid_exchanges = ib_valid_exchange.split(",")
171+
172+
for ib_exchange in valid_exchanges:
173+
config_rows = _get_relevant_config_rows_from_broker_instrument_identity_fields(
174+
config=config,
175+
ib_code=ib_code,
176+
ib_multiplier=ib_multiplier,
177+
ib_exchange=ib_exchange,
178+
)
179+
180+
if len(config_rows) == 1:
181+
## we have a match!
182+
return config_rows
183+
184+
raise missingData
185+
186+
187+
def _get_relevant_config_rows_from_broker_instrument_identity_fields(
188+
config: IBconfig, ib_code: str, ib_multiplier: float, ib_exchange: str
189+
) -> pd.Series:
190+
191+
config_rows = config[
192+
(config.IBSymbol == ib_code)
193+
& (config.IBMultiplier == ib_multiplier)
194+
& (config.IBExchange == ib_exchange)
195+
]
196+
197+
return config_rows
198+
199+
145200
def get_instrument_list_from_ib_config(
146201
config: IBconfig, log: pst_logger = logtoscreen("")
147202
):

sysbrokers/IB/ib_instruments_data.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from sysbrokers.IB.client.ib_client import ibClient
1414
from sysbrokers.broker_instrument_data import brokerFuturesInstrumentData
1515

16+
from syscore.constants import missing_contract
17+
from syscore.exceptions import missingContract
1618
from sysdata.data_blob import dataBlob
1719

1820
from syslogdiag.log_to_screen import logtoscreen
@@ -34,11 +36,17 @@ def __repr__(self):
3436
def get_instrument_code_from_broker_contract_object(
3537
self, broker_contract_object: ibContract
3638
) -> str:
37-
38-
return self.ib_client.get_instrument_code_from_broker_contract_object(
39-
broker_contract_object
39+
instrument_code = (
40+
self.ib_client.get_instrument_code_from_broker_contract_object(
41+
broker_contract_object
42+
)
4043
)
4144

45+
if instrument_code is missing_contract:
46+
raise missingContract
47+
48+
return instrument_code
49+
4250
def _get_instrument_data_without_checking(self, instrument_code: str):
4351
return self.get_futures_instrument_object_with_IB_data(instrument_code)
4452

syscore/constants.py

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def __repr__(self):
1212
missing_instrument = named_object("missing instrument")
1313
missing_file = named_object("missing file")
1414
missing_data = named_object("missing data")
15+
missing_contract = named_object("missing contract")
1516
market_closed = named_object("market closed")
1617
fill_exceeds_trade = named_object("fill too big for trade")
1718
arg_not_supplied = named_object("arg not supplied")

0 commit comments

Comments
 (0)