How to obtain trades from the websocket transaction feed? I am having a hard time figuring out how to extract trade information (i.e. a payment relative to an offer) from the Ripple websocket transaction feed.

From my current understanding, I guess there would be a way to match a payment to an offer, or maybe to know that an offer has been cancelled because it has been successful. This would probably work.

But I can't see a way to achieve any of this currently.
For a program (a client, a charting site, a simple order book display, etc) to efficiently track changes it can subscribe to a stream of transactions related to just what it's interested in, a set of accounts, a set of order books, etc. Alternatively a system can subscribe to all transactions or fetch individual closed ledgers and their associated transactions.

Whatever way a program gets transactions, each transaction includes a set of meta data describing all the changes it made within the ledger that included it. For any transaction that executes any part of a trade offer it will included meta data that references the trade offer, and describes how it is changed (how much is consumed, if the offer is to be removed, etc).

Here is an example recent transaction with meta data. These can easily be looked at via a site like https://ripple.com/tools/info/ or https://ripple.com/tools/api/

{
  Account : "rGTfnwAqmPG5CN9vgzfubDQA4CkCpeK9zZ",
  Amount : { /* currency, issuer, ... */ },
  Destination : "rMDnTSD3TUizf2qvd9oGdCz3BXLheJyYtP",
  Fee : "15",
  Flags : 0,
  LastLedgerSequence : 6005165,
  Paths : [ /* 3 items */ ],
  SendMax : { /* currency, issuer, ... */ },
  Sequence : 3539,
  SigningPubKey : "02A25443D8FC411A395B626760062F506BF44FDA7CBFE004B1F2A7D2052CCEBC33",
  TransactionType : "Payment",
  TxnSignature : "3046022100D806A3FF5F2002F55BA48C4824CF8AEC70741411383CD79037012A85C3F92A9502210084335505CA91F28DDE04290FB7A46318F6337E2522D46731403BE686176F3A12",
  hash : "14A60155B276A78ED29FF11A9EE8E63AC1FCBED50CCE1AB30642B06E8D0E00D8",
  inLedger : 6005158,
  ledger_index : 6005158,
  meta : {
        AffectedNodes : [
              {
                    ModifiedNode : {
                          FinalFields : {
                                Account : "rH1hbPUfvhmqkEE3AWi77CZNexJyTGE6SF",
                                BookDirectory : "4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5D04F0F1A6DD3F05",
                                BookNode : "0000000000000000",
                                Flags : 0,
                                OwnerNode : "0000000000000000",
                                Sequence : 71,
                                TakerGets : {
                                      currency : "USD",
                                      issuer : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
                                      value : "221.8195734530501"
                                },
                                TakerPays : "30851122872"
                          },
                          LedgerEntryType : "Offer",
                          LedgerIndex : "0346EED5470F7A69B0A8DBF4B403DDD5CFD80F4A588555A934F00B09B4CE7AD2",
                          PreviousFields : {
                                TakerGets : {
                                      currency : "USD",
                                      issuer : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
                                      value : "222.82157345305"
                                },
                                TakerPays : "30990483095"
                          },
                          PreviousTxnID : "B8613D4BE0C6EAA9F970CC2D580534B8A2B53F1BD844518BED4A73AA0FA52DFB",
                          PreviousTxnLgrSeq : 6005141
                    }
              },
              { /* ModifiedNode */ },
              { /* ModifiedNode */ },
              { /* DeletedNode */ },
              { /* ModifiedNode */ },
              { /* DeletedNode */ },
              { /* ModifiedNode */ },
              { /* ModifiedNode */ },
              { /* ModifiedNode */ },
              { /* ModifiedNode */ },
              { /* ModifiedNode */ },
              { /* ModifiedNode */ },
              { /* ModifiedNode */ }
        ],
        TransactionIndex : 0,
        TransactionResult : "tesSUCCESS"
  },
  validated : true
}


In this example, you can see that the transaction happens to be of type Payment but it modified a node with LedgerEntryType: offer. Those are what you'd look for (from any and all transaction types, "OfferCreate", "OfferCancel", "Payment", etc). Fields such as "BookDirectory" are mostly for internally finding the node with the ledger, a simple tool processing these JSON blobs could just look at the Account and Sequence fields to uniquely identify a specific offer. In this example the there is both a final TakerGets/TakerPays fields as well as previous; subtracting tells you how much of the offer was taken. (Such fields without a listed currency/issuer pair are XRP in drops, divide by 1e6 to get the amount of XRP.)

If a payment or crossing offer were to fully consume an existing offer, then you'd see a "DeletedNode" entry for it and would again need to use the previous/final fields to see how much of the offer was consumed (which could be zero if the offer was removed for other reasons, such as no longer funded or expired).

If you're trying to assemble all the ways a specific offer has changed through it's life you can track backwards via either the PreviousTxnID or the PreviousTxnLgrSeq fields.