npm package: @pipsend/sdk
Description: SDK oficial de Pipsend compatible con NodeJS y TypeScript
Author: Pipsend
Latest version: 0.3.2

# @pipsend/sdk

[![npm version](https://img.shields.io/npm/v/@pipsend/sdk.svg)](https://www.npmjs.com/package/@pipsend/sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Official Pipsend SDK for Node.js and TypeScript. Complete trading platform integration with WebSocket support.

## 🚀 Features

- ✅ **6 Complete API Modules**: Accounts, Orders, Positions, Trades, Market Data, Trading Groups
- ✅ **TypeScript Native** with 100+ complete types
- ✅ **WebSocket Support** with auto-reconnect and heartbeat
- ✅ **Advanced Filtering** (dates, ranges, multi-value, search)
- ✅ **Dynamic Statistics** grouped by any field
- ✅ **Pagination Support** for all list endpoints
- ✅ **Dual Package** (ESM + CommonJS)
- ✅ **56 Unit Tests** passing
- ✅ **Compatible with Node.js ≥16**

## 📦 Installation

```bash
npm install @pipsend/sdk
```

```bash
yarn add @pipsend/sdk
```

```bash
pnpm add @pipsend/sdk
```

## 🔐 Authentication

The SDK uses JWT-based authentication with automatic token refresh.

```typescript
import { createClient } from "@pipsend/sdk";

const client = createClient({
  server: "http://localhost:8080",
  login: "1000", // Your admin/API login
  password: "YourPassword", // Your admin/API password
});

// The SDK automatically:
// ✅ Authenticates on first API call
// ✅ Stores access and refresh tokens in memory
// ✅ Refreshes tokens automatically when they expire
// ✅ Re-authenticates if refresh fails
```

**Authentication happens automatically** - you don't need to call any login method. Just create the client and start making API calls.

### Manual Authentication Control (Advanced)

If you need manual control over authentication:

```typescript
// Check authentication status
const isAuthenticated = client.isAuthenticated();

// Manually refresh token
await client.refreshToken();

// Logout (clears tokens)
await client.logout();

// Get current token info
const tokenInfo = client.getTokenInfo();
console.log("Token expires at:", tokenInfo?.expiresAt);
```

---

## 🔧 Quick Start

```typescript
import { createClient } from "@pipsend/sdk";

// Create authenticated client
const client = createClient({
  server: "http://localhost:8080",
  login: "1000",
  password: "DemoMaster",
});

const login = 50001;

// List accounts with filters
const accounts = await client.accounts.list({
  state: "active",
  country: "Mexico",
  min_balance: 1000,
});

// Open a trade
const trade = await client.trades.open({
  login,
  symbol: "EURUSD",
  action: "buy",
  type: "market",
  volume: 1.0,
  stop_loss: 1.085,
  take_profit: 1.095,
});

// Get market data
const candles = await client.marketData.getCandles({
  symbol: "BTCUSD",
  timeframe: "1h",
  limit: 100,
});

// WebSocket real-time updates
client.stream.onPriceUpdate((price) => {
  console.log("Price update:", price);
});

await client.stream.connect();
```

## 📚 API Modules

### 1. Accounts API

Manage accounts with advanced filtering and statistics.

```typescript
// Create a new trading account
const newAccount = await client.accounts.create({
  trading_group: "Premium",
  country: "MX",
  master_password: "SecurePass123!",
  investor_password: "InvestorPass123!",
  email: "trader@example.com",
  first_name: "John",
  last_name: "Doe",
  leverage: 100,
  state: "active",
});

// List accounts with filters
const accounts = await client.accounts.list({
  state: "active",
  country: "Mexico,United States",
  trading_group: "Premium",
  min_balance: 1000,
  max_balance: 50000,
  page: 1,
  per_page: 20,
});

// Get statistics with filters
const stats = await client.accounts.getStatistics({
  country: "Mexico",
  state: "active",
});

// Get all account logins
const logins = await client.accounts.getLogins();

// Get account status/metrics (balance, equity, credit, margin)
const status = await client.accounts.getStatus(50001);
console.log("Balance:", status.data.balance);
console.log("Equity:", status.data.equity);
console.log("Credit:", status.data.credit);
console.log("Margin:", status.data.margin);

// Change passwords (master and/or investor)
await client.accounts.changePassword(50001, {
  master: "NewMasterPass123!",
  investor: "NewInvestorPass123!",
});

// Archive/Unarchive
await client.accounts.archive(50001);
await client.accounts.unarchive(50001);

// Update account information (partial update)
await client.accounts.update(50001, {
  email: "newemail@example.com",
  leverage: 200,
  city: "Los Angeles",
  trading_group: "Premium",
});

// Adjust balance or credit
await client.accounts.balance(50001, {
  type: "balance",
  amount: 500.0,
  comment: "Client deposit",
});

await client.accounts.balance(50001, {
  type: "credit",
  amount: 1000.0,
  comment: "Credit line increase",
});

// Subtract balance
await client.accounts.balance(50001, {
  type: "balance",
  amount: -200.0,
  comment: "Withdrawal request",
});
```

### 2. Orders API

Manage pending orders with statistics and validation.

```typescript
// List orders with advanced filters
const orders = await client.orders.list({
  login: "50001",
  symbol: "EURUSD,GBPUSD",
  order_type: "limit",
  status: "new,partial",
  created_from: "2025-01-01T00:00:00Z",
  created_to: "2025-01-31T23:59:59Z",
  price_min: 1.08,
  price_max: 1.1,
  sort_by: "created_at",
  sort_order: "desc",
});

// Get statistics grouped by symbol
const stats = await client.orders.getStats({
  login: "50001",
  group_by: "symbol",
});

// Get totals by order type
const totals = await client.orders.getTotals({
  login: "50001",
  group_by: "order_type",
});

// Update order
await client.orders.update(12345, {
  login: 50001,
  stop_loss: 1.08,
  take_profit: 1.095,
});

// Delete order
await client.orders.delete(12345, { login: 50001 });

// Validate orders
const checkResult = await client.orders.check({
  filters: {
    logins: [50001],
    statuses: ["new"],
  },
});
```

### 3. Positions API

Manage open positions with recalculation support.

```typescript
// List positions with filters
const positions = await client.positions.list({
  login: "50001",
  symbol: "EURUSD",
  state: "open",
  profit_min: 100,
  profit_max: 1000,
});

// Get statistics grouped by type
const stats = await client.positions.getStats({
  login: "50001",
  state: "open",
  group_by: "type",
});

// Get totals by symbol
const totals = await client.positions.getTotals({
  login: "50001",
  group_by: "symbol",
});

// Update position
await client.positions.update(1, {
  login: 50001,
  stop_loss: 1.0875,
  take_profit: 1.0975,
});

// Delete position
await client.positions.delete(1, { login: 50001 });

// Recalculate positions
const checkResult = await client.positions.check({
  filters: {
    logins: [50001],
    states: ["open"],
  },
});
```

### 4. Trades API

Execute trading operations with margin checking.

```typescript
// Check margin before trading
const marginCheck = await client.trades.checkMargin({
  login: 50001,
  symbol: "EURUSD",
  action: "buy",
  volume: 1.0,
});

if (marginCheck.can_execute) {
  // Open trade
  const trade = await client.trades.open({
    login: 50001,
    symbol: "EURUSD",
    action: "buy",
    type: "market",
    volume: 1.0,
    stop_loss: 1.085,
    take_profit: 1.095,
    comment: "API Trade",
  });

  // Modify trade
  await client.trades.modify({
    login: 50001,
    position_id: trade.position_id,
    stop_loss: 1.0875,
    take_profit: 1.0975,
  });

  // Calculate potential profit
  const profitCalc = await client.trades.calculateProfit({
    login: 50001,
    symbol: "EURUSD",
    action: "buy",
    volume: 1.0,
    open_price: 1.09,
    close_price: 1.095,
  });

  // Close trade (partial or full)
  await client.trades.close({
    login: 50001,
    position_id: trade.position_id,
    volume: 0.5, // Partial close
    comment: "Taking profit",
  });
}

// Force price (for historical data import)
const historicalTrade = await client.trades.open({
  login: 50001,
  symbol: "EURUSD",
  action: "buy",
  type: "market",
  volume: 1.0,
  price: 1.085,
  force_price: true,
  force_timestamp: "2025-01-15T10:30:00Z",
});
```

### 5. Market Data API

Query historical market data (minimum 1 hour old).

```typescript
// Get candles with filters
const candles = await client.marketData.getCandles({
  symbol: "BTCUSD,ETHUSD",
  timeframe: "1h",
  from: "2025-01-01T00:00:00Z",
  to: "2025-01-02T00:00:00Z",
  min_volume: 1000,
  max_volume: 50000,
  sort: "volume",
  order: "desc",
  limit: 100,
});

// Get last 100 candles
const recentCandles = await client.marketData.getCandles({
  symbol: "BTCUSD",
  timeframe: "1m",
  limit: 100,
  order: "desc",
});

// Get symbols with filters
const symbols = await client.marketData.getSymbols({
  group: "CRYPTO_MAJOR",
  has_data: true,
  search: "bitcoin",
});

// Get groups hierarchy
const groups = await client.marketData.getGroups({
  root_only: true,
  sort: "symbol_count",
  order: "desc",
});
```

**Timeframes**: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`

**Note**: Data must be at least 1 hour old (no real-time data).

### 6. Trading Groups API

Manage trading groups and their statistics.

```typescript
// List trading groups
const groups = await client.tradingGroups.list({
  page: 1,
  per_page: 20,
});

// Get statistics
const stats = await client.tradingGroups.getStatistics();
```

### 7. WebSocket API

Real-time updates for positions and balance with manual channel subscription.

```typescript
// Enable WebSocket in config
const client = createClient({
  server: "http://localhost:8080",
  login: "1000",
  password: "DemoMaster",
  websocket: {
    enabled: true,
    autoConnect: false,
  },
});

// Connect to WebSocket
await client.stream.connect();

// Subscribe to channels
client.stream.subscribe([
  "positions:new", // New positions opened
  "positions:closed", // Positions closed (total and partial)
  "positions:updated", // Position PnL updates (throttled to 1 per 2s)
  "accounts:balance", // Balance updates
]);

// Listen to position opened events
client.stream.onPositionOpened((event) => {
  const pos = event.position;
  console.log(
    "Position opened:",
    pos.symbol,
    pos.side === 1 ? "LONG" : "SHORT",
    pos.qty,
  );
});

// Listen to position closed events
client.stream.onPositionClosed((event) => {
  const pos = event.position;
  console.log("Position closed:", pos.symbol, "PnL:", pos.net_pnl);
});

// Listen to position updated events (throttled)
client.stream.onPositionUpdated((event) => {
  const pos = event.position;
  console.log("Position update:", pos.position_id, "PnL:", pos.unrealized_pnl);
});

// Listen to balance updates
client.stream.onBalanceUpdated((event) => {
  console.log("Balance:", event.balance, "Equity:", event.equity);
});

// Unsubscribe from channels
client.stream.unsubscribe(["positions:updated"]);

// Disconnect
client.stream.disconnect();
```

**Available Channels**:

- `positions:new` - New position opened
- `positions:closed` - Position closed (total or partial)
- `positions:updated` - PnL updates (throttled to max 1 per 2 seconds)
- `accounts:balance` - Balance changes

**Features**:

- Manual subscription to specific channels
- Auto-reconnect with exponential backoff
- Heartbeat/ping-pong mechanism
- Automatic subscription restoration on reconnect
- Throttled position updates to reduce load

See [WebSocket API Documentation](docs/WEBSOCKET_API.md) for complete guide.

## � Documentation

- **[Positions & Trading Guide](docs/POSITIONS_TRADING.md)** - Complete guide for opening, closing, and modifying positions (SL/TP)
- **[WebSocket API](docs/WEBSOCKET_API.md)** - Real-time updates and streaming data
- **[Authentication](AUTHENTICATION.md)** - Authentication system and token management

## �🔧 Configuration

### Basic Configuration

```typescript
const client = createClient({
  server: "http://localhost:8080", // Required: API server URL
  login: "1000", // Required: Admin/API login
  password: "YourPassword", // Required: Admin/API password
});
```

### Advanced Configuration

```typescript
const client = createClient({
  server: "http://localhost:8080",
  login: "1000",
  password: "YourPassword",

  // WebSocket configuration (optional)
  websocket: {
    enabled: true, // Enable WebSocket support
    autoConnect: false, // Auto-connect on client creation
    autoReconnect: true, // Auto-reconnect on disconnect
    maxReconnectAttempts: 5, // Max reconnection attempts
    heartbeatInterval: 30000, // Heartbeat interval in ms
  },
});
```

### Configuration Options

| Option                           | Type    | Required | Default | Description                        |
| -------------------------------- | ------- | -------- | ------- | ---------------------------------- |
| `server`                         | string  | ✅ Yes   | -       | Pipsend API server URL             |
| `login`                          | string  | ✅ Yes   | -       | Admin/API login for authentication |
| `password`                       | string  | ✅ Yes   | -       | Admin/API password                 |
| `websocket.enabled`              | boolean | No       | `false` | Enable WebSocket support           |
| `websocket.autoConnect`          | boolean | No       | `false` | Auto-connect on creation           |
| `websocket.autoReconnect`        | boolean | No       | `true`  | Auto-reconnect on disconnect       |
| `websocket.maxReconnectAttempts` | number  | No       | `5`     | Max reconnection attempts          |
| `websocket.heartbeatInterval`    | number  | No       | `30000` | Heartbeat interval (ms)            |

## 📦 API Response Structure

All API endpoints return a consistent response structure:

### Success Response (without pagination)

```typescript
{
  status: "success",
  status_code: "SUCCESS",
  data: [...] // Array of results or single object
}
```

### Success Response (with pagination)

```typescript
{
  status: "success",
  status_code: "SUCCESS",
  data: [...],
  meta: {
    total: 156,
    page: 1,
    per_page: 20,
    total_pages: 8,
    has_next: true,
    has_prev: false
  }
}
```

### Error Response

```typescript
{
  status: "error",
  status_code: "VALIDATION_ERROR",
  message: "The provided data is not valid.",
  errors?: [
    {
      field: "symbol",
      message: "At least one symbol is required."
    }
  ]
}
```

### Common Status Codes

**Success:**

- `SUCCESS` - Operation successful
- `CREATED` - Resource created successfully

**Errors:**

- `VALIDATION_ERROR` (400) - Invalid parameters
- `BAD_REQUEST` (400) - Invalid request
- `UNAUTHORIZED` (401) - Authentication required
- `FORBIDDEN` (403) - Insufficient permissions
- `NOT_FOUND` (404) - Resource not found
- `CONFLICT` (409) - Resource conflict (duplicate)
- `INTERNAL_ERROR` (500) - Server error

**Market Data Specific:**

- `SYMBOL_NOT_FOUND` (404) - Symbol not found
- `INVALID_TIMEFRAME` (400) - Invalid timeframe
- `TIME_RANGE_TOO_LARGE` (400) - Time range exceeds maximum
- `DATA_TOO_RECENT` (400) - Data must be at least 1 hour old

## 🎯 Common Patterns

### Pagination

**Option 1: Get all results (no pagination)**

```typescript
// Returns all results in a single array
const accounts = await client.accounts.list({
  state: "active",
});
// Response: { status, status_code, data: [...] }
```

**Option 2: Paginated results**

```typescript
// Returns paginated results with metadata
const accounts = await client.accounts.list({
  state: "active",
  page: 1,
  per_page: 20,
});
// Response: { status, status_code, data: [...], meta: {...} }

// Iterate through pages
let page = 1;
let hasMore = true;

while (hasMore) {
  const response = await client.accounts.list({
    state: "active",
    page,
    per_page: 50,
  });

  // Process response.data
  console.log(`Page ${page}:`, response.data.length);

  hasMore = response.meta.has_next;
  page++;
}
```

**Option 3: Limit results (ignores pagination)**

```typescript
// Get specific number of results
const candles = await client.marketData.getCandles({
  symbol: "BTCUSD",
  timeframe: "1h",
  limit: 100, // Max: 10,000 for candles
});
```

### Multi-value Filters

Many filters accept comma-separated values:

```typescript
// Multiple symbols (OR logic)
const positions = await client.positions.list({
  symbol: "EURUSD,GBPUSD,USDJPY", // Positions in ANY of these symbols
});

// Multiple countries
const accounts = await client.accounts.list({
  country: "Mexico,United States,Canada",
});

// Multiple states
const orders = await client.orders.list({
  status: "new,partial", // Orders with status new OR partial
});
```

### Date Filters

All date parameters must be in **ISO 8601 / RFC3339 format** (UTC):

```typescript
// Correct format
const positions = await client.positions.list({
  opened_from: "2025-01-01T00:00:00Z",
  opened_to: "2025-01-31T23:59:59Z",
});

// Also valid
const candles = await client.marketData.getCandles({
  symbol: "BTCUSD",
  timeframe: "1h",
  from: "2025-01-15T10:30:00Z",
  to: "2025-01-15T18:30:00Z",
});
```

**Note**: Market data has a minimum age of 1 hour. The `to` parameter cannot be more recent than `now - 1 hour`.

### Range Filters

Use `min_*` and `max_*` for numeric ranges:

```typescript
// Balance range
const accounts = await client.accounts.list({
  min_balance: 1000,
  max_balance: 50000,
});

// Profit range
const positions = await client.positions.list({
  profit_min: 100,
  profit_max: 1000,
});

// Volume range
const candles = await client.marketData.getCandles({
  symbol: "BTCUSD",
  timeframe: "1h",
  min_volume: 1000,
  max_volume: 50000,
});
```

### Sorting

Most list endpoints support sorting:

```typescript
const orders = await client.orders.list({
  login: "50001",
  sort_by: "created_at", // Field to sort by
  sort_order: "desc", // 'asc' or 'desc'
});

const candles = await client.marketData.getCandles({
  symbol: "BTCUSD",
  timeframe: "1h",
  sort: "volume", // Some endpoints use 'sort'
  order: "desc", // instead of 'sort_by'/'sort_order'
});
```

## ⚠️ Error Handling

All errors thrown by the SDK are instances of `PipsendError` with detailed information.

### Basic Error Handling

```typescript
import { PipsendError } from "@pipsend/sdk";

try {
  const trade = await client.trades.open({
    login: 50001,
    symbol: "EURUSD",
    action: "buy",
    type: "market",
    volume: 1.0,
  });

  console.log("Trade opened:", trade.position_id);
} catch (error) {
  if (error instanceof PipsendError) {
    console.error("Error code:", error.code);
    console.error("Status code:", error.statusCode);
    console.error("Message:", error.message);

    // Check if it's a validation error
    if (error.isValidationError()) {
      console.error("Validation errors:", error.errors);
      // Get formatted message with all details
      console.error(error.getDetailedMessage());
    }
  }
}
```

### Handling Validation Errors (422)

When the API returns validation errors, the SDK provides detailed field-level information:

```typescript
try {
  const account = await client.accounts.create({
    trading_group: "InvalidGroup",
    country: "XYZ",
    master_password: "short",
    investor_password: "short",
    email: "invalid-email",
    first_name: "John",
    last_name: "Doe",
  });
} catch (error) {
  if (error instanceof PipsendError && error.isValidationError()) {
    console.log("Validation failed:");

    // Access individual validation errors
    error.errors?.forEach((err) => {
      console.log(`  - ${err.field}: ${err.message}`);
    });

    // Or get formatted message
    console.log(error.getDetailedMessage());

    // Output:
    // The provided data is not valid.
    //   - trading_group: Trading group 'InvalidGroup' not found
    //   - country: Country 'XYZ' not found
    //   - master_password: The field master_password must be at least 8 characters.
    //   - email: The field email must be a valid email address.
  }
}
```

### Error Properties

```typescript
interface PipsendError {
  message: string; // Error message
  code: string; // Error code (e.g., 'VALIDATION_ERROR', 'NOT_FOUND')
  statusCode: number; // HTTP status code (e.g., 422, 404, 500)
  errors?: ValidationError[]; // Validation errors (for 422 responses)
  response?: any; // Full error response from server

  // Helper methods
  isValidationError(): boolean; // Check if it's a validation error
  getDetailedMessage(): string; // Get formatted message with details
}

interface ValidationError {
  field: string; // Field name that failed validation
  message: string; // Validation error message
}
```

### Common Error Scenarios

```typescript
// 1. Insufficient margin
try {
  await client.trades.open({...});
} catch (error: any) {
  if (error.status_code === 'INSUFFICIENT_MARGIN') {
    // Check margin first
    const check = await client.trades.checkMargin({...});
    console.log('Required margin:', check.margin_required);
  }
}

// 2. Position not found
try {
  await client.positions.update(999, {...});
} catch (error: any) {
  if (error.status_code === 'NOT_FOUND') {
    console.error('Position does not exist');
  }
}

// 3. Invalid time range
try {
  await client.marketData.getCandles({
    symbol: 'BTCUSD',
    timeframe: '1m',
    from: '2025-01-01T00:00:00Z',
    to: '2025-02-01T00:00:00Z'  // Too large for 1m
  });
} catch (error: any) {
  if (error.status_code === 'TIME_RANGE_TOO_LARGE') {
    console.error('Time range exceeds maximum for this timeframe');
    // Max ranges: 1m=7d, 5m=30d, 15m=60d, 1h=6mo, 4h=1y, 1d=5y
  }
}

// 4. Data too recent
try {
  await client.marketData.getCandles({
    symbol: 'BTCUSD',
    timeframe: '1h',
    to: new Date().toISOString()  // Too recent!
  });
} catch (error: any) {
  if (error.status_code === 'DATA_TOO_RECENT') {
    console.error('Market data must be at least 1 hour old');
  }
}
```

## 📝 TypeScript Support

The SDK is written in TypeScript and provides complete type definitions:

```typescript
import type {
  Account,
  Order,
  Position,
  Candle,
  Symbol,
  OpenTradeRequest,
  OpenTradeResponse,
} from "@pipsend/sdk";
```

## 🧪 Testing

```bash
# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Type checking
npm run typecheck

# Linting
npm run lint
```

## 🛠️ Development

```bash
# Install dependencies
npm install

# Build
npm run build

# Watch mode
npm run dev
```

## 📖 Examples

Check the `examples/` directory for complete usage examples:

- `examples/api-usage.ts` - Comprehensive API usage
- `examples/quick-start.ts` - Quick start guide

## 🔗 Links

- [Documentation](https://docs.pipsend.com)
- [NPM Package](https://www.npmjs.com/package/@pipsend/sdk)
- [GitHub Repository](https://github.com/pipsend/sdk)
- [Report Issues](https://github.com/pipsend/sdk/issues)

## 📄 License

MIT © 2025 Pipsend

## 🤝 Contributing

Contributions are welcome! Please:

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## ⚠️ Project Status

**Current Version: 0.1.0 (Beta)**

This SDK is in active development. The API may change between minor versions until version 1.0.0.

## 🌟 Features Roadmap

- [ ] Authentication with JWT tokens
- [ ] Rate limiting handling
- [ ] Retry logic with exponential backoff
- [ ] Request/response interceptors
- [ ] Batch operations
- [ ] Streaming data export
- [ ] Advanced error handling
- [ ] Performance metrics
- [ ] Request caching
- [ ] GraphQL support (future)

## 📊 Stats

- **6 API Modules**: Accounts, Orders, Positions, Trades, Market Data, Trading Groups
- **100+ TypeScript Types**: Fully typed interfaces
- **56 Unit Tests**: Comprehensive test coverage
- **Bundle Size**: ~26 KB (minified)
- **Zero Dependencies**: Lightweight and fast