Skip to content

Commit e3f3e4d

Browse files
committed
SettlementType
1 parent f7877b7 commit e3f3e4d

7 files changed

Lines changed: 83 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
88

99
- Support for optional Instrument `multiplier` value
1010
- `Position.value_local` and `update_valuation! = update_pnl!` functions
11+
- `SettlementType` enum for specifying settlement types of instruments (Asset vs. Cash)
1112

1213
### Changed
1314

src/Fastback.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export Fastback,
2323
Price,
2424
Quantity,
2525
TradeDir,
26+
SettlementStyle,
2627
Cash,
2728
Instrument,
2829
Order,

src/enums.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Base: *, sign
22
using EnumX
33

44
@enumx TradeDir::Int8 Null = 0 Buy = 1 Sell = -1
5+
@enumx SettlementStyle::Int8 Asset = 1 Cash = 2
56

67
@inline sign(x::TradeDir.T) = Quantity(Int8(x))
78
@inline is_long(dir::TradeDir.T) = dir == TradeDir.Buy

src/instrument.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ mutable struct Instrument{IData}
1313
const quote_symbol::Symbol
1414
const quote_tick::Price # minimum price increment of base asset
1515
const quote_digits::Int # number of digits after the decimal point for display
16+
17+
const settlement::SettlementStyle.T
1618
quote_cash_index::Int
17-
19+
1820
const multiplier::Float64
1921
const metadata::IData
2022

@@ -29,6 +31,7 @@ mutable struct Instrument{IData}
2931
base_digits=2,
3032
quote_tick::Price=0.01,
3133
quote_digits=2,
34+
settlement::SettlementStyle.T=SettlementStyle.Cash,
3235
multiplier::Float64=1.0,
3336
metadata::IData=nothing
3437
) where {IData}
@@ -43,6 +46,7 @@ mutable struct Instrument{IData}
4346
quote_symbol,
4447
quote_tick,
4548
quote_digits,
49+
settlement,
4650
0, # quote_cash_index
4751
multiplier,
4852
metadata

src/logic.jl

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
@inline function update_valuation!(acc::Account, pos::Position, close_price)
22
# update position valuation and account equity using delta of old and new value
3+
inst = pos.inst
34
new_pnl = calc_pnl_local(pos, close_price)
4-
new_value = new_pnl
5+
if inst.settlement == SettlementStyle.Asset
6+
new_value = pos.quantity * close_price * inst.multiplier
7+
else
8+
new_value = new_pnl
9+
end
510
value_delta = new_value - pos.value_local
6-
quote_cash_index = pos.inst.quote_cash_index
11+
quote_cash_index = inst.quote_cash_index
712
@inbounds acc.equities[quote_cash_index] += value_delta
813
pos.pnl_local = new_pnl
914
pos.value_local = new_value
@@ -30,37 +35,41 @@ end
3035
commission::Price=0.0, # fixed commission in quote (local) currency
3136
commission_pct::Price=0.0, # percentage commission of nominal order value, e.g. 0.001 = 0.1%
3237
)::Trade{TTime,OData,IData} where {TTime<:Dates.AbstractTime,OData,IData,CData}
38+
inst = order.inst
3339
# get quote asset index
34-
quote_cash_index = order.inst.quote_cash_index
40+
quote_cash_index = inst.quote_cash_index
3541

3642
# positions are netted using weighted average price,
3743
# hence only one static position per instrument is maintained
38-
pos = get_position(acc, order.inst)
44+
pos = get_position(acc, inst)
3945
pos_qty = pos.quantity
4046

4147
# set fill quantity if not provided
4248
fill_qty = fill_qty > 0 ? fill_qty : order.quantity
4349
remaining_qty = order.quantity - fill_qty
4450

4551
# calculate absolute paid commissions in quote currency
46-
nominal_value = fill_price * abs(fill_qty) * order.inst.multiplier
52+
nominal_value = fill_price * abs(fill_qty) * inst.multiplier
4753
commission += commission_pct * nominal_value
4854

4955
# realized P&L
5056
realized_qty = calc_realized_qty(pos_qty, fill_qty)
51-
realized_pnl = 0.0
57+
realized_pnl_gross = 0.0
5258
if realized_qty != 0.0
5359
# order is reducing exposure (covering), calculate realized P&L
54-
realized_pnl = (fill_price - pos.avg_price) * realized_qty
55-
56-
# add realized P&L to account balance
57-
@inbounds acc.balances[quote_cash_index] += realized_pnl
60+
realized_pnl_gross = (fill_price - pos.avg_price) * realized_qty * inst.multiplier
61+
end
5862

59-
# remove realized P&L from position P&L
60-
pos.pnl_local -= realized_pnl
61-
pos.value_local -= realized_pnl
63+
if inst.settlement == SettlementStyle.Asset
64+
cash_delta = -(fill_price * fill_qty * inst.multiplier) - commission
65+
else
66+
cash_delta = realized_pnl_gross - commission
6267
end
63-
realized_pnl -= commission
68+
@inbounds begin
69+
acc.balances[quote_cash_index] += cash_delta
70+
acc.equities[quote_cash_index] += cash_delta
71+
end
72+
realized_pnl = realized_pnl_gross - commission
6473

6574
# generate trade sequence number
6675
tid = tid!(acc)
@@ -104,11 +113,7 @@ end
104113
# update position quantity
105114
pos.quantity = new_exposure
106115

107-
# subtract paid commissions from account balance and equity
108-
@inbounds acc.balances[quote_cash_index] -= commission
109-
@inbounds acc.equities[quote_cash_index] -= commission
110-
111-
# update P&L of position and account equity (w/o commissions, already accounted for)
116+
# update P&L of position and account equity
112117
update_pnl!(acc, pos, fill_price)
113118

114119
push!(acc.trades, trade)

src/position.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ end
3333
"""
3434
Calculates the P&L of a position in local currency.
3535
36-
The P&L is based on the weighted average price of the position
37-
and the current closing price, without considering commissions.
36+
The P&L is based on the weighted average price of the position,
37+
the current closing price, and the contract multiplier, without considering commissions.
3838
Fees are accounted for in the account equity calculation and execution P&L.
3939
4040
# Arguments
@@ -43,7 +43,7 @@ Fees are accounted for in the account equity calculation and execution P&L.
4343
"""
4444
@inline function calc_pnl_local(pos::Position, close_price)
4545
# quantity negative for shorts, thus works for both long and short
46-
pos.quantity * (close_price - pos.avg_price)
46+
pos.quantity * (close_price - pos.avg_price) * pos.inst.multiplier
4747
end
4848

4949

test/account.jl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,54 @@ end
168168
@test trade.commission == commission_pct * expected_nominal
169169
end
170170

171+
@testitem "Spot long asset-settled valuation" begin
172+
using Test, Fastback, Dates
173+
174+
acc = Account()
175+
deposit!(acc, Cash(:USD), 10_000.0)
176+
177+
inst = register_instrument!(acc, Instrument(Symbol("SPOT/USD"), :SPOT, :USD; settlement=SettlementStyle.Asset))
178+
pos = get_position(acc, inst)
179+
180+
dt = DateTime(2021, 1, 1)
181+
price = 50.0
182+
qty = 100.0
183+
order = Order(oid!(acc), inst, dt, price, qty)
184+
fill_order!(acc, order, dt, price)
185+
186+
@test cash_balance(acc, :USD) 5_000.0
187+
@test pos.value_local 5_000.0
188+
@test equity(acc, :USD) 10_000.0
189+
190+
update_pnl!(acc, pos, 60.0)
191+
@test pos.value_local 6_000.0
192+
@test equity(acc, :USD) 11_000.0
193+
end
194+
195+
@testitem "Spot short asset-settled valuation" begin
196+
using Test, Fastback, Dates
197+
198+
acc = Account()
199+
deposit!(acc, Cash(:USD), 10_000.0)
200+
201+
inst = register_instrument!(acc, Instrument(Symbol("SPOT/USD"), :SPOT, :USD; settlement=SettlementStyle.Asset))
202+
pos = get_position(acc, inst)
203+
204+
dt = DateTime(2021, 1, 1)
205+
price = 50.0
206+
qty = -100.0
207+
order = Order(oid!(acc), inst, dt, price, qty)
208+
fill_order!(acc, order, dt, price)
209+
210+
@test cash_balance(acc, :USD) 15_000.0
211+
@test pos.value_local -5_000.0
212+
@test equity(acc, :USD) 10_000.0
213+
214+
update_pnl!(acc, pos, 60.0)
215+
@test pos.value_local -6_000.0
216+
@test equity(acc, :USD) 9_000.0
217+
end
218+
171219

172220
@testitem "Account with Date timestamps" begin
173221
using Test, Fastback, Dates, Tables

0 commit comments

Comments
 (0)