Index: /trunk/BNC/src/bnchelp.html
===================================================================
--- /trunk/BNC/src/bnchelp.html	(revision 11086)
+++ /trunk/BNC/src/bnchelp.html	(revision 11087)
@@ -147,5 +147,5 @@
     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.2 <a href="#corrint">Interval</a><br>
     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.3 <a href="#corrport">Port</a><br>
-    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#ssrqc">QC Directory, QC Streams, QC Reference, QC Interval</a><br>
+    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#ssrqc">QC Directory, QC Streams, QC Reference, QC Interval, QC Bias File</a><br>
     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.8.4 <a href="#corrwait">Wait for Full Corr Epoch</a><br>
     &nbsp; &nbsp; &nbsp; 2.9 <a href="#syncout"><b>Feed Engine</b></a><br>
@@ -274,4 +274,6 @@
     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.14.1.6 <a href=#combisatsys>Satellite
       Systems</a><br>
+    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.14.1.7 <a href=#combirefsig>Reference
+      Signals</a><br>
     &nbsp; &nbsp; &nbsp; 2.15 <a href=#upclk><b>Upload Corrections</b></a><br>
     &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.15.1 <a href=#upadd>Add, Delete Row</a><br>
@@ -5088,5 +5090,5 @@
 
   <p>
-  <h4 id="ssrqc">2.8.4 QC Directory, QC Streams, QC Reference, QC Interval - optional</h4>
+  <h4 id="ssrqc">2.8.4 QC Directory, QC Streams, QC Reference, QC Interval, QC Bias File - optional</h4>
   </p>
   <p>
@@ -5096,5 +5098,6 @@
     'SSRA00CNE1,SSRA00GMV0'. A stream name with a leading '!' excludes the stream from the QC and from the consensus,
     e.g. 'ALL,!SIRGAS200001' for a stream referring to another reference frame than the ITRF. 'QC Reference' selects the reference of the stream comparison (see below). Select
-    the 'QC Interval' for the reports (default: 1 hour). One daily file
+    the 'QC Interval' for the reports (default: 1 hour). A 'QC Bias File' (Bias SINEX, OSB or DSB, e.g. a CAS DCB or
+    a GFZ OSB product) is an optional reference for the code bias check (see below). One daily file
     '&lt;stream&gt;_S_&lt;YYYYDDD&gt;0000_01D_QC.txt' is written per stream; at the end of each interval and at the
     end of the program, a report per system is appended. Broadcast ephemerides must be received as well, e.g. via
@@ -5126,9 +5129,32 @@
       broadcast ephemeris, or to a satellite without broadcast ephemeris (number of corrections in brackets; not
       checked during the first 2 minutes, while the broadcast ephemerides are received),</li>
-    <li>the code bias signals provided, satellites without code biases of the reference signals (GPS C1W/C2W, GLONASS
-      C1P/C2P, Galileo C1C/C5Q, BDS C2I/C6I, QZSS C1C/C2L), and the standard deviation and maximum of the
+    <li>the code bias signals provided, satellites without code biases of the reference signals (default GPS C1W/C2W,
+      GLONASS C1P/C2P, Galileo C1C/C5Q, BDS C2I/C6I, QZSS C1C/C2L, SBAS C1C/C5Q; as in the combination, see option
+      'Reference Signals' in <a href=#combirefsig>section 2.14.1.7</a>), and the standard deviation and maximum of the
       ionosphere-free combination B_IF of the reference signal biases. A B_IF varying between the satellites is
       legitimate if the clocks are estimated with other signals, but must then be consistent with the clocks
       (see section 'Combination'),</li>
+    <li>the code bias check: the DCBs of the stream, i.e. the differences of its code biases to the first reference
+      signal (or to another common signal, if the stream lacks it), are compared with
+      <ul>
+        <li>each other AC separately (one vote per AC, own AC left out, combinations not voting), with the RMS of
+          the differences and the number of DCBs with opposite sign (only DCBs of at least 0.3 m are used for the
+          sign). A stream is reported to use the opposite sign if it has the opposite sign for most DCBs to more than
+          half of at least two other ACs - with only two ACs, it cannot be decided which one is wrong. Satellites
+          with DCBs deviating from the median of at least 3 other ACs are listed,</li>
+        <li>the selected 'QC Reference' stream instead, if it is not 'CONSENSUS',</li>
+        <li>the 'QC Bias File', if specified.</li>
+      </ul>
+      DCBs do not depend on the clock datum, so the check also works for streams whose clocks are estimated with
+      other signals. A sign opposite to the reference for most DCBs indicates code biases in the sign convention of
+      Bias SINEX (biases to be subtracted) instead of SSR (biases to be added). Such a stream passes the B_IF check
+      above (B_IF is zero for both signs), but a PPP user applying its biases to other than the reference signals
+      gets errors of twice the biases, e.g.
+      <pre>
+  G code biases compared with the other ACs (DCBs to C1W): BKG RMS 2.17 m, opposite sign 48/49; CNE RMS 2.44 m, opposite sign 98/101
+  G code biases with the opposite sign of 2 of 2 other ACs (BKG CNE) - SINEX sign convention instead of the SSR one?
+  G code biases compared with Bias SINEX file CAS1MGXRAP_01D_01D_DCB.BSX (DCBs to C1W, 32 satellites, 141 DCBs): RMS of the differences 2.401 m, opposite sign for 106 of 107 DCBs (|DCB| &gt;= 0.3 m)
+</pre>
+      The latest code biases of each stream are used,</li>
     <li>the phase bias signals provided,</li>
     <li>the availability of the satellite yaw angle with the phase biases: 'not provided', or the number of
@@ -8066,5 +8092,7 @@
   </p>
   <p>
-    The combination is done system-wise for the following reference signals as specified in the IGS Real-Time Committee:
+    The combination is done system-wise for the following reference signals as specified in the IGS Real-Time Committee
+    (default, they can be changed per system with the option 'Reference Signals', see
+    <a href=#combirefsig>section 2.14.1.7</a>):
   <p>
   <table>
@@ -8478,4 +8506,45 @@
   <p>
     Specify for each satellite system whether the clock corrections shall be combined.</p>
+
+  <p>
+  <h4 id="combirefsig">2.14.1.7 Reference Signals - optional</h4>
+  </p>
+  <p>
+    Specify the reference signals of the satellite clocks per GNSS as '&lt;system&gt;:&lt;band&gt;&lt;attribute&gt;&amp;&lt;band&gt;&lt;attribute&gt;',
+    separated by blanks, e.g. 'C:1P&amp;5P J:1C&amp;5Q'. The clocks refer to the ionosphere-free linear combination of
+    the code signals of these two signals. Systems not specified keep the default of the IGS Real-Time Committee:
+  </p>
+  <pre>
+G:1W&amp;2W R:1P&amp;2P E:1C&amp;5Q C:2I&amp;6I J:1C&amp;2L S:1C&amp;5Q
+</pre>
+  <p>
+    The reference signals are used
+  </p>
+  <ul>
+    <li>by the combination, to refer the AC clocks to the reference signals with the code biases of each AC (B_IF, see
+      above); an AC without the code biases of both reference signals of a satellite is excluded from the clock datum,
+    </li>
+    <li>by the combination, to derive the code biases of the combined clocks from a Bias SINEX file ('BSX File'): the
+      ionosphere-free linear combination of the two reference signal biases is set to zero and all other biases are
+      derived from the DCBs. With the default reference signals, the DCBs of the IGS conventions are used as before;
+      with other reference signals, all DCBs of the satellite in the file are used, and the DCB of the reference
+      signals themselves may also be derived via other signals,</li>
+    <li>by the SSR QC (see section 'Broadcast Corrections'), for the code biases of the reference signals, B_IF and the
+      clock comparison. The reference signals used are written to the header of each SSR QC report.</li>
+  </ul>
+  <p>
+    Changing the reference signals is meant for a coordinated switch of the IGS Real-Time convention, e.g. for BDS from
+    C2I/C6I to C1P/C5P (B1C/B2a) following the IGS products, or for QZSS to an L1/L5 pair because the QZSS Block III
+    satellites no longer transmit L2. All ACs of a combination should then provide the code biases of the new reference
+    signals. A switch changes the combined clocks of the affected satellites by the ionosphere-free combination of the
+    DCBs between the old and the new reference signals.
+  </p>
+  <p>
+    Invalid entries (unknown system or band, wrong format) are reported in the logfile and ignored, i.e. the system
+    keeps its default. Non-default reference signals are written to the logfile when the combination starts.
+  </p>
+  <p>
+    Default is an empty option field, meaning the default reference signals. <i>[key: ssrRefSignals]</i>
+  </p>
 
   <p>
Index: /trunk/BNC/src/bncmain.cpp
===================================================================
--- /trunk/BNC/src/bncmain.cpp	(revision 11086)
+++ /trunk/BNC/src/bncmain.cpp	(revision 11087)
@@ -214,4 +214,5 @@
         "   ssrQcRef     {Reference of the stream comparison [character string: CONSENSUS or a stream name, e.g. INTERNAL]}\n"
         "   ssrQcIntr    {SSR QC report interval [character string: 5 min|15 min|30 min|1 hour|1 day]}\n"
+        "   ssrQcBiasFile {Bias SINEX file (OSB or DSB) as reference for the SSR QC code bias check, full path [character string]}\n"
         "\n"
         "Feed Engine Panel keys:\n"
@@ -314,4 +315,6 @@
         "   cmbMethodFilter    {Combination approach [character string: Single-Epoch|Filter]}\n"
         "   cmbBsxFile         {SINEX Bias file, full path [char string]}\n"
+        "   ssrRefSignals      {Reference signals of the clocks per GNSS, also used by the SSR QC [character string,\n"
+        "                       example: C:1P&5P J:1C&5Q; empty: G:1W&2W R:1P&2P E:1C&5Q C:2I&6I J:1C&2L S:1C&5Q]}\n"
         "   cmbMaxres          {Clock outlier residuum threshold in meters [floating-point number]}\n"
         "   cmbMaxdisplacement {Maximal orbit displacement from the mean of corrections for a satellite [floating-point number]}\n"
Index: /trunk/BNC/src/bncsettings.cpp
===================================================================
--- /trunk/BNC/src/bncsettings.cpp	(revision 11086)
+++ /trunk/BNC/src/bncsettings.cpp	(revision 11087)
@@ -142,4 +142,6 @@
         setValue_p("ssrQcRef", "CONSENSUS");
         setValue_p("ssrQcIntr", "1 hour");
+        setValue_p("ssrQcBiasFile", "");
+        setValue_p("ssrRefSignals", "");
         // Feed Engine
         setValue_p("outPort", "");
Index: /trunk/BNC/src/bncwindow.cpp
===================================================================
--- /trunk/BNC/src/bncwindow.cpp	(revision 11086)
+++ /trunk/BNC/src/bncwindow.cpp	(revision 11087)
@@ -301,4 +301,6 @@
   _ssrQcRefLineEdit = new QLineEdit(settings.value("ssrQcRef").toString().isEmpty() ? QString("CONSENSUS") : settings.value("ssrQcRef").toString());
   _ssrQcStreamsLineEdit = new QLineEdit(settings.value("ssrQcStreams").toString().isEmpty() ? QString("ALL") : settings.value("ssrQcStreams").toString());
+  _ssrQcBiasFileChooser = new qtFileChooser(0, qtFileChooser::File);
+  _ssrQcBiasFileChooser->setFileName(settings.value("ssrQcBiasFile").toString());
   _ssrQcIntrComboBox = new QComboBox();
   _ssrQcIntrComboBox->setEditable(false);
@@ -571,4 +573,7 @@
   _cmbBsxFile = new qtFileChooser(0, qtFileChooser::File);
   _cmbBsxFile->setFileName(settings.value("cmbBsxFile").toString());
+
+  _cmbRefSigLineEdit = new QLineEdit(settings.value("ssrRefSignals").toString());
+  _cmbRefSigLineEdit->setPlaceholderText("default");
 
   // Upload Results
@@ -926,6 +931,8 @@
   cLayout->addWidget(new QLabel("QC Interval"), 7, 0);
   cLayout->addWidget(_ssrQcIntrComboBox, 7, 1);
-  cLayout->addWidget(new QLabel(""), 8, 1);
-  cLayout->setRowStretch(7, 999);
+  cLayout->addWidget(new QLabel("QC Bias File"), 8, 0);
+  cLayout->addWidget(_ssrQcBiasFileChooser, 8, 1, 1, 30);
+  cLayout->addWidget(new QLabel(""), 9, 1);
+  cLayout->setRowStretch(8, 999);
   cgroup->setLayout(cLayout);
 
@@ -1418,23 +1425,25 @@
   cmbLayout->addWidget(new QLabel("Sampling"), 7, 10, Qt::AlignLeft);
   cmbLayout->addWidget(_cmbSamplComboBox, 7, 11, Qt::AlignRight);
+  cmbLayout->addWidget(new QLabel("Reference signals"), 8, 10, Qt::AlignLeft);
+  cmbLayout->addWidget(_cmbRefSigLineEdit, 8, 11, 1, 5);
 
 
   cmbLayout->addWidget(new QLabel("GNSS"), 0, 14, Qt::AlignLeft);
-  cmbLayout->addWidget(new QLabel("GPS (C1W/C2W)"), 1, 14);
+  cmbLayout->addWidget(new QLabel("GPS"), 1, 14);
   cmbLayout->addWidget(_cmbGpsCheckBox, 1, 15);
 
-  cmbLayout->addWidget(new QLabel("GLONASS (C1P/C2P)"), 2, 14);
+  cmbLayout->addWidget(new QLabel("GLONASS"), 2, 14);
   cmbLayout->addWidget(_cmbGloCheckBox, 2, 15);
 
-  cmbLayout->addWidget(new QLabel("Galileo (C1C/C5Q)"), 3, 14);
+  cmbLayout->addWidget(new QLabel("Galileo"), 3, 14);
   cmbLayout->addWidget(_cmbGalCheckBox, 3, 15);
 
-  cmbLayout->addWidget(new QLabel("Beidou (C2I/C6I)"), 4, 14);
+  cmbLayout->addWidget(new QLabel("Beidou"), 4, 14);
   cmbLayout->addWidget(_cmbBdsCheckBox, 4, 15);
 
-  cmbLayout->addWidget(new QLabel("QZSS (C1C/C2L)"), 5, 14);
+  cmbLayout->addWidget(new QLabel("QZSS"), 5, 14);
   cmbLayout->addWidget(_cmbQzssCheckBox, 5, 15);
 
-  cmbLayout->addWidget(new QLabel("SBAS (C1C/C5Q)"), 6, 14);
+  cmbLayout->addWidget(new QLabel("SBAS"), 6, 14);
   cmbLayout->addWidget(_cmbSbasCheckBox, 6, 15);
 
@@ -1597,4 +1606,5 @@
   _ssrQcRefLineEdit->setWhatsThis(tr("<p>Specify the reference for the comparison of the orbits and clocks of the checked streams: 'CONSENSUS' for the median of all other streams, or the name of a stream, e.g. 'INTERNAL' for the combination.</p><p>Default is 'CONSENSUS'. <i>[key: ssrQcRef]</i></p>"));
   _ssrQcStreamsLineEdit->setWhatsThis(tr("<p>Specify the Broadcast Correction streams to be checked, as a comma separated list of stream names (e.g. SSRA00CNE1,SSRA00GMV0), or 'ALL' for all decoded streams. A stream with a leading '!' is excluded from the QC and from the consensus, e.g. 'ALL,!SIRGAS200001' for streams in another reference frame.</p><p>Default is 'ALL'. <i>[key: ssrQcStreams]</i></p>"));
+  _ssrQcBiasFileChooser->setWhatsThis(tr("<p>Specify a Bias SINEX file (OSB or DSB, e.g. a CAS DCB or a GFZ OSB product) as reference for the code bias check of the SSR QC: the DCBs of each stream are compared with the DCBs of this file, in addition to the comparison with the other ACs. A sign opposite to the reference for most DCBs indicates a stream using the SINEX instead of the SSR sign convention.</p><p>Default is an empty option field, meaning the code biases are compared with the other ACs only. <i>[key: ssrQcBiasFile]</i></p>"));
   _ssrQcIntrComboBox->setWhatsThis(tr("<p>Select the interval of the SSR QC reports. <i>[key: ssrQcIntr]</i></p>"));
   _corrPortLineEdit->setWhatsThis(tr("<p>BNC can produce Broadcast Ephemeris Corrections on your local host through an IP port.</p><p>Specify a port number here to activate this function. <i>[key: corrPort]</i></p>"));
@@ -1745,4 +1755,5 @@
   _cmbSbasCheckBox->setWhatsThis(tr("<p>SBAS clock corrections shall be combined; SBAS Broadcast ephemeris and corrections are required. <i>[key: cmbSbas]</i></p>"));
   _cmbNavicCheckBox->setWhatsThis(tr("<p>NavIC clock corrections shall be combined; NavIC Broadcast ephemeris and corrections are required. <i>[key: cmbNavic]</i></p>"));
+  _cmbRefSigLineEdit->setWhatsThis(tr("<p>Specify the reference signals of the satellite clocks per GNSS: the clocks refer to the ionosphere-free combination of the code signals of these two signals. They are used by the combination to refer the AC clocks with their code biases and to derive the code biases from a Bias SINEX file, and by the SSR QC.</p><p>Systems not specified keep the default 'G:1W&2W R:1P&2P E:1C&5Q C:2I&6I J:1C&2L S:1C&5Q' (IGS conventions). Example: 'C:1P&5P J:1C&5Q'.</p><p>Default is an empty option field, meaning the default reference signals. <i>[key: ssrRefSignals]</i></p>"));
   _cmbBsxFile->setWhatsThis(tr("<p> Specify a Bias SINEX File that will be used to add satellite code biases to the combined clocks. <i>[key: cmbBsxFile]</i></p>"));
 
@@ -1875,4 +1886,5 @@
   delete _ssrQcRefLineEdit;
   delete _ssrQcIntrComboBox;
+  delete _ssrQcBiasFileChooser;
   delete _corrPortLineEdit;
   delete _outPortLineEdit;
@@ -1918,4 +1930,5 @@
   delete _cmbNavicCheckBox;
   delete _cmbBsxFile;
+  delete _cmbRefSigLineEdit;
   delete _uploadSamplRtcmEphCorrComboBox;
   _uploadEphTable->deleteLater();
@@ -2405,4 +2418,5 @@
   settings.setValue("ssrQcRef", _ssrQcRefLineEdit->text());
   settings.setValue("ssrQcIntr", _ssrQcIntrComboBox->currentText());
+  settings.setValue("ssrQcBiasFile", _ssrQcBiasFileChooser->fileName());
   settings.setValue("corrPort", _corrPortLineEdit->text());
   // Feed Engine
@@ -2471,4 +2485,5 @@
   settings.setValue("cmbNavic", _cmbNavicCheckBox->checkState());
   settings.setValue("cmbBsxFile", _cmbBsxFile->fileName());
+  settings.setValue("ssrRefSignals", _cmbRefSigLineEdit->text());
 
   // Upload Corrections
@@ -2943,4 +2958,5 @@
     enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcStreamsLineEdit);
     enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcRefLineEdit);
+    enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcBiasFileChooser);
   }
 
Index: /trunk/BNC/src/bncwindow.h
===================================================================
--- /trunk/BNC/src/bncwindow.h	(revision 11086)
+++ /trunk/BNC/src/bncwindow.h	(revision 11087)
@@ -154,4 +154,5 @@
     QLineEdit* _ssrQcStreamsLineEdit;
     QLineEdit* _ssrQcRefLineEdit;
+    qtFileChooser* _ssrQcBiasFileChooser;
     QLineEdit* _miscMountLineEdit;
     QLineEdit* _miscPortLineEdit;
@@ -241,4 +242,5 @@
     QCheckBox* _cmbNavicCheckBox;
     qtFileChooser* _cmbBsxFile;
+    QLineEdit* _cmbRefSigLineEdit;
 
     QTableWidget* _uploadTable;
Index: /trunk/BNC/src/combination/bncbiassnx.cpp
===================================================================
--- /trunk/BNC/src/combination/bncbiassnx.cpp	(revision 11086)
+++ /trunk/BNC/src/combination/bncbiassnx.cpp	(revision 11087)
@@ -40,4 +40,5 @@
 
 #include "bncbiassnx.h"
+#include "ssrRefSig.h"
 
 #include <iostream>
@@ -48,6 +49,12 @@
 // Constructor
 ////////////////////////////////////////////////////////////////////////////
-bncBiasSnx::bncBiasSnx() {
+bncBiasSnx::bncBiasSnx(bool allGnss) {
   bncSettings settings;
+  if (allGnss) {
+    for (char sys : QString("GRECJSI").toLatin1()) {
+      _useGnss[sys] = true;
+    }
+    return;
+  }
   _useGnss['G'] = (Qt::CheckState(settings.value("cmbGps").toInt()) == Qt::Checked) ? true : false;
   _useGnss['R'] = (Qt::CheckState(settings.value("cmbGlo").toInt()) == Qt::Checked) ? true : false;
@@ -307,226 +314,134 @@
 }
 
-//
+// DCBs (OSB_A - OSB_B) used to derive the code biases from the default
+// reference signals (see t_ssrRefSig), in the order of evaluation
+////////////////////////////////////////////////////////////////////////////
+static const QVector<QPair<QString, QString> >& defaultDcbs(char sys) {
+  static QMap<char, QVector<QPair<QString, QString> > > dcbs;
+  static QMutex mutex;
+  QMutexLocker locker(&mutex);
+  if (dcbs.isEmpty()) {
+    auto add = [](QVector<QPair<QString, QString> >& vv, const char* list) {
+      for (const QString& dcb : QString(list).split(' ', Qt::SkipEmptyParts)) {
+        vv.append(qMakePair(dcb.section('-', 0, 0), dcb.section('-', 1, 1)));
+      }
+    };
+    add(dcbs['G'], "1C-1W 1C-5Q 1C-5X 1C-2W 2C-2W 2W-2S 2W-2L 2W-2X");
+    add(dcbs['R'], "1C-1P 2C-2P");
+    add(dcbs['E'], "1C-6C 1C-7Q 1C-8Q");
+    add(dcbs['C'], "2I-7I 1X-6I 1X-7Z 1X-8X 1X-5X 1P-6I 1P-5P 1D-6I 1D-5D");
+    add(dcbs['J'], "1C-1X 1X-2X 1C-5X");
+  }
+  static const QVector<QPair<QString, QString> > none;
+  return dcbs.contains(sys) ? dcbs[sys] : none;
+}
+
+// Satellite code biases (OSB) from the DCBs, referred to the reference
+// signals of the clocks: aIF1 * OSB_ref1 + aIF2 * OSB_ref2 = 0
 ////////////////////////////////////////////////////////////////////////////
 void bncBiasSnx::determineSsrSatCodeBiases(QString prn, double aIF1, double aIF2,
                                            t_satCodeBias& satCodeBias) {
-  std::string rnxType2ch;
-  double value;
-  if (_snxSatCodeBiasMap.contains(prn)) {
-    char sys = prn.at(0).toLatin1();
-    QMap<QString, double>& cb = _snxSatCodeBiasMap[prn]->_biasMap;
-    switch (sys) {
-      case 'G':
-        if  (cb.contains("C1W-C2W")) {
-          double dcb_1W2W = cb.find("C1W-C2W").value();
-          rnxType2ch = "1W";
-          value =  aIF2 * dcb_1W2W;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          rnxType2ch = "2W";
-          value = -aIF1 * dcb_1W2W;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          if   (cb.contains("C1C-C1W")) {
-            double dcb_1C1W = cb.find("C1C-C1W").value();
-            rnxType2ch = "1C";
-            value = dcb_1C1W + aIF2 * dcb_1W2W;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            if  (cb.contains("C1C-C5Q")) {
-              double dcb_1C5Q = cb.find("C1C-C5Q").value();
-              rnxType2ch = "5Q";
-              value = dcb_1C1W + aIF2 * dcb_1W2W - dcb_1C5Q;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-            if  (cb.contains("C1C-C5X")) {
-              double dcb_1C5X = cb.find("C1C-C5X").value();
-              rnxType2ch = "5X";
-              value = dcb_1C1W + aIF2 * dcb_1W2W - dcb_1C5X;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-          }
-          else {
-            if  (cb.contains("C1C-C2W")) {
-              double dcb_1C2W = cb.find("C1C-C2W").value();
-              rnxType2ch = "1C";
-              value = dcb_1C2W - aIF1 * dcb_1W2W;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-          }
-          if   (cb.contains("C2C-C2W")) {
-            double dcb_2C2W = cb.find("C2C-C2W").value();
-            rnxType2ch = "2C";
-            value = dcb_2C2W - aIF1 * dcb_1W2W;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-          if   (cb.contains("C2W-C2S")) {
-            double dcb_2W2S = cb.find("C2W-C2S").value();
-            rnxType2ch = "2S";
-            value = -aIF1 * dcb_1W2W - dcb_2W2S;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-          if   (cb.contains("C2W-C2L")) {
-            double dcb_2W2L = cb.find("C2W-C2L").value();
-            rnxType2ch = "2L";
-            value = -aIF1 * dcb_1W2W - dcb_2W2L;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-          if   (cb.contains("C2W-C2X")) {
-            double dcb_2W2X = cb.find("C2W-C2X").value();
-            rnxType2ch = "2X";
-            value = -aIF1 * dcb_1W2W - dcb_2W2X;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-        }
-        break;
-      case 'R':
-        if  (cb.contains("C1P-C2P")) {
-          double dcb_1P2P = cb.find("C1P-C2P").value();
-          rnxType2ch = "1P";
-          value =  aIF2 * dcb_1P2P;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          rnxType2ch = "2P";
-          value = -aIF1 * dcb_1P2P;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          if   (cb.contains("C1C-C1P")) {
-            double dcb_1C1P = cb.find("C1C-C1P").value();
-            rnxType2ch = "1C";
-            value = dcb_1C1P + aIF2 * dcb_1P2P;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-          if   (cb.contains("C2C-C2P")) {
-            double dcb_2C2P = cb.find("C2C-C2P").value();
-            rnxType2ch = "2C";
-            value = dcb_2C2P - aIF1 * dcb_1P2P;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-        }
-        break;
-      case 'E':
-        if  (cb.contains("C1C-C5Q")) {
-          double dcb_1C5Q = cb.find("C1C-C5Q").value();
-          rnxType2ch = "5Q";
-          value = -aIF1 * dcb_1C5Q;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          rnxType2ch = "1C";
-          value =  aIF2 * dcb_1C5Q;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          if  (cb.contains("C1C-C6C")) {
-            double dcb_1C6C = cb.find("C1C-C6C").value();
-            rnxType2ch = "6C";
-            value = aIF2 * dcb_1C5Q - dcb_1C6C;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-          if  (cb.contains("C1C-C7Q")) {
-            double dcb_1C7Q = cb.find("C1C-C7Q").value();
-            rnxType2ch = "7Q";
-            value = aIF2 * dcb_1C5Q - dcb_1C7Q;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-          if  (cb.contains("C1C-C8Q")) {
-            double dcb_1C8Q = cb.find("C1C-C8Q").value();
-            rnxType2ch = "8Q";
-            value = aIF2 * dcb_1C5Q - dcb_1C8Q;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-        }
-        break;
-      case 'C':
-        if  (cb.contains("C2I-C6I")) {
-          double dcb_2I6I = cb.find("C2I-C6I").value();
-          rnxType2ch = "2I";
-          value =  aIF2 * dcb_2I6I;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          rnxType2ch = "6I";
-          value = -aIF1 * dcb_2I6I;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          if (cb.contains("C2I-C7I")) {
-            double dcb_2I7I = cb.find("C2I-C7I").value();
-            rnxType2ch = "7I";
-            value = aIF2 * dcb_2I6I - dcb_2I7I;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-          if (cb.contains("C1X-C6I")) {
-            double dcb_1X6I = cb.find("C1X-C6I").value();
-            rnxType2ch = "1X";
-            value = dcb_1X6I - aIF1 * dcb_2I6I;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            if (cb.contains("C1X-C7Z")) {
-              double dcb_1X7Z = cb.find("C1X-C7Z").value();
-              rnxType2ch = "7Z";
-              value = dcb_1X6I - aIF1 * dcb_2I6I - dcb_1X7Z;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-            if (cb.contains("C1X-C8X")) {
-              double dcb_1X8X = cb.find("C1X-C8X").value();
-              rnxType2ch = "8X";
-              value = dcb_1X6I - aIF1 * dcb_2I6I - dcb_1X8X;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-            if (cb.contains("C1X-C5X")) {
-              double dcb_1X5X = cb.find("C1X-C5X").value();
-              rnxType2ch = "5X";
-              value = dcb_1X6I - aIF1 * dcb_2I6I - dcb_1X5X;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-          }
-          if (cb.contains("C1P-C6I")) {
-            double dcb_1P6I = cb.find("C1P-C6I").value();
-            rnxType2ch = "1P";
-            value = dcb_1P6I - aIF1 * dcb_2I6I;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            if (cb.contains("C1P-C5P")) {
-              double dcb_1P5P = cb.find("C1P-C5P").value();
-              rnxType2ch = "5P";
-              value = dcb_1P6I - aIF1 * dcb_2I6I - dcb_1P5P;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-          }
-          if (cb.contains("C1D-C6I")) {
-            double dcb_1D6I = cb.find("C1D-C6I").value();
-            rnxType2ch = "1D";
-            value = dcb_1D6I - aIF1 * dcb_2I6I;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            if (cb.contains("C1D-C5D")) {
-              double dcb_1D5D = cb.find("C1D-C5D").value();
-              rnxType2ch = "5D";
-              value = dcb_1D6I - aIF1 * dcb_2I6I - dcb_1D5D;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-          }
-        }
-        break;
-      case 'J':
-        if (cb.contains("C1C-C2L")) {
-          double dcb_1C2L = cb.find("C1C-C2L").value();
-          rnxType2ch = "1C";
-          value =  aIF2 * dcb_1C2L;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          rnxType2ch = "2L";
-          value = -aIF1 * dcb_1C2L;
-          setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          if (cb.contains("C1C-C1X")) {
-            double dcb_1C1X = cb.find("C1C-C1X").value();
-            rnxType2ch = "1X";
-            value =  aIF2 * dcb_1C2L - dcb_1C1X;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            if (cb.contains("C1X-C2X")) {
-              double dcb_1X2X = cb.find("C1X-C2X").value();
-              rnxType2ch = "2X";
-              value =  aIF2 * dcb_1C2L - dcb_1C1X - dcb_1X2X;
-              setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-            }
-          }
-          if (cb.contains("C1C-C5X")) {
-            double dcb_1C5X = cb.find("C1C-C5X").value();
-            rnxType2ch = "5X";
-            value =  aIF2 * dcb_1C2L - dcb_1C5X;
-            setSsrSatCodeBias(rnxType2ch, value, satCodeBias);
-          }
-        }
-        break;
-      default:
-        break;
-    }
-  }
-  return;
+  if (!_snxSatCodeBiasMap.contains(prn)) {
+    return;
+  }
+  char sys = prn.at(0).toLatin1();
+  t_ssrRefSig::t_pair pair;
+  if (!t_ssrRefSig::get(sys, pair)) {
+    return;
+  }
+  const QString ref1 = QString::fromStdString(pair.sig1());
+  const QString ref2 = QString::fromStdString(pair.sig2());
+  const QMap<QString, double>& cb = _snxSatCodeBiasMap[prn]->_biasMap;
+  auto dcb = [&cb](const QString& sig1, const QString& sig2, double& value) {
+    QString key = "C" + sig1 + "-C" + sig2;
+    if (cb.contains(key)) {
+      value = cb[key];
+      return true;
+    }
+    return false;
+  };
+
+  QMap<QString, double> osb;
+  QStringList           order;
+
+  // Default reference signals: the DCB of the reference signals and the
+  // DCBs of the IGS conventions, evaluated in one pass as before
+  if (t_ssrRefSig::isDefault(sys)) {
+    const QVector<QPair<QString, QString> >& dcbs = defaultDcbs(sys);
+    double dcbRef;
+    if (dcbs.isEmpty() || !dcb(ref1, ref2, dcbRef)) {
+      return;
+    }
+    osb[ref1] =  aIF2 * dcbRef;
+    osb[ref2] = -aIF1 * dcbRef;
+    if (sys == 'E') {   // order of the output as before
+      order << ref2 << ref1;
+    }
+    else {
+      order << ref1 << ref2;
+    }
+    for (const QPair<QString, QString>& dd : dcbs) {
+      double value;
+      if (!dcb(dd.first, dd.second, value)) {
+        continue;
+      }
+      if (osb.contains(dd.first) && !osb.contains(dd.second)) {
+        osb[dd.second] = osb[dd.first] - value;
+        order << dd.second;
+      }
+      else if (osb.contains(dd.second) && !osb.contains(dd.first)) {
+        osb[dd.first] = value + osb[dd.second];
+        order << dd.first;
+      }
+    }
+  }
+
+  // Other reference signals: all DCBs of the satellite, the DCB of the
+  // reference signals may also be derived via other signals
+  else {
+    QMap<QString, double> rel;   // OSB - OSB_ref1
+    QStringList           relOrder;
+    rel[ref1] = 0.0;
+    relOrder << ref1;
+    bool changed = true;
+    while (changed) {
+      changed = false;
+      QMapIterator<QString, double> it(cb);
+      while (it.hasNext()) {
+        it.next();
+        QStringList hlp = it.key().split('-');
+        if (hlp.size() != 2 || hlp[0].size() != 3 || hlp[1].size() != 3) {
+          continue;
+        }
+        QString sigA = hlp[0].mid(1);
+        QString sigB = hlp[1].mid(1);
+        if (rel.contains(sigA) && !rel.contains(sigB)) {
+          rel[sigB] = rel[sigA] - it.value();
+          relOrder << sigB;
+          changed = true;
+        }
+        else if (rel.contains(sigB) && !rel.contains(sigA)) {
+          rel[sigA] = rel[sigB] + it.value();
+          relOrder << sigA;
+          changed = true;
+        }
+      }
+    }
+    if (!rel.contains(ref2)) {
+      return;
+    }
+    double osbRef1 = -aIF2 * rel[ref2];
+    order << ref1 << ref2;
+    for (const QString& sig : relOrder) {
+      osb[sig] = osbRef1 + rel[sig];
+      if (sig != ref1 && sig != ref2) {
+        order << sig;
+      }
+    }
+  }
+
+  for (const QString& sig : order) {
+    setSsrSatCodeBias(sig.toStdString(), osb[sig], satCodeBias);
+  }
 }
 
Index: /trunk/BNC/src/combination/bncbiassnx.h
===================================================================
--- /trunk/BNC/src/combination/bncbiassnx.h	(revision 11086)
+++ /trunk/BNC/src/combination/bncbiassnx.h	(revision 11087)
@@ -58,5 +58,5 @@
 class bncBiasSnx {
  public:
-  bncBiasSnx();
+  bncBiasSnx(bool allGnss = false); // allGnss: all systems, not only the combined ones
   ~bncBiasSnx();
   t_irc readFile(const QString& fileName);
Index: /trunk/BNC/src/combination/bnccomb.cpp
===================================================================
--- /trunk/BNC/src/combination/bnccomb.cpp	(revision 11086)
+++ /trunk/BNC/src/combination/bnccomb.cpp	(revision 11087)
@@ -206,4 +206,13 @@
 
   connect(this, SIGNAL(newMessage(QByteArray,bool)), BNC_CORE, SLOT(slotMessage(const QByteArray,bool)));
+
+  // Reference signals of the clocks
+  for (const QString& err : t_ssrRefSig::init()) {
+    emit newMessage(("bncComb: " + err).toLatin1(), true);
+  }
+  if (!t_ssrRefSig::allDefault()) {
+    emit newMessage(("bncComb: reference signals of the clocks " + t_ssrRefSig::currentString()).toLatin1(), true);
+  }
+
   connect(BNC_CORE, SIGNAL(providerIDChanged(QString)), this, SLOT(slotProviderIDChanged(QString)));
   connect(BNC_CORE, SIGNAL(newOrbCorrections(QList<t_orbCorr>)), this, SLOT(slotNewOrbCorrections(QList<t_orbCorr>)));
Index: /trunk/BNC/src/combination/bnccomb.h
===================================================================
--- /trunk/BNC/src/combination/bnccomb.h	(revision 11086)
+++ /trunk/BNC/src/combination/bnccomb.h	(revision 11087)
@@ -18,4 +18,5 @@
 #include "bncconst.h"
 #include "bncoutf.h"
+#include "ssrRefSig.h"
 
 class bncRtnetDecoder;
@@ -176,51 +177,31 @@
     enum type {dummy = 0, c1, c2, cIF};
 
+    // reference signals of the clocks, see t_ssrRefSig (option ssrRefSignals)
     static t_frequency::type toFreq(char sys, type tt) {
+      t_ssrRefSig::t_pair pair;
+      if (!t_ssrRefSig::get(sys, pair)) {
+        return t_frequency::dummy;
+      }
       switch (tt) {
-      case c1:
-        if      (sys == 'G') return t_frequency::G1;
-        else if (sys == 'R') return t_frequency::R1;
-        else if (sys == 'E') return t_frequency::E1;
-        else if (sys == 'C') return t_frequency::C2;
-        else if (sys == 'J') return t_frequency::J1;
-        else if (sys == 'S') return t_frequency::S1;
-        else                 return t_frequency::dummy;
-      case c2:
-        if      (sys == 'G') return t_frequency::G2;
-        else if (sys == 'R') return t_frequency::R2;
-        else if (sys == 'E') return t_frequency::E5;
-        else if (sys == 'C') return t_frequency::C6;
-        else if (sys == 'J') return t_frequency::J2;
-        else if (sys == 'S') return t_frequency::S5;
-        else                 return t_frequency::dummy;
-      case dummy:
-      case cIF:
-        return t_frequency::dummy;
+        case c1: return pair.frq1;
+        case c2: return pair.frq2;
+        case dummy:
+        case cIF:
+          return t_frequency::dummy;
       }
       return t_frequency::dummy;
     }
 
-    static char toAttrib(char sys, type LC) {
-      switch (LC) {
-        case c1:
-          if      (sys == 'G') return 'W';
-          else if (sys == 'R') return 'P';
-          else if (sys == 'E') return 'C';
-          else if (sys == 'C') return 'I';
-          else if (sys == 'J') return 'C';
-          else if (sys == 'S') return 'C';
-          break;
-        case c2:
-          if      (sys == 'G') return 'W';
-          else if (sys == 'R') return 'P';
-          else if (sys == 'E') return 'Q';
-          else if (sys == 'C') return 'I';
-          else if (sys == 'J') return 'L';
-          else if (sys == 'S') return 'Q';
-          break;
-      case dummy:
-      case cIF:
+    static char toAttrib(char sys, type tt) {
+      t_ssrRefSig::t_pair pair;
+      if (!t_ssrRefSig::get(sys, pair)) {
         return '_';
-        break;
+      }
+      switch (tt) {
+        case c1: return pair.att1;
+        case c2: return pair.att2;
+        case dummy:
+        case cIF:
+          return '_';
       }
       return '_';
Index: /trunk/BNC/src/src.pri
===================================================================
--- /trunk/BNC/src/src.pri	(revision 11086)
+++ /trunk/BNC/src/src.pri	(revision 11087)
@@ -47,5 +47,5 @@
               -lnewmat -lqwtpolar -lqwt
 
-HEADERS = bnchelp.html bncgetthread.h ssrQc.h    bncwindow.h   bnctabledlg.h  \
+HEADERS = bnchelp.html bncgetthread.h ssrQc.h ssrRefSig.h bncwindow.h   bnctabledlg.h  \
           bnccaster.h bncrinex.h bnccore.h bncutils.h   bnchlpdlg.h   \
           bncconst.h bnchtml.h bnctableitem.h bnczerodecoder.h        \
@@ -89,5 +89,5 @@
 win32:HEADERS += serial/win_qextserialport.h
 
-SOURCES = bncgetthread.cpp ssrQc.cpp  bncwindow.cpp bnctabledlg.cpp             \
+SOURCES = bncgetthread.cpp ssrQc.cpp ssrRefSig.cpp bncwindow.cpp bnctabledlg.cpp             \
           bnccaster.cpp bncrinex.cpp bnccore.cpp bncutils.cpp         \
           bncconst.cpp bnchtml.cpp bnchlpdlg.cpp bnctableitem.cpp     \
Index: /trunk/BNC/src/ssrQc.cpp
===================================================================
--- /trunk/BNC/src/ssrQc.cpp	(revision 11086)
+++ /trunk/BNC/src/ssrQc.cpp	(revision 11087)
@@ -42,4 +42,6 @@
 
 #include "ssrQc.h"
+#include "ssrRefSig.h"
+#include "combination/bncbiassnx.h"
 #include "bnccore.h"
 #include "bncutils.h"
@@ -126,4 +128,23 @@
 
   connect(this, SIGNAL(newMessage(QByteArray,bool)), BNC_CORE, SLOT(slotMessage(const QByteArray,bool)));
+  for (const QString& err : t_ssrRefSig::init()) {
+    emit newMessage(("SSR QC: " + err).toLatin1(), true);
+  }
+  // reference code biases from a Bias SINEX file (optional)
+  _bsxRef = 0;
+  bncSettings settings;
+  QString bsxFile = settings.value("ssrQcBiasFile").toString();
+  expandEnvVar(bsxFile);
+  if (!bsxFile.isEmpty()) {
+    _bsxRef = new bncBiasSnx(true);
+    if (_bsxRef->readFile(bsxFile) == success) {
+      _bsxName = QFileInfo(bsxFile).fileName();
+    }
+    else {
+      emit newMessage(("SSR QC: wrong Bias SINEX file " + bsxFile).toLatin1(), true);
+      delete _bsxRef;
+      _bsxRef = 0;
+    }
+  }
   connect(BNC_CORE, SIGNAL(newOrbCorrections(QList<t_orbCorr>)),
           this, SLOT(slotNewOrbCorrections(QList<t_orbCorr>)), Qt::DirectConnection);
@@ -183,5 +204,6 @@
            << " completeness w.r.t. healthy broadcast ephemerides," << endl
            << "IOD matching, code and phase bias signals, yaw angle, code biases of the reference signals, clock jumps > "
-           << MAX_CLK_JUMP << " m, largest orbit corrections" << endl;
+           << MAX_CLK_JUMP << " m, largest orbit corrections" << endl
+           << "Reference signals of the clocks: " << t_ssrRefSig::currentString().toStdString() << endl;
     }
     _outs[staID]     = out;
@@ -199,4 +221,5 @@
     delete out;
   }
+  delete _bsxRef;
 }
 
@@ -207,16 +230,17 @@
 }
 
-// Reference signals (IGS conventions, as in the combination)
+// Reference signals of the clocks (as in the combination, option ssrRefSignals)
 ////////////////////////////////////////////////////////////////////////////
 bool t_ssrQc::refSignals(char sys, QString& sig1, QString& sig2,
                          t_frequency::type& frq1, t_frequency::type& frq2) {
-  switch (sys) {
-    case 'G': sig1 = "1W"; sig2 = "2W"; frq1 = t_frequency::G1; frq2 = t_frequency::G2; return true;
-    case 'R': sig1 = "1P"; sig2 = "2P"; frq1 = t_frequency::R1; frq2 = t_frequency::R2; return true;
-    case 'E': sig1 = "1C"; sig2 = "5Q"; frq1 = t_frequency::E1; frq2 = t_frequency::E5; return true;
-    case 'C': sig1 = "2I"; sig2 = "6I"; frq1 = t_frequency::C2; frq2 = t_frequency::C6; return true;
-    case 'J': sig1 = "1C"; sig2 = "2L"; frq1 = t_frequency::J1; frq2 = t_frequency::J2; return true;
-  }
-  return false;
+  t_ssrRefSig::t_pair pair;
+  if (!t_ssrRefSig::get(sys, pair)) {
+    return false;
+  }
+  sig1 = QString::fromStdString(pair.sig1());
+  sig2 = QString::fromStdString(pair.sig2());
+  frq1 = pair.frq1;
+  frq2 = pair.frq2;
+  return true;
 }
 
@@ -376,4 +400,7 @@
       _codeSignals[staSys].insert(sig);
       values[sig] = frqBias._value;
+    }
+    if (!values.isEmpty()) {
+      _cbLast[QString::fromStdString(bias._staID)][prn] = values;
     }
     t_satStat& stat = _satStat[QString::fromStdString(bias._staID) + ' ' + prn];
@@ -425,4 +452,253 @@
     if (yawProvided) {
       stat.numYaw++;
+    }
+  }
+}
+
+// Reference DCBs (bias(base) - bias(signal)) of a satellite: median of the
+// other ACs (one vote per AC, own AC left out, combinations not voting) or
+// the selected reference stream, or from the Bias SINEX file
+////////////////////////////////////////////////////////////////////////////
+bool t_ssrQc::refDcbs(const QString& staID, const QString& prn, const QString& base, bool fromFile,
+                      QMap<QString, double>& dcbs, const QString& onlyAC, QMap<QString, int>* numVotes) {
+  dcbs.clear();
+  if (numVotes) {
+    numVotes->clear();
+  }
+  if (fromFile) {
+    if (!_bsxRef) {
+      return false;
+    }
+    char sys = prn[0].toLatin1();
+    t_ssrRefSig::t_pair pair;
+    if (!t_ssrRefSig::get(sys, pair)) {
+      return false;
+    }
+    // the DCBs do not depend on the coefficients (as long as they add up to 1)
+    double f1 = t_CST::freq(pair.frq1, 0.0);
+    double f2 = t_CST::freq(pair.frq2, 0.0);
+    double aIF1 =  f1 * f1 / (f1 * f1 - f2 * f2);
+    double aIF2 = -f2 * f2 / (f1 * f1 - f2 * f2);
+    t_satCodeBias satCodeBias;
+    _bsxRef->determineSsrSatCodeBiases(prn, aIF1, aIF2, satCodeBias);
+    QMap<QString, double> osb;
+    for (const t_frqCodeBias& bb : satCodeBias._bias) {
+      osb[QString::fromStdString(bb._rnxType2ch)] = bb._value;
+    }
+    if (!osb.contains(base)) {
+      return false;
+    }
+    for (auto it = osb.constBegin(); it != osb.constEnd(); ++it) {
+      if (it.key() != base) {
+        dcbs[it.key()] = osb[base] - it.value();
+      }
+    }
+    return !dcbs.isEmpty();
+  }
+
+  // per AC: DCBs of its streams
+  QMap<QString, QMap<QString, QVector<double> > > acDcbs;  // AC, signal
+  for (auto itS = _cbLast.constBegin(); itS != _cbLast.constEnd(); ++itS) {
+    const QString& sta = itS.key();
+    if (sta == staID) {
+      continue;
+    }
+    if (_reference == "CONSENSUS") {
+      if (!votes(sta) || acOf(sta) == acOf(staID) || (!onlyAC.isEmpty() && acOf(sta) != onlyAC)) {
+        continue;
+      }
+    }
+    else if (sta != _reference) {
+      continue;
+    }
+    if (!itS.value().contains(prn)) {
+      continue;
+    }
+    const QMap<QString, double>& bb = itS.value()[prn];
+    if (!bb.contains(base)) {
+      continue;
+    }
+    for (auto it = bb.constBegin(); it != bb.constEnd(); ++it) {
+      if (it.key() != base) {
+        acDcbs[acOf(sta)][it.key()].append(bb[base] - it.value());
+      }
+    }
+  }
+  QMap<QString, QVector<double> > votesPerSig;
+  for (auto itA = acDcbs.constBegin(); itA != acDcbs.constEnd(); ++itA) {
+    for (auto it = itA.value().constBegin(); it != itA.value().constEnd(); ++it) {
+      votesPerSig[it.key()].append(median(it.value()));
+    }
+  }
+  for (auto it = votesPerSig.constBegin(); it != votesPerSig.constEnd(); ++it) {
+    dcbs[it.key()] = median(it.value());
+    if (numVotes) {
+      (*numVotes)[it.key()] = it.value().size();
+    }
+  }
+  return !dcbs.isEmpty();
+}
+
+// Code bias check: the DCBs of the stream (relative to the first reference
+// signal, otherwise to another common signal) compared with a reference;
+// DCBs do not depend on the clock datum. A sign opposite to the reference
+// for most DCBs indicates the SINEX instead of the SSR sign convention.
+// References: the Bias SINEX file, the selected reference stream, or each
+// other AC separately (a median of two ACs cannot decide which one is wrong)
+////////////////////////////////////////////////////////////////////////////
+void t_ssrQc::compareCodeBiases(const QString& staID, char sys, QStringList& lines) {
+  const double MIN_DCB  = 0.3;   // [m] DCBs used for the sign check
+  const double MIN_DEV  = 0.5;   // [m] deviating DCBs: > max(MIN_DEV, DEV_FACTOR x median)
+  const int    MIN_VOTE = 3;     // ACs needed for a consensus DCB (deviating satellites)
+  if (!_cbLast.contains(staID)) {
+    return;
+  }
+  t_ssrRefSig::t_pair pair;
+  if (!t_ssrRefSig::get(sys, pair)) {
+    return;
+  }
+  const QString ref1 = QString::fromStdString(pair.sig1());
+
+  class t_cmp {
+   public:
+    unsigned numSat = 0, numSign = 0, numOpp = 0;
+    double   sumSqr = 0.0;
+    QVector<double> absDiffs;
+    QMap<QString, QPair<QString, double> > maxDiff;   // PRN: signal, difference
+    QSet<QString> bases;
+    bool opposite() const {return numSign >= 5 && numOpp > numSign / 2;}
+    double rms() const {return absDiffs.isEmpty() ? 0.0 : sqrt(sumSqr / absDiffs.size());}
+  };
+  // mode 0: file, 1: reference stream or median of the other ACs, 2: one other AC
+  auto compare = [&](int mode, const QString& onlyAC) {
+    t_cmp cc;
+    for (auto itP = _cbLast[staID].constBegin(); itP != _cbLast[staID].constEnd(); ++itP) {
+      const QString& prn = itP.key();
+      if (prn[0].toLatin1() != sys) {
+        continue;
+      }
+      const QMap<QString, double>& bb = itP.value();
+      QStringList candidates = QStringList() << ref1 << bb.keys();
+      QMap<QString, double> dcbs;
+      QMap<QString, int>    numVotes;
+      QString base;
+      for (const QString& cand : candidates) {
+        if (bb.contains(cand) && refDcbs(staID, prn, cand, mode == 0, dcbs, onlyAC, &numVotes)) {
+          base = cand;
+          break;
+        }
+      }
+      if (base.isEmpty()) {
+        continue;
+      }
+      bool used = false;
+      for (auto it = dcbs.constBegin(); it != dcbs.constEnd(); ++it) {
+        if (!bb.contains(it.key())) {
+          continue;
+        }
+        if (mode == 1 && _reference == "CONSENSUS" && numVotes.value(it.key()) < MIN_VOTE) {
+          continue;
+        }
+        double dcb  = bb[base] - bb[it.key()];
+        double diff = dcb - it.value();
+        used = true;
+        cc.sumSqr += diff * diff;
+        cc.absDiffs.append(fabs(diff));
+        if (fabs(diff) > fabs(cc.maxDiff.value(prn).second)) {
+          cc.maxDiff[prn] = qMakePair("C" + it.key(), diff);
+        }
+        if (fabs(it.value()) >= MIN_DCB) {
+          cc.numSign++;
+          if (dcb * it.value() < 0.0) {
+            cc.numOpp++;
+          }
+        }
+      }
+      if (used) {
+        cc.numSat++;
+        cc.bases.insert("C" + base);
+      }
+    }
+    return cc;
+  };
+  auto baseStr = [](const t_cmp& cc) {
+    QStringList ll = cc.bases.values();
+    ll.sort();
+    return ll.join("/");
+  };
+  auto deviating = [&](const t_cmp& cc, const QString& refName) {
+    if (cc.absDiffs.isEmpty() || cc.opposite()) {
+      return;
+    }
+    double limit = std::max(MIN_DEV, DEV_FACTOR * median(cc.absDiffs));
+    QStringList devs;
+    for (auto it = cc.maxDiff.constBegin(); it != cc.maxDiff.constEnd(); ++it) {
+      if (fabs(it.value().second) > limit) {
+        devs << QString("%1(%2 %3)").arg(it.key()).arg(it.value().first).arg(it.value().second, 0, 'f', 2);
+      }
+    }
+    if (!devs.isEmpty()) {
+      lines << QString("satellites with DCBs deviating from %1 [m]: %2").arg(refName).arg(devs.join(" "));
+    }
+  };
+
+  // Bias SINEX file or the selected reference stream
+  for (int mode = 0; mode < 2; mode++) {
+    if ((mode == 0 && !_bsxRef) || (mode == 1 && _reference == "CONSENSUS")) {
+      continue;
+    }
+    QString refName = (mode == 0) ? QString("Bias SINEX file %1").arg(_bsxName) : _reference;
+    t_cmp cc = compare(mode, QString());
+    if (cc.absDiffs.isEmpty()) {
+      continue;
+    }
+    lines << QString("code biases compared with %1 (DCBs to %2, %3 satellites, %4 DCBs): RMS of the differences "
+                     "%5 m, opposite sign for %6 of %7 DCBs (|DCB| >= %8 m)")
+             .arg(refName).arg(baseStr(cc)).arg(cc.numSat).arg(cc.absDiffs.size())
+             .arg(cc.rms(), 0, 'f', 3).arg(cc.numOpp).arg(cc.numSign).arg(MIN_DCB, 0, 'f', 1);
+    if (cc.opposite()) {
+      lines << QString("code biases with the opposite sign of %1 for %2 of %3 DCBs - "
+                       "SINEX sign convention instead of the SSR one?").arg(refName).arg(cc.numOpp).arg(cc.numSign);
+    }
+    deviating(cc, refName);
+  }
+
+  // Each other AC separately, and the median of the other ACs for deviating satellites
+  if (_reference == "CONSENSUS") {
+    QSet<QString> acs;
+    for (auto itS = _cbLast.constBegin(); itS != _cbLast.constEnd(); ++itS) {
+      if (itS.key() != staID && votes(itS.key()) && acOf(itS.key()) != acOf(staID)) {
+        acs.insert(acOf(itS.key()));
+      }
+    }
+    QStringList acList = acs.values();
+    acList.sort();
+    QStringList items;
+    QStringList oppACs;
+    int numACs = 0;
+    QSet<QString> bases;
+    for (const QString& ac : acList) {
+      t_cmp cc = compare(2, ac);
+      if (cc.absDiffs.isEmpty()) {
+        continue;
+      }
+      numACs++;
+      bases.unite(cc.bases);
+      items << QString("%1 RMS %2 m, opposite sign %3/%4").arg(ac).arg(cc.rms(), 0, 'f', 2)
+                                                         .arg(cc.numOpp).arg(cc.numSign);
+      if (cc.opposite()) {
+        oppACs << ac;
+      }
+    }
+    if (numACs > 0) {
+      QStringList ll = bases.values();
+      ll.sort();
+      lines << QString("code biases compared with the other ACs (DCBs to %1): %2").arg(ll.join("/")).arg(items.join("; "));
+      if (numACs >= 2 && oppACs.size() > numACs / 2) {
+        lines << QString("code biases with the opposite sign of %1 of %2 other ACs (%3) - "
+                         "SINEX sign convention instead of the SSR one?")
+                 .arg(oppACs.size()).arg(numACs).arg(oppACs.join(" "));
+      }
+      deviating(compare(1, QString()), QString("the median of the other ACs (at least %1 ACs)").arg(MIN_VOTE));
     }
   }
@@ -865,4 +1141,5 @@
         }
       }
+      compareCodeBiases(staID, sys, lines);
       if (_phaseSignals.contains(staSys)) {
         QStringList sigs = _phaseSignals[staSys].values();
Index: /trunk/BNC/src/ssrQc.h
===================================================================
--- /trunk/BNC/src/ssrQc.h	(revision 11086)
+++ /trunk/BNC/src/ssrQc.h	(revision 11087)
@@ -14,4 +14,6 @@
 // each interval and at the end of the program.
 ////////////////////////////////////////////////////////////////////////////
+class bncBiasSnx;
+
 class t_ssrQc : public QObject {
  Q_OBJECT
@@ -133,4 +135,14 @@
   QMap<QString, QSet<QString> > _codeSignals;  // key "STA SYS": signals with code biases
   QMap<QString, QSet<QString> > _phaseSignals; // key "STA SYS": signals with phase biases
+
+  // Code bias check: DCBs of each stream compared with the other ACs (or
+  // the reference stream) and optionally with a Bias SINEX file
+  void   compareCodeBiases(const QString& staID, char sys, QStringList& lines);
+  bool   refDcbs(const QString& staID, const QString& prn, const QString& base, bool fromFile,
+                 QMap<QString, double>& dcbs, const QString& onlyAC = QString(),
+                 QMap<QString, int>* numVotes = 0);
+  QMap<QString, QMap<QString, QMap<QString, double> > > _cbLast; // stream, PRN, signal: latest code bias [m]
+  bncBiasSnx*               _bsxRef;    // reference code biases (option ssrQcBiasFile)
+  QString                   _bsxName;
 };
 
Index: /trunk/BNC/tests/README.md
===================================================================
--- /trunk/BNC/tests/README.md	(revision 11086)
+++ /trunk/BNC/tests/README.md	(revision 11087)
@@ -66,4 +66,7 @@
   encodings, missing reference-signal biases, inconsistent biases and
   persistent outliers. The longest case (about 4 min).
+* `combi_refsig`: `combi` with non-default reference signals (`ssrRefSignals
+  C:1P&5P`): BDS clock referral and code biases from the Bias SINEX file on
+  C1P/C5P (generic DCB derivation), CLK/BIA and the combination log compared.
 * Upload (`upload_*`; 16, 19, 20) from `data/upload_rtnet_5min.raw` (RTNET engine
   output + BCEP00BKG0): BNC uploads to a local fake caster
@@ -84,5 +87,6 @@
 * `ssr_qc`, `ssr_qc_select`: SSR QC (31_SsrQC, one file per stream, 5 min interval;
   all streams resp. `ssrQcStreams SSRA00CNE1,SSRA00GMV0`) of the 7 AC streams of
-  `data/combi_multi_261002_5min.raw`.
+  `data/combi_multi_261002_5min.raw`. `ssr_qc_bias` adds the CAS DCB file as
+  'QC Bias File' (code bias check against the other ACs and the file).
 
 ## Adding a replay case
