Adding a new objective to Calypso Forward Ladder

I’ve been spending the last 6 weeks configuring the Forward Ladder in Calypso 11 in order to get a real-time view into FX risk from Opics.  While I actually did some work on the Forward Ladder analysis when I was back at Calypso, they did a complete rewrite for version 11 and I must say that, while I did encounter a few issues, it is well thought out and quite extensible.  As such, I thought I’d share how easy it is to plug in a new objective in the forward ladder.

The request was to display the discount factor (df, for short) which is unfortunately not available in the new ForwardLadder analysis.   While it was a column in one of the earlier incantations of the analysis (i.e., FwdLadder), it seems to have disappeared in this latest version.   We could have of course asked for a P6 enhancement request, but that probably means we’d have had to roll this out next year as opposed to this year, so that was a no go.   Digging into the code, I came across the Forward Ladder Registry, a class that allows you to plug in and/or modify the objectives and flow generation extremely easily.

The ForwardLadderRegistry class actually reads the configuration from an XML file included in the resources.jar called com/calypso/tk/risk/ForwardLadderGeneratorMappings.xml.  Here’s a snippet from that file:

<registry>
    <global>
        <objectiveGenerators>
            <generator>
                <objectiveClass>com.calypso.tk.risk.forwardladder.objective.ForwardLadderObjectiveCore</objectiveClass>
                <generatorClass>com.calypso.tk.risk.forwardladder.generator.DefaultCoreGenerator</generatorClass>
            </generator>
            <generator>
                <objectiveClass>com.calypso.tk.risk.forwardladder.objective.ForwardLadderObjectiveCash</objectiveClass>
                <generatorClass>com.calypso.tk.risk.forwardladder.generator.DefaultCashFlowAmountCashGenerator</generatorClass>
            </generator>
            ...
        </objectiveGenerators>
    </global>
    ...
</registry>

I decided to create a new objective called MarketData in order to bring in the df, as requested by the users.   Thus, I added the following lines to the xml file:

            ...
            <generator>
                <objectiveClass>com.myco.tk.risk.forwardladder.objective.ForwardLadderObjectiveMarketData</objectiveClass>
                <generatorClass>com.myco.tk.risk.forwardladder.generator.DefaultMarketDataGenerator</generatorClass>
            </generator>
            ...

That’s easy enough, right?   The code for the ForwardLadderObjectiveMarketData is trivial enough and you can easily implement it based on one of the other objectives supplied by Calypso.   The meat & bones, however, resides in the DefaultMarketDataGenerator which actually implements the ForwardLadderObjectiveGeneratorI interface.  (Incidentally, I’m not quite sure why Calypso forces you to implement 2 interfaces here.   I think one interface could easily have described the contract for both the objective metadata and the actual implementation!  Go figure…)

Here’s what the code looks like to display the discount factor:

public class DefaultMarketDataGenerator implements ForwardLadderObjectiveGeneratorI {
...
public void populateObjectiveData(ForwardLadderObjectiveI objective, FlowGenerationContextI context,
List<ForwardLadderFlowItem> flows) { Trade trade = context.getTrade(); PricingEnvBasedFlowGenerationContext peContext = (PricingEnvBasedFlowGenerationContext)context; for (ForwardLadderFlowItem flow : flows) { for (ColumnMetaData metaData : objective.getObjectiveColumnMetaData(context.getParams())) { String dataName = metaData.getName(); if (ForwardLadderObjectiveMarketData.CURRENCY_DF.equals(dataName)) { CashFlow cf = flow.getCashFlow(); String ccy = cf.getCurrency(); CurveZero curve = peContext.getPricingEnv().getPricerConfig().getDiscountZeroCurve(ccy); double df = 1.; try { df = curve.getDF(trade.getSettleDate(), QuoteSet.MID); Amount dfDV = new Amount(df, ForwardLadderAnalysisUtil.DEFAULT_CONVERSION_RATE_ROUNDING); ForwardLadderObjectiveData data = new ForwardLadderObjectiveData(metaData, dfDV); flow.setObjectiveData(dataName, data); } catch (Throwable t) { Log.error(LOG_CATEGORY, t); } } } } }
}

That just about does it, believe it or not!   The only thing is you’ll have to tweak one more resource bundle file or the ParamViewer will choke when trying to add the new objective.   Look for a file called com/calypso/bundles/apps/forwardladder/ForwardLadderObjectives.properties in the resources.jar and add the following properties at the end of that file:

objective.displayname.MarketData=Market Data
objective.help.MarketData=

As long as your classes and modified resource files come before Calypso’s in your classpath, you’ll be able to add this new objective to your Forward Ladder.

Adding custom Calypso packages by adding a new jar file

After my very interesting and enlightening attendance at The Server Side Java Symposium last month, I’ve decided to start exploring new technologies and how better do this than by creating some Open Source tools and libraries.  Since I’ve been working almost exclusively with the Calypso API for the last decade, it’s somewhat logical that I would start there.  I decided that my first project is to integrate Calypso with GridGain as Dispatcher to distribute Risk Analysis execution on the Grid.   Calypso has its own Dispatcher implementation, but they themselves acknowledge it’s not ready for prime-time.   They do sell an adapter to plug into DataSynapse as well, but I figured an Open-Source alternative would be a worthwhile tool for Calypso implementors.  Besides, once I get it all up and running, I want to play with Scala as it seems to integrate very easily with GridGain.

So last week I launched Eclipse and created a new project.   As I began creating a new package, com.steepi, I paused.   In order to plug my package into Calypso, I would need to implement CustomGetPackages in the calypsox.tk.util package.  But if I do that, how is a Calypso implementor going to use it?   After all, they most certainly already have their own instance of CustomGetPackages!   Now granted, they certainly could make the modification to their class to add my packages.   Still.  What if I truly wanted to provide a library that would attach my packages simply by adding a jar to the CLASSPATH?   This problem merited further investigation…

After a few hours of research, I found a solution by locating an undocumented feature of Calypso.   During startup, the AppStarter class will at some point try to instantiate calypsox.apps.main.UserStartup.   If an instance of this class is found by InstantiateUtil, the method start() will be called via reflection.   Just the hook I needed!   I could place my custom code there to attach my custom packages.

Wait, though…

How would I go about doing so?   Calypso’s API does not provide a method to add packages to InstantiateUtil.   It’s all done within a static block when the class is loaded.   Thankfully, I’ve encountered limitations with the Calypso API before and the way to circumvent this problem is to use Java reflection to render methods and fields accessible.   Here, then, is the code that does exactly what I wanted!  If you compile the following code and add it to your CLASSPATH, you’ll be able to attach custom packages to existing Calypso implementation projects that have their own CustomGetPackages implementation. It’s a nifty way to provide a third-party library on top of Calypso, don’t you think?

1:  package calypsox.apps.main;
2:
3:  import java.lang.reflect.Field;
4:  import java.util.Collections;
5:  import java.util.List;
6:
7:  import com.calypso.tk.core.Log;
8:  import com.calypso.tk.util.InstantiateUtil;
9:
10: public class UserStartup  {
11:     public void start() {
12:         // In order to attach our packages, we operate on
13:         // InstantiateUtil class using reflection so as to
14:         // reach through its encapsulation.
15:         Class clazz = InstantiateUtil.class;
16:         Field field;
17:         List packages = null;
18:         try {
19:             // Retrieve the _packages field in InstantiateUtil
20:             field = clazz.getDeclaredField("_packages");
21:             // Since this is a private field, we need to set it
22:             // to accessible so we can access it
23:             field.setAccessible(true);
24:             // Get the value of the field via reflection.
25:             // This is the actual List object
26:             packages = (List)field.get(clazz);
27:             // Add Steepi packages so they are available when
28:             // instantiating through reflection
29:             packages.add(0,"com.steepi");
30:         }
31:         catch (Throwable t) {
32:             Log.error("Error", "Unable to locate InstantiateUtil._packages via reflection.", t);
33:             return;
34:         }
35:         // TODO: Apply same logic to update _invertPackages field as well
36:     }
37: }

As noted inline, you’ll want to do the same to update the inverted packages field. I’ve omitted the code for brevity.

This solution should work with any version of Calypso prior to 11.1.   Who knows whether or not they will eventually remove this logic since it is, after all, undocumented.   Still, for the time being, it should be easier to deploy my packages in existing Calypso implementations without needing any code change.   Sweet!

Back in San Francisco!

I’m baaaack! This is an aerial view of the Carquinez bridge, near our home.

Actually we’ve been back for a few weeks now, but it’s been so crazy busy I haven’t had time to blog about it until now. My wife and I had talked about moving back up here for a few months but the idea became concrete when I received an offer from Calypso, my former employer. So here I am now, development manager for the FX and Treasury group. Simply put, I’m totally psyched!

As far as timing is concerned, it truly was impeccable. Within a month of my giving notice at Countrywide, the bad news started rolling in. I’m not too concerned for the team I worked with there, though, since they’re pretty well insulated from the layoffs. It’s a very small and solid team of IT professionals that are supporting the Calypso implementation for the whole organization. Countrywide Securities is actually one of the few subsidiaries that’s making serious money, and as they’re moving more and more financial products onto Calypso, I can’t imagine them getting rid of the IT team that keeps this all going!

We’re back in our house… You have no idea how happy we are!

Programming Fashion Faux-Pas

We’re releasing a new version of our code today so the last week’s basically been a free-for-all as far as fixing bugs and implementing enhancements. I had to push some additional Swaption data onto TIBCO for Blackrock to consume. So I had the pleasure of modifying an interface I’ve never worked on before. So I come across this:

if (product instanceof Swap) {
    ...
} else if (product instanceof TotalReturnSwap) {
    ...
} else if (product instanceof CDSABSIndex) {
    ...
}

Dude, are you kidding me? You ever heard of an interface? I think that right there tells me whether or not the person who wrote this code is a seasoned developer with good refactoring skills. I should remember to use that as an interview question. Frankly I wouldn’t hire a guy who codes like that…. It shows a complete lack of programming fashion sense.

Work’s been keeping me quite busy lately, actually. We are finally going live with CDS on ABX next week and I’m pretty pleased since I did pretty much 95% of the development for that. I got to get very intimate with the various credit events that can occur and how they should be handled. Actually Calypso’s not quite up to par in how it models CDS on ABX, at least as far as accounting is concerned. For accounting to get what it wants, a separate transfer needs to be created for each individual credit event. So if MarkIT says on a given month that ABX.HE.BBB-.06-1 has got an interest shortfall of 59.2, an interest reimbursement of 12.5, and a fixed correction of 15.1, Calypso needs to generate 3 individual transfers. As of 8.0.3, Calypso doesn’t even handle corrections at all. (Maybe they fixed that in 9.0?) Of course, the individual transfers can and should be netted together, but each one should generate its own transfer type and, further downstream, its own posting.

Anyway, it was interesting making this enhancement and seeing it trickle all the way down to Peoplesoft.

On the Cutting Edge of Finance

So by now everybody’s read about the subprime mortgage meltdown, right? Well thankfully Countrywide doesn’t seem to be so affected, but I’ve been reading some interesting analysis by Gretchen Morgenson over at the New York Times. She’s way sharp and although I frankly don’t know enough about all this, I tend to agree with her prognosis. Let’s just say it’s going to be a bumpy ride for the next 3 or 4 years when it comes to Real Estate in the U.S.

The thing is, you had all these mortgage lenders pretty much lending trillions of dollars to just about anyone who’d walk into their office, qualified or not. This of course drove housing prices way up since you had flippers gobbling up houses like crazy. Demand went through the roof but supply remained a constant. So now these lenders all have tons of expected payments but they want to liquify their assets, so they securitize these loans into asset backed securities. Subprime loans magically become BBB and BBB- asset tranches. Investors start buying these up since they offer a pretty good return on investment. Hell, you’ve even got investors borrowing Yen, converting into dollars just so they can gobble up all these asset backed securities.

Fast forward to the present. The Fed’s had to raise its lending rate quite a bit to curb inflation. Inflation is a risk thanks to instability in the Middle East and the growing demand for oil, especially in China. Americans are consuming as much as ever, not saving anything. All of a sudden, however, a lot of these ARMs are resetting so homeowners who were 2 years ago paying 4.5% are all of a sudden paying 8%. In subprime land, it’s more like 7% to 12% or some such nonsense. All of a sudden, the mortgage payment goes from $2,000 a month, which was a stretch, to $3,500 or more. So all these homeowners start defaulting on their loans. The lenders in turn pass the bad news to investors who start seeing interest shortfalls on their BBBs and BBB-s. So these guys are pissed and selling the assets. In the meantime, Japan’s raising rates and there’s a lot of Americans owing a lot of Yen.

What a mess…

Anyway, I’ve totally rambled on, but what’s cool about all this as far as I’m concerned is that I’m currently implementing Credit Derivatives on Asset Backed Securities (CDS on ABS, for short) and my employer plans to go live on indices within a month. All this is so very a propos especially with the subprime mortgage meltdown. Basically lenders use credit derivatives to minimize their credit risk. If you’re a lender, you buy protection by paying a monthly premium to a third party in exchange for them to cover you if there’s a default on the loan.

Right now our focus is to finish the implementation of ABX indices as we’re going live with that early April. I’ve been spearheading the effort, making enhancements and especially interfacing with MarkIT data feeds. It’s cool because it’s still so new everyone is trying to figure out how these creatures work. I’m pulling XML data from MarkIT before it’s even completely ripe… I’m on the cutting edge of finance and lovin’ it!

Think Outside the Box

I came upon an interesting problem late last week.

I’ve recently implemented an OTR Bond interface to Calypso so that, when new TIBCO messages alerts us of a new “On-the-run” Bond issue, we can insert the appropriate data into Calypso. Of course, if it’s a new Bond, there’s also a good chance that we don’t yet have the Bond definition in Calypso and, for that, we use the Bloomberg Connect API to retrieve the Bond and link it to the Calypso BondBenchmark… Sorry. That was just an overview but I can already see that most of y’all have your eyes all glazed over. Didn’t mean to bore you to death.

Anyway, I’m rather familiar with Calypso’s Bloomberg Connect API and how it works. Puh-lease… give me a hard one!!! After all, I pretty much wrote the whole thing single-handedly a couple of years ago. Let’s just say that a request file is generated, then FTP’ed over to Bloomberg. Then, it’s your responsibility to poll the remote directory for the equivalent response file. Once it is available, you download it and parse it locally with the data you requested. Pretty straightforward, right?

Well, when I implemented the Bloomberg interface, I thought of the “requested” scenario and never bothered (foolish me!) to think outside the box. Let’s say you request a Bond with CUSIP 123456, then I’d simply generate a request file called cy_123456.req (cy being short for Calypso. Y’see? To top it all off Bloomberg states that there’s a maximum number of characters in the file name. Some puny amount like 12 or 15, I think.) So anyway, cy_123456.req would get processed and FTP’ed to the remote directory. Then, I’d poll every once in a while for a file called cy_123456.out. When the file would be present, I’d FTP it back to my local directory and remove the file remotely, thus cleaning up after myself.

This ain’t rocket science, right? That should work just fine, no?

If you’ve got one instance of Bloomberg Connect running, it runs fine and dandy, yes… but here at Countrywide, we got a bunch of environments all running in parallel. We’ve got a couple of development boxes, UAT, and of course, the production environment. The configuration for these boxes are identical and the OTR Bond adapter task kicks off from 4 AM until 5:30 AM. So now you have these 5 or 6 processes all running on different boxes each listening to TIBCO. Let’s say at 4:05 TIBCO broadcasts: The 3 month OTR Bond has CUSIP 2223123. Well what’s been happening is this: these processes would check Calypso and determine it that Gosh-darn-it… we ain’t got that Bond definition. Let’s go fetch it!

5 processes on 5 different machines each generating a request file, cy_2223123.req. Some time passes and the Bloomberg Engine running on each box processes the file and uploads it to the remote (COMMON!) Bloomberg directory. Some time later, these 5 processes would each look for some file, cy_2223123.out, download it back locally and proceed to delete the remote file.

Hmmm…. can I get semaphores implemented on top of FTP? On second thought, don’t answer that…

I’m surprised that the process failed so infrequently, but occasionally, you’d have some poor process who’d never get its .out file. No wonder, some selfish sibling would have wiped it out before he got a chance to retrieve it. The funny thing is that, had I even pondered the question back at Calypso, it would have easily been apparent. I just never bothered to think: “Hey, wait… what if they run this process 5 times instead of just once, which seems logical.”

The quick fix? We’ve decided, until Calypso issues an official fix, to stagger the execution time for OTR Processing. Typically TIBCO will broadcast OTR information sometime between 3 and 4 AM and, of course, that information remains cached for new subscribers. So as long as we don’t connect before 4 AM and we have each box start processing OTR Bond messages on TIBCO at 10 minute interval, each instance of Bloomberg Engine should, hopefully, get to do its thing by itself. It’s not the most elegant solution, but hopefully it’ll do the trick!

On-the-run Bonds

For the last few weeks, I’ve worked on implementing OTR Bonds in Calypso. An OTR Treasury security is the most recently auctioned Treasury bill, note or bond of a stated maturity, with OTR an abbreviation for “On-the-Run.” Today, 09/29/2006, for instance, the 3 Month OTR Bond has CUSIP 912795YL9 and was issued yesterday with a maturity of 12/28/2006. In 15 days, however, it will roll to a different Bond issued sometime in mid-October. So, in essence, the 3 month OTR Bond is a reference to an actual bond. Calypso’s modeled this quite nicely, in fact, with the concept of a Bond Benchmark which, itself, is an instrument that can be priced or traded, but actually references a “benchmark” Bond. Simply put, OTR securities provide a convenient way to model market data curves for Fixed Income Trading.

The thing that’s been rather tricky, actually, is to interface Calypso with a Treasury TIBCO feed that supplies us with the latest OTR Bond aliases. Obviously enough, the first step is to retrieve the Bond Benchmark to make sure that OTR Bond and the actual benchmark match the data provided by TIBCO. If they don’t match, then the Bond Benchmark needs to be updated as well. Then, however, we also need to update Calypso’s feed config so it knows how to retrieve the latest quote from TIBCO in the future.

It’s actually not all that complicated, except that the persistence layer for Calypo’s Bond Benchmark keeps barfing up NullPointerExceptions. Apparently it tries to do some caching, but it’s really not all that good at it, and considering that you’re dealing with… hell, let’s say one thousand bond benchmarks, Calypso’d be better off just fetching the data from the DB instead of spewing exceptions at me. That’s simply not polite.

Unfortunately, Calypso doesn’t give you the ability to trade a Bond Benchmark. You can trade the underlying Bond, but apparently trading a reference was too hairy to design. It’s true that it adds a level of complexity since you would need to take a “snapshot” of the benchmark at the point in time when the trade is created. If that trade settles a month down the line, the 3 month OTR Bond will have changed, but obviously the Bond which was traded will not! Then again, seeing it from that perspective, it might be just a little too confusing to keep track of. A nifty feature, though, would be to at least be able to enter a trade with the 3 month OTR Bond and having Calypso automatically fetch and populate the trade entry screen with the appropriate bond.

I’m kind of curious why not create a generic ProductPointer that can be traded and have the BondBenchmark either extend the base class or implement the interface. My gut says I’d go with an abstract base class since you’d want to bring down as much functionality as possible into the superclass and just give hooks to customize the behavior.

Have a good weekend,

From Conjunction Junction to Infastructure Juncture

I’m currently working with the infrastructure group to improve the scalability of our system. Although built on sound Java technology with sound architecture that clearly puts 99.9% of comparable systems to shame, clients will always find ways to push the boundaries.

Some clients will say, “Since our end of day risk analysis scheduled tasks only take 20 minutes to run thanks to the wonderful performance of your system, can’t you guys just give us real-time portfolio pricing?” Others might simply think that it’s perfectly natural that one should never have to go to the database: “Yes, we’d really like to have 250,000 trades in memory at all time, including all associated advice documents, settlements, documents generated and those received from our counterparties. Why? Is that a problem?”

Frankly, I think it’s totally legit… you’re always going to want to push the envelope. So I’ve recently moved to the infrastructure group as they need my help to migrate our back-end and make it adaptable to high-performance distributed caching tools such as GemFire and Coherence. In theory it actually doesn’t look so complex since we already have a fairly clean cache API (with an internal implementation) so my idea is to upgrade this and adapt the framework a little bit in order for us to be jCache compliant. Once that’s done, we should be able to theoretically plug in any 3rd party Cache and voila… or not Voila, since the French can’t seem to agree to anything these days other than holding some sort of weekly strike… But anyway, the point is that it shouldn’t be too hard… though I am a bit worried about cache consistency and database transactions. The API doesn’t provide a way to delegate this to the cache implementation, so I’m guessing it’s up to us to deal with this. Anyway, that’s what i’m up to right now…. Still doing some infrastructure level enhancements on the report framework though, but most of our Back Office development is migrating back to Europe, which makes sense since settlement typically occurs closer to GMT so most of the knowledgeable Back Office staff is typically found in Paris and London.

When I’m doing serious refactoring and design work where I don’t interact much with others, I tend to immerse myself in code with the help of music and I’ve been listening to Launchcast a lot lately. I was thinking today: They know my favorite artists, and they know (or should be able to know) new release dates. So how come they don’t give fans “sneak peeks” at upcoming album releases, potentially even making deals with major labels so that I can buy the album 2 weeks before it goes out to the general public? I’d love to start hearing a rotation of Morcheeba songs before the album hits the streets…

Ciao,

David

Ideas emerging from necessity

It’s funny how software comes to life…

I designed a reporting framework for Calypso. It is used to plug easily and extract data from the system. Among the many features, it’s possible to customize the way the data is displayed, whether to aggregate and on what fields, keeping subtotals, averages, and various other aggregation functions, and easily allows the data to be exported to HTML, Excel, and PDF. Simply put, you write 3 classes, one that defines your search criteria, the main class that takes this search criteria and “queries” the system via the API, and a class that extracts discrete column values from an object. You compile these classes, plug the name of the new report into a database table, and you’re good to go: You will get a GUI for this report and it will allow you to sort, aggregate, basically slice and dice your data every which way you like. You can export the data to all the aforementioned file formats…. It’s probably very similar to Crystal Reports and is definitely similar to Excel in that we’ve implemented some functionality based on what we found in there. The nice thing, though, is that because it’s built and tailor-made to plug into the API, it provides a nice level of abstraction. I can typically crank out a brand new vanilla report in half a day. No kidding.

The report framework is only about a year and a half old and I pretty much worked on it solo for the first 9 months. Now we have probably 5 to 6 developers using it, making enhancements, refactoring the code, what not. It’s been integrated into a variety of GUI windows and we have over 60 different reports built on top of this framework. What’s really interesting though is its reason for existing. Did we brainstorm and decide we needed to offer this functionality, both for use internally and for our clients? (Several clients are loving it, by the way, and are already building upon this framework extensively.)

Nope.

The reporting framework was born out of sheer desperation as I was assigned 15 back office reports to implement from scratch with a delivery 2 weeks later. Well, actually the delivery was 4 weeks later, but this enhancement request for a very large New York-based bank came into my mailbox exactly 11 days before I was to head off to Tuscany for a wedding.

“Let me get this straight,” I thought, “They want 15 brand new reports, with GUI and the ability to run on a scheduled basis?” I did the quick math… “That’s a minimum of 75 new classes that I need to write and debug if I am to deliver all these reports and follow the existing design guidelines to implement reports.” Not completing the reports was not an option. It had been negotiated from above, and I certainly sympathize with a client that’s paid a lot of money for a very good SLA. But we’re talking Tuscany here… Sienna, Firenze, and Brunello. Not going to Italy was not an option!

So I figured my best bet was to build upon the existing interface-less report methodology, refactor the code extensively, and make these new reports “pluggable” into a generic GUI window. Next thing you know, the reporting framework was born.

I’m just thinking back on this as I am currently drafting a developer’s guide on the subject. Since that beautiful September day when I began coding it, the reporting framework has grown at mindblowing speeds. It can be updated real-time by plugging into our event server. Search criteria can be exported to XML and be shared across multiple users. Clients can extend the framework and build their own custom aggregation functions. We provide sums, averages, minimums, and maximum functions out of the box. All kinds of tweaks can be made to the look of the output as views can be customized with row and column color settings based on whether such condition is met.

It’s a trip to see the organic growth of this framework even with just a handful of developers having access to the source code… Have a great weekend.

Peace.

Writing a language interpreter

Today was a kickass day… I love days like this. I woke up this morning with feedback from one of our consultants in Tokyo. Sumitomo Trust went live on FX yesterday and it turns out that they processed 240 incoming MT202 SWIFT messages yesterday and the STP process is working well. The messages come in via MQ and the trades are automatically matched with appropriate payments generated. I felt quite gratified because I seriously suffered trying to implement this stuff.

I spent the rest of the day implementing iteration in our advice document generation language. Calypso generates different formats for advice documents (SWIFT, HTML, text) to be sent from the system via various gateways (SWIFT, E-mail, printer, fax) and the HTML documents are generated based on template files using a rudimentary in-house processing language and keyword substitution. I implemented conditionals a while back but it finally came time to add in iteration. Talk about a blast to code! I’ve got a command queue, contexts and variables being pushed on and off the stack (since we can have nested conditionals and, now, nested iterations). It had been a while since I’d revisited that code and I took advantage of this enhancement to do a lot of refactoring. From the onset (and for historical reasons) I’ve been using JavaCup to generate a parse tree. With the added complexity, however, I’ve moved all of the processing outside of the cryptic cup file, especially now that I’ve got a command queue (since we can only actually execute the commands within the iteration block after we’ve exited it.) Granted, there’s still plenty of performance optimization to do, and I’ve got to investigate whether or not I can use a cache. One limitation of the current setup is that the conditional statements within the iteration block can’t actually reference the iterator. That bugs me, but only a little since all that is really being done is keyword substitution in generated HTML. I’m also pushing the limits of JavaCup, I think… Thankfully, I’ve got the companion book at home so it looks like that’ll be coming off the shelf tonight and I’ll be burning the midnight oil.