Repository: ccxt/ccxt
Pull request: Bybit: fetchMarkets, add ETH and SOL option markets
Added support for ETH and SOL option markets to Bybit fetchMarkets:
fixes: #18471

```
bybit.fetchMarkets ()
2023-07-09T04:25:46.480Z iteration 0 passed in 855 ms

                   id |                        symbol |       base | quote | settle |     baseId | quoteId | settleId |   type |  spot | margin |  swap | future | option | active | contract | linear | inverse |  taker |  maker | contractSize |        expiry |           expiryDatetime |  strike | optionType |                          precision |                                                                                              limits  
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
              BTCUSDT |                      BTC/USDT |        BTC |  USDT |        |        BTC |    USDT |          |   spot |  true |        | false |  false |  false |   true |    false |        |         |  0.001 |  0.001 |              |               |                          |         |            |   {"amount":0.000001,"price":0.01} |                                                                                     [object Object]  
              ETHUSDT |                      ETH/USDT |        ETH |  USDT |        |        ETH |    USDT |          |   spot |  true |        | false |  false |  false |   true |    false |        |         |  0.001 |  0.001 |              |               |                          |         |            |    {"amount":0.00001,"price":0.01} |                                                                                     [object Object]  
...
          ETH-29SEP23 |           ETH/USD:USDC-230929 |        ETH |   USD |   USDC |        ETH |     USD |     USDC | future | false |        | false |   true |  false |   true |     true |   true |   false | 0.0006 | 0.0001 |            1 | 1695974400000 | 2023-09-29T08:00:00.000Z |         |            |       {"amount":0.01,"price":0.05} |                                                                                     [object Object]  
              ETHPERP |                  ETH/USD:USDC |        ETH |   USD |   USDC |        ETH |     USD |     USDC |   swap | false |        |  true |  false |  false |   true |     true |   true |   false | 0.0006 | 0.0001 |            1 |               |                          |         |            |       {"amount":0.01,"price":0.01} |                                                                                     [object Object]  
              ETHUSDT |                 ETH/USDT:USDT |        ETH |  USDT |   USDT |        ETH |    USDT |     USDT |   swap | false |        |  true |  false |  false |   true |     true |   true |   false | 0.0006 | 0.0001 |            1 |               |                          |         |            |       {"amount":0.01,"price":0.01} |                                                                                     [object Object]  
...
  BTC-29DEC23-80000-C |   BTC/USD:USDC-231229-80000-C |        BTC |   USD |   USDC |        BTC |     USD |     USDC | option | false |  false | false |  false |   true |   true |     true |   true |   false | 0.0006 | 0.0001 |         0.01 | 1703836800000 | 2023-12-29T08:00:00.000Z |   80000 |       call |          {"amount":0.01,"price":5} |          {"leverage":{},"amount":{"min":0.01,"max":500},"price":{"min":5,"max":10000000},"cost":{}}  
...
   ETH-29DEC23-3300-C |    ETH/USD:USDC-231229-3300-C |        ETH |   USD |   USDC |        ETH |     USD |     USDC | option | false |  false | false |  false |   true |   true |     true |   true |   false | 0.0006 | 0.0001 |          0.1 | 1703836800000 | 2023-12-29T08:00:00.000Z |    3300 |       call |         {"amount":0.1,"price":0.1} |        {"leverage":{},"amount":{"min":0.1,"max":5000},"price":{"min":0.1,"max":10000000},"cost":{}}  
...
     SOL-28JUL23-36-C |      SOL/USD:USDC-230728-36-C |        SOL |   USD |   USDC |        SOL |     USD |     USDC | option | false |  false | false |  false |   true |   true |     true |   true |   false | 0.0006 | 0.0001 |            1 | 1690531200000 | 2023-07-28T08:00:00.000Z |      36 |       call |          {"amount":1,"price":0.01} |      {"leverage":{},"amount":{"min":1,"max":2000000},"price":{"min":0.01,"max":10000000},"cost":{}}  
...
1585 objects
```

Problem statement:
Bybit USDC options: ETH and SOL
### ؜

Programming Language version: Python 3.11
CCXT version: 4.0.8

Hi, 

I would like to get informations from USDC options on Bybit.
So I set `exchange.options['defaultType'] = 'option'` and after I get markets with `exchange.load_markets()`
When I list markets `exchange.markets`, I had only BTC contracts. 
I had try to fetch information manually from a ETH option like that: `await exchange.fetch_ticker('ETH/USD:USDC-230929-1200-C')` (the informations to build the name are corrects)
and I get: `ccxt.base.errors.BadSymbol: bybit does not have market symbol ETH/USD:USDC-230929-1200-C`

So I would like to know how interact with ETH/SOL options.

Thank you

Hints from issue discussion:
Another thing, for the initialization of the Bybit object, defaultType doesn't work:
```
import ccxt.asyncio as ccxt
ccxt.bybit({'apiKey': APIKEY', 'secret': SECRET', 'defaultType': 'option', })
```
When you call `options['defaultType']`, it's `swap`
But if you set the defaultType after initialization, it works.
Hello @Hadevmin,

> Another thing, for the initialization of the Bybit object, defaultType doesn't work:

You need to put it inside `options`, example:

```Python
ccxt.bybit({'apiKey': APIKEY', 'secret': SECRET', 'options' : { defaultType': 'option'} })
```

Regarding the second question, we will take a look and ever asap. 
Hello @carlosmiei ,

Thank you for your answer. 
I'm sorry, I forgot how to initialize correctly... Thanks for the reminder.

I remain at your disposal if you need further information. 
@Hadevmin I opened a pull request that adds support for loading ETH and SOL option markets without the need for specifying the defaultType. I'll also be adding more option support to other methods on bybit soon
@Dan-krm Thanks, I'm waiting for the PR to be merged to test your update.

Commit message: add 'loadExpiredOptions' setting

Added a loadExpiredOptions setting to load expired options contracts when it is set to true

Patch to review:
diff --git a/ts/src/bybit.ts b/ts/src/bybit.ts
index 5232dc4f20bb0..ca13c1c6e3ec2 100644
--- a/ts/src/bybit.ts
+++ b/ts/src/bybit.ts
@@ -1091,6 +1091,8 @@ export default class bybit extends Exchange {
                 'recvWindow': 5 * 1000, // 5 sec default
                 'timeDifference': 0, // the difference between system clock and exchange server clock
                 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation
+                'loadAllOptions': false, // load all possible option markets, adds signficant load time
+                'loadExpiredOptions': false, // loads expired options, to load all possible expired options set loadAllOptions to true
                 'brokerId': 'CCXT',
                 'accountsByType': {
                     'spot': 'SPOT',
@@ -1406,27 +1408,26 @@ export default class bybit extends Exchange {
         if (this.options['adjustForTimeDifference']) {
             await this.loadTimeDifference ();
         }
-        let type = undefined;
-        [ type, params ] = this.handleMarketTypeAndParams ('fetchMarkets', undefined, params);
         const promisesUnresolved = [
             this.fetchSpotMarkets (params),
-            this.fetchDerivativesMarkets ({ 'category': 'linear' }),
-            this.fetchDerivativesMarkets ({ 'category': 'inverse' }),
+            this.fetchFutureMarkets ({ 'category': 'linear' }),
+            this.fetchFutureMarkets ({ 'category': 'inverse' }),
+            this.fetchOptionMarkets ({ 'baseCoin': 'BTC' }),
+            this.fetchOptionMarkets ({ 'baseCoin': 'ETH' }),
+            this.fetchOptionMarkets ({ 'baseCoin': 'SOL' }),
         ];
-        if (type === 'option') {
-            promisesUnresolved.push (this.fetchDerivativesMarkets ({ 'category': 'option' }));
-        }
         const promises = await Promise.all (promisesUnresolved);
         const spotMarkets = promises[0];
         const linearMarkets = promises[1];
         const inverseMarkets = promises[2];
-        let markets = spotMarkets;
-        markets = this.arrayConcat (markets, linearMarkets);
-        if (type === 'option') {
-            const optionMarkets = promises[3];
-            markets = this.arrayConcat (markets, optionMarkets);
-        }
-        return this.arrayConcat (markets, inverseMarkets);
+        const btcOptionMarkets = promises[3];
+        const ethOptionMarkets = promises[4];
+        const solOptionMarkets = promises[5];
+        const futureMarkets = this.arrayConcat (linearMarkets, inverseMarkets);
+        let optionMarkets = this.arrayConcat (btcOptionMarkets, ethOptionMarkets);
+        optionMarkets = this.arrayConcat (optionMarkets, solOptionMarkets);
+        const derivativeMarkets = this.arrayConcat (futureMarkets, optionMarkets);
+        return this.arrayConcat (spotMarkets, derivativeMarkets);
     }
 
     async fetchSpotMarkets (params) {
@@ -1537,7 +1538,7 @@ export default class bybit extends Exchange {
         return result;
     }
 
-    async fetchDerivativesMarkets (params) {
+    async fetchFutureMarkets (params) {
         params = this.extend (params);
         params['limit'] = 1000; // minimize number of requests
         const response = await this.publicGetV5MarketInstrumentsInfo (params);
@@ -1559,8 +1560,6 @@ export default class bybit extends Exchange {
             }
         }
         //
-        // linear response
-        //
         //     {
         //         "retCode": 0,
         //         "retMsg": "OK",
@@ -1604,43 +1603,6 @@ export default class bybit extends Exchange {
         //         "time": 1672712495660
         //     }
         //
-        // option response
-        //
-        //     {
-        //         "retCode": 0,
-        //         "retMsg": "OK",
-        //         "result": {
-        //             "category": "option",
-        //             "nextPageCursor": "",
-        //             "list": [
-        //                 {
-        //                     "category": "option",
-        //                     "symbol": "ETH-3JAN23-1250-P",
-        //                     "status": "ONLINE",
-        //                     "baseCoin": "ETH",
-        //                     "quoteCoin": "USD",
-        //                     "settleCoin": "USDC",
-        //                     "optionsType": "Put",
-        //                     "launchTime": "1672560000000",
-        //                     "deliveryTime": "1672732800000",
-        //                     "deliveryFeeRate": "0.00015",
-        //                     "priceFilter": {
-        //                         "minPrice": "0.1",
-        //                         "maxPrice": "10000000",
-        //                         "tickSize": "0.1"
-        //                     },
-        //                     "lotSizeFilter": {
-        //                         "maxOrderQty": "1500",
-        //                         "minOrderQty": "0.1",
-        //                         "qtyStep": "0.1"
-        //                     }
-        //                 }
-        //             ]
-        //         },
-        //         "retExtInfo": {},
-        //         "time": 1672712537130
-        //     }
-        //
         const result = [];
         let category = this.safeString (data, 'category');
         for (let i = 0; i < markets.length; i++) {
@@ -1673,17 +1635,13 @@ export default class bybit extends Exchange {
             const priceFilter = this.safeValue (market, 'priceFilter', {});
             const leverage = this.safeValue (market, 'leverageFilter', {});
             const status = this.safeString (market, 'status');
-            const active = (status === 'Trading');
             const swap = linearPerpetual || inversePerpetual;
             const future = inverseFutures || linearFutures;
-            const option = (category === 'option');
             let type = undefined;
             if (swap) {
                 type = 'swap';
             } else if (future) {
                 type = 'future';
-            } else if (option) {
-                type = 'option';
             }
             let expiry = undefined;
             // some swaps have deliveryTime meaning delisting time
@@ -1694,22 +1652,9 @@ export default class bybit extends Exchange {
                 }
             }
             const expiryDatetime = this.iso8601 (expiry);
-            let strike = undefined;
-            let optionType = undefined;
             symbol = symbol + ':' + settle;
             if (expiry !== undefined) {
                 symbol = symbol + '-' + this.yymmdd (expiry);
-                if (option) {
-                    const splitId = id.split ('-');
-                    strike = this.safeString (splitId, 2);
-                    const optionLetter = this.safeString (splitId, 3);
-                    symbol = symbol + '-' + strike + '-' + optionLetter;
-                    if (optionLetter === 'P') {
-                        optionType = 'put';
-                    } else if (optionLetter === 'C') {
-                        optionType = 'call';
-                    }
-                }
             }
             const contractSize = inverse ? this.safeNumber2 (lotSizeFilter, 'minTradingQty', 'minOrderQty') : this.parseNumber ('1');
             result.push ({
@@ -1726,8 +1671,8 @@ export default class bybit extends Exchange {
                 'margin': undefined,
                 'swap': swap,
                 'future': future,
-                'option': option,
-                'active': active,
+                'option': false,
+                'active': (status === 'Trading'),
                 'contract': true,
                 'linear': linear,
                 'inverse': inverse,
@@ -1736,8 +1681,8 @@ export default class bybit extends Exchange {
                 'contractSize': contractSize,
                 'expiry': expiry,
                 'expiryDatetime': expiryDatetime,
-                'strike': strike,
-                'optionType': optionType,
+                'strike': undefined,
+                'optionType': undefined,
                 'precision': {
                     'amount': this.safeNumber (lotSizeFilter, 'qtyStep'),
                     'price': this.safeNumber (priceFilter, 'tickSize'),
@@ -1766,6 +1711,192 @@ export default class bybit extends Exchange {
         return result;
     }
 
+    async fetchOptionMarkets (params) {
+        const request = {
+            'category': 'option',
+        };
+        const response = await this.publicGetV5MarketInstrumentsInfo (this.extend (request, params));
+        const data = this.safeValue (response, 'result', {});
+        let markets = this.safeValue (data, 'list', []);
+        if (this.options['loadAllOptions']) {
+            request['limit'] = 1000;
+            let paginationCursor = this.safeString (data, 'nextPageCursor');
+            if (paginationCursor !== undefined) {
+                while (paginationCursor !== undefined) {
+                    request['cursor'] = paginationCursor;
+                    const responseInner = await this.publicGetDerivativesV3PublicInstrumentsInfo (this.extend (request, params));
+                    const dataNew = this.safeValue (responseInner, 'result', {});
+                    const rawMarkets = this.safeValue (dataNew, 'list', []);
+                    const rawMarketsLength = rawMarkets.length;
+                    if (rawMarketsLength === 0) {
+                        break;
+                    }
+                    markets = this.arrayConcat (rawMarkets, markets);
+                    paginationCursor = this.safeString (dataNew, 'nextPageCursor');
+                }
+            }
+        }
+        //
+        //     {
+        //         "retCode": 0,
+        //         "retMsg": "success",
+        //         "result": {
+        //             "category": "option",
+        //             "nextPageCursor": "0%2C2",
+        //             "list": [
+        //                 {
+        //                     "symbol": "BTC-29DEC23-80000-C",
+        //                     "status": "Trading",
+        //                     "baseCoin": "BTC",
+        //                     "quoteCoin": "USD",
+        //                     "settleCoin": "USDC",
+        //                     "optionsType": "Call",
+        //                     "launchTime": "1688630400000",
+        //                     "deliveryTime": "1703836800000",
+        //                     "deliveryFeeRate": "0.00015",
+        //                     "priceFilter": {
+        //                         "minPrice": "5",
+        //                         "maxPrice": "10000000",
+        //                         "tickSize": "5"
+        //                     },
+        //                     "lotSizeFilter": {
+        //                         "maxOrderQty": "500",
+        //                         "minOrderQty": "0.01",
+        //                         "qtyStep": "0.01"
+        //                     }
+        //                 },
+        //             ]
+        //         },
+        //         "retExtInfo": {},
+        //         "time": 1688873094448
+        //     }
+        //
+        const result = [];
+        for (let i = 0; i < markets.length; i++) {
+            const market = markets[i];
+            const id = this.safeString (market, 'symbol');
+            const baseId = this.safeString (market, 'baseCoin');
+            const quoteId = this.safeString (market, 'quoteCoin');
+            const settleId = this.safeString (market, 'settleCoin');
+            const base = this.safeCurrencyCode (baseId);
+            const quote = this.safeCurrencyCode (quoteId);
+            const settle = this.safeCurrencyCode (settleId);
+            const lotSizeFilter = this.safeValue (market, 'lotSizeFilter', {});
+            const priceFilter = this.safeValue (market, 'priceFilter', {});
+            const status = this.safeString (market, 'status');
+            const expiry = this.safeInteger (market, 'deliveryTime');
+            const splitId = id.split ('-');
+            const strike = this.safeString (splitId, 2);
+            const optionLetter = this.safeString (splitId, 3);
+            if ((this.options['loadAllOptions']) || (this.options['loadExpiredOptions'])) {
+                result.push ({
+                    'id': id,
+                    'symbol': base + '/' + quote + ':' + settle + '-' + this.yymmdd (expiry) + '-' + strike + '-' + optionLetter,
+                    'base': base,
+                    'quote': quote,
+                    'settle': settle,
+                    'baseId': baseId,
+                    'quoteId': quoteId,
+                    'settleId': settleId,
+                    'type': 'option',
+                    'spot': false,
+                    'margin': false,
+                    'swap': false,
+                    'future': false,
+                    'option': true,
+                    'active': (status === 'Trading'),
+                    'contract': true,
+                    'linear': true,
+                    'inverse': false,
+                    'taker': this.safeNumber (market, 'takerFee', this.parseNumber ('0.0006')),
+                    'maker': this.safeNumber (market, 'makerFee', this.parseNumber ('0.0001')),
+                    'contractSize': this.safeNumber (lotSizeFilter, 'minOrderQty'),
+                    'expiry': expiry,
+                    'expiryDatetime': this.iso8601 (expiry),
+                    'strike': this.parseNumber (strike),
+                    'optionType': this.safeStringLower (market, 'optionsType'),
+                    'precision': {
+                        'amount': this.safeNumber (lotSizeFilter, 'qtyStep'),
+                        'price': this.safeNumber (priceFilter, 'tickSize'),
+                    },
+                    'limits': {
+                        'leverage': {
+                            'min': undefined,
+                            'max': undefined,
+                        },
+                        'amount': {
+                            'min': this.safeNumber (lotSizeFilter, 'minOrderQty'),
+                            'max': this.safeNumber (lotSizeFilter, 'maxOrderQty'),
+                        },
+                        'price': {
+                            'min': this.safeNumber (priceFilter, 'minPrice'),
+                            'max': this.safeNumber (priceFilter, 'maxPrice'),
+                        },
+                        'cost': {
+                            'min': undefined,
+                            'max': undefined,
+                        },
+                    },
+                    'info': market,
+                });
+            } else {
+                if (status === 'Trading') {
+                    result.push ({
+                        'id': id,
+                        'symbol': base + '/' + quote + ':' + settle + '-' + this.yymmdd (expiry) + '-' + strike + '-' + optionLetter,
+                        'base': base,
+                        'quote': quote,
+                        'settle': settle,
+                        'baseId': baseId,
+                        'quoteId': quoteId,
+                        'settleId': settleId,
+                        'type': 'option',
+                        'spot': false,
+                        'margin': false,
+                        'swap': false,
+                        'future': false,
+                        'option': true,
+                        'active': true,
+                        'contract': true,
+                        'linear': true,
+                        'inverse': false,
+                        'taker': this.safeNumber (market, 'takerFee', this.parseNumber ('0.0006')),
+                        'maker': this.safeNumber (market, 'makerFee', this.parseNumber ('0.0001')),
+                        'contractSize': this.safeNumber (lotSizeFilter, 'minOrderQty'),
+                        'expiry': expiry,
+                        'expiryDatetime': this.iso8601 (expiry),
+                        'strike': this.parseNumber (strike),
+                        'optionType': this.safeStringLower (market, 'optionsType'),
+                        'precision': {
+                            'amount': this.safeNumber (lotSizeFilter, 'qtyStep'),
+                            'price': this.safeNumber (priceFilter, 'tickSize'),
+                        },
+                        'limits': {
+                            'leverage': {
+                                'min': undefined,
+                                'max': undefined,
+                            },
+                            'amount': {
+                                'min': this.safeNumber (lotSizeFilter, 'minOrderQty'),
+                                'max': this.safeNumber (lotSizeFilter, 'maxOrderQty'),
+                            },
+                            'price': {
+                                'min': this.safeNumber (priceFilter, 'minPrice'),
+                                'max': this.safeNumber (priceFilter, 'maxPrice'),
+                            },
+                            'cost': {
+                                'min': undefined,
+                                'max': undefined,
+                            },
+                        },
+                        'info': market,
+                    });
+                }
+            }
+        }
+        return result;
+    }
+
     parseTicker (ticker, market = undefined) {
         //
         // spot


Code review comments:
diff_hunk: @@ -1766,6 +1711,192 @@ export default class bybit extends Exchange {
         return result;
     }
 
+    async fetchOptionMarkets (params) {
+        const request = {
+            'category': 'option',
+        };
+        const response = await this.publicGetV5MarketInstrumentsInfo (this.extend (request, params));
+        const data = this.safeValue (response, 'result', {});
+        let markets = this.safeValue (data, 'list', []);
+        if (this.options['loadAllOptions']) {
+            request['limit'] = 1000;
+            let paginationCursor = this.safeString (data, 'nextPageCursor');
+            if (paginationCursor !== undefined) {
+                while (paginationCursor !== undefined) {
+                    request['cursor'] = paginationCursor;
+                    const responseInner = await this.publicGetDerivativesV3PublicInstrumentsInfo (this.extend (request, params));
+                    const dataNew = this.safeValue (responseInner, 'result', {});
+                    const rawMarkets = this.safeValue (dataNew, 'list', []);
+                    const rawMarketsLength = rawMarkets.length;
+                    if (rawMarketsLength === 0) {
+                        break;
+                    }
+                    markets = this.arrayConcat (rawMarkets, markets);
+                    paginationCursor = this.safeString (dataNew, 'nextPageCursor');
+                }
+            }
+        }
+        //
+        //     {
+        //         "retCode": 0,
+        //         "retMsg": "success",
+        //         "result": {
+        //             "category": "option",
+        //             "nextPageCursor": "0%2C2",
+        //             "list": [
+        //                 {
+        //                     "symbol": "BTC-29DEC23-80000-C",
+        //                     "status": "Trading",
+        //                     "baseCoin": "BTC",
+        //                     "quoteCoin": "USD",
+        //                     "settleCoin": "USDC",
+        //                     "optionsType": "Call",
+        //                     "launchTime": "1688630400000",
+        //                     "deliveryTime": "1703836800000",
+        //                     "deliveryFeeRate": "0.00015",
+        //                     "priceFilter": {
+        //                         "minPrice": "5",
+        //                         "maxPrice": "10000000",
+        //                         "tickSize": "5"
+        //                     },
+        //                     "lotSizeFilter": {
+        //                         "maxOrderQty": "500",
+        //                         "minOrderQty": "0.01",
+        //                         "qtyStep": "0.01"
+        //                     }
+        //                 },
+        //             ]
+        //         },
+        //         "retExtInfo": {},
+        //         "time": 1688873094448
+        //     }
+        //
+        const result = [];
+        for (let i = 0; i < markets.length; i++) {
+            const market = markets[i];
+            const id = this.safeString (market, 'symbol');
+            const baseId = this.safeString (market, 'baseCoin');
+            const quoteId = this.safeString (market, 'quoteCoin');
+            const settleId = this.safeString (market, 'settleCoin');
+            const base = this.safeCurrencyCode (baseId);
+            const quote = this.safeCurrencyCode (quoteId);
+            const settle = this.safeCurrencyCode (settleId);
+            const lotSizeFilter = this.safeValue (market, 'lotSizeFilter', {});
+            const priceFilter = this.safeValue (market, 'priceFilter', {});
+            const status = this.safeString (market, 'status');
+            const expiry = this.safeInteger (market, 'deliveryTime');
+            const splitId = id.split ('-');
+            const strike = this.safeString (splitId, 2);
+            const optionLetter = this.safeString (splitId, 3);
+            if ((this.options['loadAllOptions']) || (this.options['loadExpiredOptions'])) {
path: ts/src/bybit.ts
text: @user1:
hum probably we don't need this repetitive if-else clause, we can do something like

`if (status === 'Trading') || (this.options['loadAllOptions']) || (this.options['loadExpiredOptions']`